avoigt1121
C4: curate-and-cache β€” the cBioPortal API leaves the request path
cb94eb2
Raw
History Blame Contribute Delete
2.87 kB
"""Lineage filter β€” select the pancreatic subset of a pan-cancer source.
Spike caveat 2: CCLE (`ccle_broad_2019`) is 1,739 cell lines across every lineage, but
Carl's intent is *pancreatic* lines. cBioPortal exposes lineage via sample clinical
attributes (`CANCER_TYPE` / `ONCOTREE_CODE`), so the pancreatic subset is a clinical-data
filter, NOT a separate study. Registered PDAC tumor cohorts (`paad_tcga`) are already
pancreatic end-to-end and need no filter β€” `needs_lineage_filter()` says which is which.
"""
from __future__ import annotations
from . import cbioportal_io, curated_store
# Pan-cancer sources that must be narrowed to their pancreatic subset before use.
PAN_CANCER_STUDIES = {"ccle_broad_2019"}
# Clinical-attribute values (any-case substring / exact) that mark a pancreatic sample.
_CANCER_TYPE_MATCH = "pancreatic" # CANCER_TYPE == "Pancreatic Cancer"
_ONCOTREE_PANC = {"PAAD", "PAAC", "PANET", "UCP", "SPN", "PB", "PAASC", "ACCA"} # OncoTree pancreas codes
def needs_lineage_filter(study_id: str) -> bool:
"""True for pan-cancer sources (CCLE); False for registered PDAC cohorts."""
return study_id in PAN_CANCER_STUDIES
def _attribute(study_id: str, attribute_id: str) -> dict[str, str]:
"""`{sampleId: value}` for a clinical attribute β€” from the curated artifact if there is one.
ADR-0005 C4: the request path must not call the API. A curated study carries its clinical
attributes in the artifact, so the lineage filter resolves offline. The live call remains
only for curation time (and for tests that patch the client).
"""
cached = curated_store.clinical_values(study_id, attribute_id)
if cached is not None:
return dict(cached)
return {
r["sampleId"]: (r.get("value") or "")
for r in cbioportal_io.clinical_data(study_id, [attribute_id])
}
def pancreatic_sample_ids(study_id: str) -> list[str]:
"""Sample ids in `study_id` whose clinical lineage is pancreatic.
Prefers `CANCER_TYPE`; falls back to `ONCOTREE_CODE`. Returns them sorted so the
curated subset is deterministic (fixture/CI stability). Raises if neither attribute
resolves any pancreatic sample β€” better to refuse than silently return the whole
pan-cancer panel.
"""
cancer_type = _attribute(study_id, "CANCER_TYPE")
selected = {s for s, v in cancer_type.items() if _CANCER_TYPE_MATCH in (v or "").lower()}
if not selected: # fall back to OncoTree code
for s, v in _attribute(study_id, "ONCOTREE_CODE").items():
if (v or "").upper() in _ONCOTREE_PANC:
selected.add(s)
if not selected:
raise ValueError(
f"No pancreatic samples resolved for {study_id!r} via CANCER_TYPE/ONCOTREE_CODE β€” "
"refusing to return the full pan-cancer cohort."
)
return sorted(selected)