PRS Frontend — requirements sketch
Input for a dedicated planning session. Written 2026-07-21, immediately after Gate B completed, to preserve decisions made while the data was in front of us. Not a design — a brief plus the constraints a design has to satisfy.
1. Why this needs its own session
The PRS pipeline rebuild has produced two substantial data structures that nothing can see:
mods_prs_run(Gate A) — dense-callset provenance: caller, callability policy, FASTA checksum, autosomal median depth, four-outcome reconciliation counts.mods_prs_score_manifest(Gate B) — 5,377 rows, one per published score: compatibility class, production-eligibility, reasons, structural feature counts, theoretical bounds, scoring-file checksum.
Grepping both frontend/src/components/PRSVectorBody.jsx and backend/mods_prs_api.py
for compat_class, production_eligible, score_manifest, prs_run, dense_callset,
validity returns zero matches in either file. There is no API surface and no UI.
Meanwhile Gates C–E will add computed scores with coverage, missingness and percentile semantics that are easy to display wrongly. Deciding the API contract now, before Gate C writes its first score, avoids retrofitting.
2. What exists today (all legacy)
| Surface | File | Covers |
|---|---|---|
| Patient Profile PRS slot | frontend/src/components/PRSVectorBody.jsx | genotype summary, per-disease score table, upload, compute, PDR export |
| Terminal command | frontend/src/components/Terminal.jsx (~1502) | prs status | compute | export | open |
| Downstream consumers | PDRInputInspector.jsx, PatientWeightsPanel.jsx | PDR input display |
| API | backend/mods_prs_api.py | /genotype/upload|summary|concordance, /prs/compute|scores|export/*|preview/pdr-input |
Released-only filtering does exist, but in mods_prs_service.py
(all_scores_for / latest_score_for, .where(validity == "released")), so the API
inherits it. mods_polygenic_score is currently empty, so the UI correctly shows nothing.
3. Hard constraints (things a design must not get wrong)
3.1 Retire the “Cov.” column — do not port it
PRSVectorBody.jsx renders a colour-coded coverage column computing
snps_used / snps_in_scoring_file, painting <50% red as “unreliable”.
That ratio is the REF-effect bug’s signature, not a property of the score. Across all 40 legacy outlier scores it clusters at 42–52% — the ~49% of loci the old scorer silently dropped. The UI presents a symptom of our own defect as a data-quality verdict about a published score. It must be removed, not carried forward under a new name.
3.2 Three coverage quantities, never conflated
Each is a distinct number and must be labelled distinctly:
- Structural overlap — % of scoring rows matched to target/reference alleles.
- Genotype callability — % of matched loci with a confident patient genotype.
- Effective score-information coverage —
Σ_retained 2p(1-p)w² / Σ_all 2p(1-p)w²(variance-weighted; the one that actually matters).
A single “coverage %” chip is exactly the mistake §3.1 describes.
3.3 Deferred means deferred
A score that is not production_eligible shows “not calculated —
3.4 Percentiles: rank + N + interval, no decorative precision
Show “1 of 930” or “above ~99.9th in this panel”. Never “98.1376th”. A parametric percentile is a secondary field, valid only when the gates pass — never for few-variant, haplotype, skewed or multimodal scores.
3.5 Nothing unreleased may reach a display or an export
validity='released' gates the score readers today. Any new endpoint must inherit that,
and the PDR export path must never ingest a non-released row.
4. What the manifest makes newly displayable
Real numbers, already in prod, that have no home yet:
- Fleet composition — A 1,997 / B 2,950 / C 426 / D 4; 3,863 production-eligible (71.8%); 3.45B variant rows examined.
- Sole-blocker analysis — the decision-driving view: multiallelic is the only thing
deferring 588 scores;
unplaceable_rowsappears in 399 but blocks alone in 18. Report “appears in” and “sole blocker” as separate columns (they rank oppositely). - Per-score detail — class, eligibility, reasons, feature counts (indel, palindromic,
multiallelic, chrX, inferred/missing other-allele, unplaceable), theoretical
[L, U], scoring-file checksum, trait,weight_type. - Gate A provenance — reconciliation counts proving zero residual implicit-zero, callability policy, reference-FASTA checksum, median depth.
weight_typedistribution — messy catalog-wide (3,250beta, 1,390NR,BETA/Betacase variants, 199log(OR), free-text tail). Worth surfacing, since it affects whether a score’s scale is interpretable.
5. Open questions for the session
- Where does this live? A new tab on the Research dashboard, an extension of the Patient Profile PRS slot, or a distinct “PRS pipeline” panel? The manifest is catalog-scoped (not patient-scoped), unlike everything currently in the PRS slot — that may argue for separating “which scores can we compute” from “this patient’s scores”.
- Does the manifest need a browsable 5,377-row table, or only aggregates plus lookup by PGS ID? Pagination/filtering cost vs. actual use.
- API shape — extend
mods_prs_api.pyor a newmods_prs_manifest_api.py? Endpoints for fleet summary, per-score detail, sole-blocker analysis, run provenance. - Terminal vs GUI split — the existing
prscommand is genuinely useful; how much of this is better asprs manifest/prs runssubcommands rather than pixels? - Does the promotion policy get a UI? Promoting
multiallelicis a signed-off human decision worth 588 scores; showing its impact before approval may be valuable, but the decision must not become a casual toggle. - Mobile — per CLAUDE.md all frontend work must be mobile-compatible; a 5,377-row table is not, so decide the mobile view deliberately.
6. Sequencing note
Gate C (now built as backend/mods_prs_scorer.py) produces the first correct scores and
emits the three coverage quantities as a coverage_v1 metadata payload, which
mods_prs_pipeline_service.coverage_block re-validates on read. The frontend needs that
API contract to be settled anyway, so either do this planning first and build against
an agreed contract, or accept one revision after Gate C. Deciding §3.2’s three
quantities up front is the part that matters most, because that is what the legacy UI got
wrong.
Reference: docs/prs_validation/compatibility_classification.md (the classifier +
fleet results), the rebuild plan at
an internal agent path, and
GH #237 (classifier cross-validation).