Phenopackets¶
phenopackets_etl.py reads dashboard_data.json and writes one GA4GH Phenopacket v2 JSON document per participant, using the Human Phenotype Ontology (HPO) for phenotypes and the Mondo Disease Ontology for diseases. Phenopackets are the exchange format used by rare disease networks such as Matchmaker Exchange, GA4GH Beacon networks and the Monarch Initiative, and they sit alongside the OMOP tables as a second standard output.
Every field comes from a coded record in the bundle. Note text is used only when you ask for it, and genetic results come only from a curated file you supply.
Demo Phenopacket (JSON) Module source (phenopackets_etl.py)
De-identified by default · Fields · Seed tables · Medication codes · Note measurements · Genetic results · Running it · Output · Checks
De-identified by default¶
| De-identified (default) | Identified (--also-write-identified) |
|
|---|---|---|
| Subject ID | PP-NNNN, assigned in order of participant ID |
Registry participant ID |
| Date of birth | Omitted | Included |
| Time elements | Age as an ISO 8601 duration, capped at 90 years | Calendar dates |
| Folder | phenopackets_output/deidentified/ |
phenopackets_output/identified/, with crosswalk.csv |
Both folders are restricted: even without names or dates, each file describes one person with a rare disease. run_all.py writes them under restricted/. Review a file against the receiving network's rules before it is submitted anywhere.
How the fields are filled¶
| Phenopacket field | Source in the bundle | How it is mapped |
|---|---|---|
subject |
patients[]: sex, vital status, and date of birth in the identified folder only |
Direct |
diseases[] |
problems[] (ICD-10-CM, SNOMED CT), every coding |
Seed table or Athena "Maps to" to Mondo; a motor neuron disease code (ICD-10-CM G12.2x) with no Mondo mapping is written as its ICD-10-CM term |
phenotypicFeatures[] |
Other problems[] codes |
Seed table or Athena "Maps to" to HPO |
measurements[] |
labs_vitals[] (LOINC) |
Passed through with the value and unit |
medicalActions[].treatment |
medications[] (RxNorm), immunizations[] (CVX) |
Passed through |
medicalActions[].procedure |
procedures[] (SNOMED CT, CPT, HCPCS) |
Passed through; a procedure whose coding system has no declared resource is left out |
interpretations[] |
A curated genetics CSV you supply | See Genetic results |
metaData.resources[] |
The prefixes used in the file | One entry per ontology, with the release when Athena records it |
A diagnosis such as ALS is written once, as a disease, and not repeated as a phenotypic feature. Only fields the Phenopacket v2 schema defines are written, and no placeholder codes or placeholder interpretations are added. Immunizations are written without a route unless the record states one.
Seed tables¶
The module ships hand-curated mappings for the motor neuron disease spectrum, epilepsy, and autoimmune neurologic and rheumatic disease, in the dictionaries at the top of phenopackets_etl.py. Matching is exact for SNOMED CT and by code prefix for ICD-10-CM, with the longest matching prefix winning. For broader coverage, pass an Athena vocabulary folder with HPO and Mondo selected; its "Maps to" relationships add to the seed tables.
unmapped_codes.csv lists every code that matched neither HPO nor Mondo, with the number of records, which makes it the worklist for extending the seed tables. Mondo and HPO IDs change between releases, so check the seed IDs against the current releases before results are used. The Mondo IDs in the seed tables were checked in November 2026.
Medication codes¶
One FHIR MedicationRequest often holds several RxNorm codes for the same prescription: an ingredient (IN), a clinical drug (SCD) and a brand name (BN). When an Athena folder is supplied, the code is chosen by RxNorm term type in this order:
So a request with both RxNorm:6809 (ingredient) and RxNorm:860975 (clinical drug) is written with the clinical drug. Without Athena, the module uses the coding chosen during parsing, which prefers a display name with a strength and dose form.
Note measurements¶
With --include-note-measurements, ALSFRS-R totals and FVC percent predicted values found by note extraction are added as measurements:
| Note pattern | Measurement code | Unit |
|---|---|---|
alsfrs_r_total |
LOINC 82953-1 (ALSFRS-R total score) | none (UCUM unity) |
fvc_percent_predicted |
LOINC 19872-1 (FVC measured/predicted) | % |
Each one has a description saying it is a candidate from note text that needs review, and summary.csv counts them separately. LOINC has no codes for the ALSFRS-R domain subscores, and ECAS scores are not mapped, so neither is written. The option is off by default; turn it on only after the patterns have been validated against your documents.
Genetic results¶
Genetic results usually live outside the EHR export: in a genetic test report, a research data set or a genetic counseling record. Supply them as a curated CSV, one row per variant, and the module writes them as interpretations[]. Results are never created from note text.
participant_id,gene_symbol,hgnc_id,hgvs,origin,pathogenicity,zygosity,source
R-000123,SOD1,HGNC:11179,NM_000454.4:c.272A>C,germline,Likely pathogenic,heterozygous,Clinical ALS gene panel report
R-000456,C9orf72,HGNC:28337,,germline,Pathogenic,heterozygous,Repeat-primed PCR report
| Column | Contents |
|---|---|
participant_id |
Must match patient_id in the bundle (patient_id is also accepted as the column name) |
gene_symbol |
HGNC symbol. The HGNC ID is filled in for C9orf72, SOD1, FUS, TARDBP, TBK1, VCP, UBQLN2, PFN1, MATR3, CHCHD10, ANG, OPTN and ATXN2; give hgnc_id for other genes |
hgnc_id |
With or without the HGNC: prefix |
hgvs |
HGVS expression, written as given. Without it the interpretation names the gene only |
origin |
germline or somatic |
pathogenicity |
Pathogenic, Likely pathogenic, Uncertain significance (or VUS), Likely benign, Benign |
actionability |
actionable or not actionable (optional) |
zygosity |
heterozygous, homozygous or hemizygous, mapped to Genotype Ontology (GENO) terms |
source |
Where the result came from; written to the variant description |
Column names are not case-sensitive. A pathogenic or likely pathogenic result is marked CONTRIBUTORY; others are UNKNOWN_STATUS. A participant with no row in the file has no interpretations[] block.
Running it¶
run_all.py writes de-identified Phenopackets to restricted/phenopackets_output/. To run the module on its own:
python pipeline/phenopackets_etl.py --bundle dashboard_data.json --out restricted/
python pipeline/phenopackets_etl.py --bundle dashboard_data.json --out restricted/ \
--vocab /path/to/athena --external-genetics genetics.csv
| Option | Default | Effect |
|---|---|---|
--bundle |
./dashboard_data.json |
Bundle from run_pipeline.py |
--out |
./ |
Folder in which phenopackets_output/ is written |
--vocab |
none | Athena vocabulary folder for broader HPO and Mondo mapping and RxNorm term types |
--external-genetics |
none | Curated genetic results CSV |
--note-extractions |
./note_extractions.csv |
Used only with the next option |
--include-note-measurements |
off | Adds ALSFRS-R totals and FVC percent predicted from notes, marked as candidates |
--also-write-identified |
off | Adds the identified folder and crosswalk |
Output¶
Each folder under phenopackets_output/ holds:
| File | Contents |
|---|---|
<subject_id>.json |
One Phenopacket per participant |
cohort.json |
All participants as a Phenopacket Cohort |
summary.csv |
One row per subject: counts of diseases, phenotypic features, measurements, note-derived measurements, medical actions and interpretations |
unmapped_codes.csv |
Each vocabulary and code with no HPO or Mondo mapping, the mapping it lacks, and the number of records |
crosswalk.csv |
Identified folder only: registry participant ID and PP-NNNN |
Checks¶
The test suite checks that Phenopackets use only fields the GA4GH Phenopacket v2 schema defines, and that de-identified files hold no names, dates of birth or calendar dates (see Quality control). Before results are used, a clinician can compare a participant's Phenopacket with the same participant in the record viewer to catch mappings that are valid but clinically wrong.