avoigt1121
Claude Opus 5.5
feat: the five knowledge endpoints, gradio_ui and frozen descriptor (s2)
3ed6b66 Download src/endpoints/pathways.py from anne-voigt/pdac-knowledge-agent-dev: direct link, hf CLI and curl.
- Browser
- Download file 4.78 kB
-
https://huggingface.co/spaces/anne-voigt/pdac-knowledge-agent-dev/resolve/main/src/endpoints/pathways.py
- Command line
-
hf download hf://spaces/anne-voigt/pdac-knowledge-agent-dev/src/endpoints/pathways.py
-
curl -L -o pathways.py https://huggingface.co/spaces/anne-voigt/pdac-knowledge-agent-dev/resolve/main/src/endpoints/pathways.py
4.78 kB
| """pathways_for_genes — which Reactome pathways each gene's protein belongs to. | |
| Membership, never enrichment: a row says "Reactome annotates this protein to this pathway", and | |
| nothing is computed over the gene list. ``shared_by_all`` lists the pathways every resolved gene | |
| belongs to — a set intersection over Reactome's own annotation, with no statistic attached. | |
| Which pathways are *enriched* in a gene list is DecoupleRpy's question. | |
| Symbols resolve through HGNC to UniProt accessions; accessions may also be given directly. | |
| Reactome's UniProt mapping returns the lowest-level pathways (Phase 0 Q9: ISG15, RSAD2 and OAS1 | |
| in "Interferon alpha/beta signaling"). | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| from src import payloads | |
| from src.endpoints._common import ( | |
| Budget, | |
| Collector, | |
| according_to, | |
| get_client, | |
| resolution_rows, | |
| resolve_symbols, | |
| stamped, | |
| ) | |
| from src.identifiers import parse_gene_list | |
| ENDPOINT = "pathways_for_genes" | |
| REACTOME_MAP = "Reactome_map_uniprot_to_pathways" | |
| def pathways_for_genes(genes: str = "") -> dict[str, Any]: | |
| parsed = parse_gene_list(genes, allow_uniprot=True, allow_ensembl=False) | |
| if not parsed.ok: | |
| return payloads.refused(ENDPOINT, parsed.reason, parsed.message) | |
| client, budget, out = get_client(), Budget(), Collector() | |
| symbols = [i.value for i in parsed.identifiers if i.kind == "symbol"] | |
| resolved = resolve_symbols(symbols, client, budget, out) | |
| proteins: list[tuple[str, str]] = [] # (gene label, UniProt accession) | |
| for gene in resolved: | |
| if not gene.uniprot_ids: | |
| out.unresolved_input( | |
| gene.input, | |
| f"HGNC links no UniProt entry (locus type: {gene.locus_type or 'not stated'})", | |
| ) | |
| proteins += [(gene.symbol, acc) for acc in gene.uniprot_ids] | |
| proteins += [(i.value, i.value) for i in parsed.identifiers if i.kind == "uniprot"] | |
| by_gene: dict[str, set[str]] = {} | |
| names: dict[str, str] = {} | |
| for label, acc in proteins: | |
| result = out.call(client, budget, REACTOME_MAP, {"uniprot_id": acc}, unknown=("404", label)) | |
| if result is None: | |
| continue | |
| pathways = result.data or [] | |
| by_gene.setdefault(label, set()) | |
| for pw in pathways: | |
| st_id = pw["stId"] | |
| names[st_id] = pw["displayName"] | |
| if st_id in by_gene[label]: | |
| continue | |
| by_gene[label].add(st_id) | |
| out.rows.append( | |
| stamped( | |
| { | |
| "gene": label, | |
| "uniprot_accession": acc, | |
| "pathway_id": st_id, | |
| "pathway_name": pw["displayName"], | |
| "is_in_disease": bool(pw.get("isInDisease")), | |
| "species": pw.get("speciesName"), | |
| }, | |
| result, | |
| ref=st_id, | |
| url=f"https://reactome.org/content/detail/{st_id}", | |
| ) | |
| ) | |
| if not out.rows and out.source_errors and not by_gene: | |
| return payloads.error( | |
| ENDPOINT, | |
| "all_sources_failed", | |
| "No source answered; nothing was looked up.", | |
| source_errors=out.source_errors, | |
| ) | |
| shared = sorted(set.intersection(*by_gene.values())) if len(by_gene) > 1 else [] | |
| lead = according_to(["reactome"], out.retrieved) | |
| sentences = [ | |
| f"{lead}, {label} is annotated to {len(ids)} lowest-level pathway" | |
| f"{'s' if len(ids) != 1 else ''}." | |
| for label, ids in by_gene.items() | |
| ] | |
| if len(by_gene) > 1: | |
| sentences.append( | |
| f"Pathways all {len(by_gene)} genes belong to: " | |
| + (", ".join(f"{names[s]} ({s})" for s in shared) if shared else "none") | |
| + "." | |
| ) | |
| sentences.append("This is membership, not enrichment: no statistic was computed.") | |
| if out.unresolved: | |
| sentences.append( | |
| "Not resolved: " | |
| + "; ".join(f"{u['input']} ({u['reason']})" for u in out.unresolved) | |
| + "." | |
| ) | |
| if out.source_errors: | |
| sentences.append( | |
| "Source problems: " | |
| + "; ".join(f"{e['source']} {e['reason']}" for e in out.source_errors) | |
| + "." | |
| ) | |
| return payloads.ok( | |
| ENDPOINT, | |
| query={"genes": [i.value for i in parsed.identifiers]}, | |
| rows=out.rows, | |
| sources_used=out.sources_used or {"reactome"}, | |
| narrative=" ".join(sentences), | |
| unresolved=out.unresolved, | |
| source_errors=out.source_errors, | |
| resolved_identifiers=resolution_rows(resolved), | |
| shared_by_all=[{"pathway_id": s, "pathway_name": names[s]} for s in shared], | |
| ) | |