Q (sonnet-5.5): write me python code that can query the timeline files for msk-chord ▶ get_study_guide { "study_id": "msk_chord_2024" } ▶ read_guide { "uri": "cbioportal://treatment-guide" } ◀ result # MSK-CHORD (MSK, Nature 2024) **Study ID:** `msk_chord_2024` ## Overview Targeted sequencing via MSK-IMPACT panels. Clinical annotations include some derived from natural language processing (denoted NLP). **Exactly five cancer types** (`CANCER_TYPE`, patients): Non-Small Cell Lung Cancer 7,809, Colorectal Cancer 5,543, Breast Cancer 5,368, Prostate Cancer 3,211, Pancreatic Cancer 3,109. There is **no melanoma** or any other cancer type; say so up front if asked, instead of substituting another type. **No therapy-response variable.** There is no RECIST, objective response, or best-response attribute or event. For treatment-outcome questions (e.g. immunotherapy response), say this first; the only proxies are `OS_MONTHS`/`OS_STATUS`, or NLP radiology progression events (`Diagnosis` events with `SUBTYPE = 'Progression'`, key `PROGRESSION` = Y/N/Indeterminate), in patients with `Treatment` events of the relevant `SUBTYPE` (e.g. `Immuno`: 3,341 patients). Hand off the comparison to cBioPortal group comparison / survival. **Nearly one sample per patient: 24,950 patients / 25,040 samples.** Only 90 patients have more than one sample, and all 90 have samples from two different cancer types (second primaries); only 26 have both a `Primary` and a `Metastasis` sample. There is no meaningful same-patient (paired) primary-vs-metastasis cohort. For "same patient" / paired questions, say this up front, then offer the **unpaired** comparison of all `Primary` vs `Metastasis` samples (`SAMPLE_TYPE`), labelled as unpaired. ```sql SELECT countIf(n > 1) AS multi_sample_patients, -- 90 countIf(has_p AND has_m) AS primary_and_met -- 26 FROM (SELECT patient_unique_id, count() AS n, has(groupArray(attribute_value), 'Primary') AS has_p, has(groupArray(attribute_value), 'Metastasis') AS has_m FROM clinical_data_derived WHERE cancer_study_identifier = 'msk_chord_2024' AND attribute_name = 'SAMPLE_TYPE' GROUP BY patient_unique_id); ``` ## Gene Panels This study uses multiple MSK-IMPACT panel versions: - **IMPACT341**: Earlier version, 341 genes - **IMPACT410**: 410 genes - **IMPACT468**: 468 genes - **IMPACT505**: Latest version, 505 genes **Important:** Different samples may have different gene coverage. Always use gene-specific denominators when calculating mutation frequencies. ## Clinical Attributes - Semantic Guide ### Cancer Classification | Attribute | Description | Values | |-----------|-------------|--------| | `CANCER_TYPE` | Broad cancer category | e.g., "Non-Small Cell Lung Cancer", "Breast Cancer" | | `CANCER_TYPE_DETAILED` | Specific subtype | e.g., "Lung Adenocarcinoma", "Invasive Ductal Carcinoma" | | `ONCOTREE_CODE` | OncoTree classification code | Standardized cancer type codes | ### Sample Information | Attribute | Description | Values | |-----------|-------------|--------| | `SAMPLE_TYPE` | Sample origin | Primary, Metastasis, Local Recurrence, Unknown | | `SAMPLE_CLASS` | Sample … (5439 more chars) ◀ result # Treatment Data Query Guide ## Overview Treatment data in cBioPortal is stored in **clinical event tables**, separate from clinical attributes. This allows for timeline-based treatment records with start/stop dates. ## Start Here: Treatment Views For "most common treatments / agents / regimens in study X", use the parameterized views (documented in `cbioportal://clinical-data-guide`, Study-View Chart Counts): ```sql -- Patients per agent (the portal's Treatment chart), with type/subtype arrays SELECT * FROM treatment_counts_in_study(study='msk_chord_2024') ORDER BY patients DESC LIMIT 20; -- Same-day agent combinations (investigational, prior-medication and radiation events excluded) SELECT * FROM treatment_regimens_in_study(study='msk_chord_2024') ORDER BY patients DESC LIMIT 20; ``` Write raw event queries (below) only for subgroups, timelines or keys the views do not expose. ## Key Tables | Table | Description | |-------|-------------| | `clinical_event` | Event records with patient_id, event_type, start_date, stop_date | | `clinical_event_data` | Key-value pairs linked to each clinical_event_id | ## Schema ``` clinical_event ├── clinical_event_id (PK) ├── patient_id (FK → patient.internal_id) ├── event_type (Treatment, TREATMENT, Diagnosis, SURGERY, etc.) ├── start_date (days from diagnosis) └── stop_date (days from diagnosis) clinical_event_data ├── clinical_event_id (FK) ├── key (AGENT, SUBTYPE, etc.) └── value ``` ## Event Types Not all studies have all event types. Common ones include: | Event Type | Description | |------------|-------------| | `Treatment` / `TREATMENT` | Drug/therapy administration | | `SURGERY` | Surgical procedures | | `Diagnosis` | Diagnosis events | | `LAB_TEST` | Laboratory results | | `Sequencing` | Sequencing events | | `Sample acquisition` | Sample collection | | `PATHOLOGY` / `Pathology` | Pathology reports | ## Basic *** Queries ### List Available Event Types in a Study ```sql SELECT DISTINCT ce.event_type, COUNT(*) as event_count FROM clinical_event ce JOIN patient p ON ce.patient_id = p.internal_id JOIN cancer_study cs ON p.cancer_study_id = cs.cancer_study_id WHERE cs.cancer_study_identifier = 'your_study_id' GROUP BY ce.event_type ORDER BY event_count DESC; ``` ### List Treatment Data Keys in a Study ```sql SELECT DISTINCT ced.key, COUNT(*) as cnt FROM clinical_event ce JOIN clinical_event_data ced ON ce.clinical_event_id = ced.clinical_event_id JOIN patient p ON ce.patient_id = p.internal_id JOIN cancer_study cs ON p.cancer_study_id = cs.cancer_study_id WHERE cs.cancer_study_identifier = 'your_study_id' AND ce.event_type IN ('Treatment', 'TREATMENT') GROUP BY ced.key ORDER BY cnt DESC; ``` ### Get Most Common Treatment Agents ```sql SELECT ced.value as agent, COUNT(DISTINCT ce.patient_id) as patient_count FROM clinical_event ce JOIN clinical_event_data ced ON ce.clinical_event_id = ced.clinical_event_id JOIN patient p ON ce.patient_id = p.internal_id JOIN cancer_stud … (6220 more chars) ═ answer (success) This script pulls the MSK-CHORD timeline events (study ID `msk_chord_2024`) through the public cBioPortal REST API and flattens them into a pandas DataFrame. I haven't run it, and I wrote the endpoint and field names from memory of the cBioPortal API, so check them against the Swagger docs at https://www.cbioportal.org/api/swagger-ui/index.html. ```python import requests import pandas as pd BASE = "https://www.cbioportal.org/api" STUDY = "msk_chord_2024" HEADERS = {"Accept": "application/json"} # For private/authenticated portals, add: HEADERS["Authorization"] = "Bearer " def fetch_timeline(study_id=STUDY, page_size=50000, max_pages=None): """Fetch all clinical (timeline) events for a study, paged.""" rows, page = [], 0 while True: r = requests.get( f"{BASE}/studies/{study_id}/clinical-events", params={"projection": "DETAILED", "pageSize": page_size, "pageNumber": page}, headers=HEADERS, timeout=300, ) r.raise_for_status() batch = r.json() if not batch: break rows.extend(batch) page += 1 if len(batch) < page_size or (max_pages and page >= max_pages): break records = [] for e in rows: rec = { "patientId": e.get("patientId"), "eventType": e.get("eventType"), "startDate": e.get("startNumberOfDaysSinceDiagnosis"), "stopDate": e.get("endNumberOfDaysSinceDiagnosis"), } # Each event carries key/value attributes (AGENT, SUBTYPE, ...) for a in e.get("attributes", []): rec[a["key"]] = a["value"] records.append(rec) return pd.DataFrame(records) def fetch_patient_timeline(patient_id, study_id=STUDY): r = requests.get( f"{BASE}/studies/{study_id}/patients/{patient_id}/clinical-events", params={"projection": "DETAILED"}, headers=HEADERS, timeout=60, ) r.raise_for_status() return r.json() if __name__ == "__main__": df = fetch_timeline() print(df.shape) print(df["eventType"].value_counts()) # Example 1: chemo agents by number of patients tx = df[df["eventType"].str.lower() == "treatment"] chemo = tx[tx["SUBTYPE"] == "Chemo"] print(chemo.groupby("AGENT")["patientId"].nunique().sort_values(ascending=False).head(20)) # Example 2: NLP radiology progression events prog = df[(df["eventType"] == "Diagnosis") & (df.get("SUBTYPE") == "Progression")] print(prog["PROGRESSION"].value_counts()) # Example 3: one patient's timeline, sorted pid = df["patientId"].iloc[0] print(df[df["patientId"] == pid].sort_values("startDate")) ``` What to know about the MSK-CHORD timeline data: - **Event types:** Treatment, Diagnosis (which includes the NLP progression calls), Sequencing, and others. Run `df["eventType"].value_counts()` to see what's there. Casing can differ (`Treatment` vs `TREATMENT`), so the example lowercases it. - **Treatment keys:** `AGENT`, `SUBTYPE` (Chemo, Immuno, Targeted, Hormone, Radiation Therapy and others), `RX_INVESTIGATIVE`, `PRIOR_MED_TO_MSK` and `INFERRED_TX_PROB`. Some of this data is NLP-extracted, and `INFERRED_TX_PROB` gives the confidence. - **Dates:** `startDate` and `stopDate` are days from diagnosis. - **Percentages:** report treatment results as patient counts, not percentages. The data can't distinguish "not collected" from "not received". - **Speed:** the study has about 25k patients, so the full pull can be large. Lower `page_size`, set `max_pages`, or use `fetch_patient_timeline` for a few patients while testing. - **Files instead of the API:** if you want the raw `data_timeline_*.txt` files, download the study from [the DataSets page](https://www.cbioportal.org/datasets). Then load them with `pd.read_csv(path, sep="\t")`. They have columns `PATIENT_ID`, `START_DATE`, `STOP_DATE`, `EVENT_TYPE` and the attribute columns. I can also adapt this to load the downloaded timeline files, or to filter by cancer type.