Phase 13 — Temporal Patient State Modeling — Planning Document
Status: PLANNING (not yet started) Created: 2026-05-07 Owner: whafa Master plan reference: PDR_PIPELINE_PLAN.md § Phase 13 Companion writeup template: pdr_phase0_gpkg_writeup.md Adjacent planning doc: pdr_phase8.5_variant_kg_planning.md — variant-aware KG; complementary not overlapping.
Seed for a dedicated planning session on representing temporal change in Maureen’s biomarker, genomic-test, and clinical data — both as immediate snapshot-policy fixes and as deeper temporal-graph architecture work.
1. Problem statement
Maureen’s health record changes daily. New labs come in (CEA, LDH, albumin, lymphocytes). Imaging gets re-read. RGCC and Caris panels can be repeated. Symptoms progress and regress. Treatments start and stop. Each of these is a trajectory of a patient parameter, not a static fact — and the trajectory is often clinically more informative than any single point on it. CEA doubling time predicts CRC recurrence months before imaging shows it. A falling albumin trajectory during chemo signals failure-to-thrive even when the absolute value is in range.
Our current MODS architecture treats patient state as a snapshot. Two distinct concerns hide inside that:
(1) The snapshot policy bug. When the same parameter has multiple values across time (e.g., re-tested gene expression, repeated blood panels), our consumer code currently picks the earliest available value rather than the most recent. This is a near-term fix that can be done independently. Findings from a code audit (2026-05-07):
mods_kgia_profile_service.py::_build_biomarker_series(line 389) is correct — usesORDER BY v.ts DESC LIMIT 1via LATERAL join to take the latest value, with prior-value capture for trend computation.mods_kgia_profile_service.py::_build_genomic_alterations(line 236) is the bug. Orders rows bygeneonly — no temporal sort. When Maureen has multiple genomic test reports (e.g., 2024 Caris + 2025 Caris re-test) covering the same gene, all rows are returned in alphabetical-by-gene order. Whatever consumer dedupes by gene picks the first row encountered — typically the earliest report by insertion order.mods_kgia_profile_service.py::_build_gene_expression(line 265) has the same bug — ordered byregulationandexpression_value, never byreport_date.mods_snapshot_service.py::_build_biomarkers_section(line 189) is correct in intent — returns the full trajectory, not a single value. Whether the LLM downstream picks the right value is a separate question.
Recommended fix for (1): add ModsGenomicTestReport.reported_date.desc().nullslast() as the primary sort key in _build_genomic_alterations and _build_gene_expression, then dedupe by gene at the consumer level taking the first row per gene (which will now be the most recent). Two-line code change in each method.
(2026-10-03: shipped. Both methods now sort on report_date DESC NULLS LAST (the column on the alteration and expression rows, not the report table). The date gaps still open are in §8.2.)
(2) Representing trajectory itself. This is the deeper concern. Currently we have no way to feed change into the link-prediction model — only state. The CEA trajectory over the last six months is invisible to the GNN unless we encode it as features somewhere. This requires architectural decisions, not just a code fix.
What the papers do (and don’t)
Audited 2026-05-07 for temporal handling in PDR1 and PDR2:
- PDR1 (Oğuztüzün 2025) uses
patient_merge.pyto ingest UK Biobank patient data. Features used: PRS for AD, PRS for comorbidities, and protein biomarker expression values. Each is a single scalar per patient. No temporal modeling. - PDR2 (Oğuztüzün 2026) uses ULTRA + KGIA loss with
--lambda1for genetic risk and--lambda2for biomarker influence. Same shape: per-patient scalars at fine-tune time. No temporal modeling.
Conclusion: neither paper provides a precedent for temporal patient modeling in this drug-repurposing-by-KG-completion lineage. If we want trajectory-aware ranking, we are operating beyond the published architecture. This is a research direction, not a reproduction task. Two implications:
- No paper-target benchmark for closure. Phase 0 closes against KG-Predict’s published metrics; Phase 13 doesn’t have an equivalent. We need to design our own validation criteria (see § 5 closure criteria).
- Literature search is part of the work. Outside the PDR lineage, temporal KG completion is a real research subfield (TKG models like TNTComplEx, RE-NET; clinical-AI temporal patient models; multimodal architectures combining time-series with KGs). We need to map this landscape before architectural commitment.
Why this matters specifically for Maureen
CRC has one of the strongest “trajectory-matters-more-than-state” stories in oncology:
- CEA can be normal at diagnosis even with metastatic disease. Doubling time predicts recurrence with a months-long lead vs imaging.
- Tumor markers and inflammatory markers (LDH, albumin, neutrophil-lymphocyte ratio) have prognostic value as trajectories that single values lack.
- Caris re-tests (if ordered) would let us see actual biological evolution of the tumor — which genes turn on/off under treatment pressure. That signal is invisible if we only carry the most recent test.
- RGCC re-tests show whether her tumor’s drug sensitivity profile is shifting. Critical for adaptive therapy decisions.
For Phase 9 (current personalization plan) operating on a single snapshot, we lose all of this.
2. The five-tier framework
Approaches to representing temporal data in graphs, ordered from “easiest to ship” to “research project.”
Tier 1 — Trajectory features as static node attributes
Compute summary statistics from the time series and encode them as attributes on a single (current) node. The graph stays static. Per biomarker: most recent value, slope (regression of value vs time, last 3-6 months), variance / coefficient of variation, ratio to baseline, days since last out-of-range crossing, count of threshold crossings.
Pros: No model architecture changes. Fits any static GNN (PDR1, PDR2, anything). Keeps the KG human-auditable. Schema work is straightforward. Cons: Loses interaction effects between biomarker trajectories. Requires manual feature engineering per biomarker. Limited to features humans think to compute.
Tier 2 — Multi-snapshot training
Build the KG at multiple time points (Maureen-as-of-2025-01-01, Maureen-as-of-2025-04-01, etc.) and train the model on the sequence.
Pros: Captures evolution. Lets the model learn to recognize concerning trajectories rather than relying on hand-crafted features. Cons: Storage and compute cost balloons. The model still treats each snapshot as static unless we add temporal edges (which moves to Tier 3-4). Snapshot intervals are arbitrary given uneven sampling rates (genomic tests rare, labs frequent).
Tier 3 — Time-stamped edges (Temporal Knowledge Graph)
Every edge gets valid_from / valid_to timestamps. Add edges like (Maureen, had_value, CEA=8.2, 2025-03-15) and (Maureen, had_value, CEA=12.1, 2025-04-15). Query the graph “as of date X” to recover historical state.
Pros: Preserves full history. Standardized — TKG completion is a research subfield with established models (TNTComplEx, TeMP, RE-NET, ULTRA-Time variants). Cons: Most TKG models are tested on event data (news, social), not clinical data — generalization is uncertain. Graph size balloons (every lab result becomes an edge). Most TKG models predict “what edge will exist at time T+1” rather than “given this trajectory, what drug ranks highest” — there’s a translation layer needed.
Tier 4 — Patient-as-node with trajectory-encoded embedding (recommended target)
The patient is a node (per Phase 12). Her embedding is computed by a small temporal encoder (1-D CNN or Transformer over her time series), and that embedding feeds into the graph as her node features. The graph model itself stays static; the temporal complexity is absorbed into a separate sub-model that produces her node representation.
Time series of labs / imaging / symptoms / genomic re-tests
↓
Temporal encoder (Transformer or LSTM)
↓
Patient embedding vector (e.g., 256-d)
↓
GNN (CompGCN / ULTRA) — Maureen is a node with this embedding as features
↓
Drug ranking
Pros: Cleanest separation of concerns. The temporal encoder can be small and trained on whatever per-patient data we have, while the GNN keeps its static-KG advantages. Captures interaction effects natively (Transformer self-attention over multivariate biomarker series). Ships in a reasonable time frame. Aligns naturally with Phase 12’s patient-as-node architecture. Cons: Embedding becomes a learned summary — less human-auditable than Tier 1. Training requires patient outcome labels, of which we have one patient. Multi-patient generalization is unclear until we have a cohort. Encoder overfit risk on a single patient is real.
Tier 5 — Full continuous-time dynamic graph
Each edge appears and disappears as events occur. The model maintains time-varying embeddings via methods like TGN (Temporal Graph Networks) or DyRep. Most sophisticated, most expensive, most research-grade.
Pros: State of the art in temporal graph modeling. Cons: Production-ready clinical implementations are rare. Probably overkill for our use case. Defer indefinitely.
3. Recommendation
A phased approach within Phase 13:
13.1 — Snapshot policy fix (immediate, in service of Phase 9). Fix the genomic-alteration and gene-expression dedup bugs documented in §1. Take most recent value per gene as the default, with a configurable “freshness window” (default: prefer values within 12 months over older ones). Should ship before Phase 9 starts.
13.2 — Tier 1 trajectory features (Phase 9 / 12 alignment). Add trajectory features as node attributes on the patient (Phase 12) or as patient-feature scalars (Phase 9 PDR1 path). Per biomarker: latest value, slope over 90 days, ratio to baseline, days since out-of-range crossing. CEA as the first test case. Cheap, well-scoped, captures most of the clinical signal.
13.3 — Tier 4 trajectory-encoded patient embedding (research project). Build the temporal encoder for the patient node. Genuinely a research project — needs validation against alternative architectures, needs an outcome labeling strategy, may benefit from a small cohort (even 2-3 patients) to validate the temporal encoder rather than overfitting Maureen.
Skip Tiers 2, 3, 5 unless we hit a specific blocker. Tier 1 + Tier 4 cover the practical clinical-AI space well. The other tiers are research artifacts that haven’t broken into clinical production.
4. Why CEA is the concrete first test case
For CRC specifically, CEA trajectory is THE textbook example of why temporal matters:
- CEA can be normal at diagnosis even with metastatic disease.
- Doubling time predicts recurrence months before imaging shows it.
- Absolute value matters less than trend.
- Threshold crossings (5 ng/mL conventional cutoff, 25%-rise-over-baseline) trigger clinical action.
If we get any temporal modeling in, CEA should be the test case. Maureen’s record likely has CEA values across her treatment history — there’s enough signal to validate that trajectory features improve drug ranking vs current-value-alone.
(2026-10-03: stale. CEA has no numeric value after 2025-09-16. Kaiser appears to have moved her surveillance marker to CA 19-9, so a CEA series flatlines to null. CA 19-9 should be the first series, then ctDNA MTM/mL once GH #131 parses the Signatera reports. See the mission spec §2.3 and §2.6.)
Concrete validation hypothesis: patient features that include CEA slope over 90 days produce different drug rankings than features using only the most recent CEA value, AND those rankings agree better with clinical guidelines for CRC progression (escalation to next-line therapy when CEA is rising).
5. Closure criteria
Phase 13 is unusual because no published paper provides a comparator. Closure criteria are internal:
13.1 (snapshot fix) is closed when:
- For each parameter with multiple values per patient, the most recent value is selected by default.
- A regression test against Maureen’s known data confirms specific known-correct values are now selected (e.g., 2025 Caris values not 2024).
- No degradation in any existing Phase 7.5 RGCC validation metric.
13.2 (Tier 1 trajectory features) is closed when:
- Trajectory features (slope, baseline ratio, crossings) are computed and stored for at least 5 prioritized biomarkers per patient.
- CEA-trajectory feature improves Phase 7.5 RGCC validation by at least one of: ≥ 0.03 AUROC, ≥ 0.05 Spearman ρ.
- Feature definitions are documented and reproducible.
(2026-10-03: the Phase 7.5 RGCC figures these criteria compare against are superseded. They pooled the two draws (GH #324), and the PDR2 v2 numbers were leak-driven (GH #214). A criterion now needs a clean, per-draw baseline. §8.4 proposes a within-patient alternative.)
13.3 (Tier 4 temporal encoder) is closed when:
- A temporal encoder (LSTM or Transformer) is trained on Maureen’s biomarker history and produces a fixed-dim patient embedding.
- The embedding is consumable by the Phase 12 patient-as-node architecture.
- Compared to Tier 1 baseline: at least one validation metric improves by ≥ 0.02 AUROC or ≥ 0.03 ρ. Meaningful improvement is hard to define on a single patient — see open question 6.
- Cohort generalization: when a second patient is added, the encoder produces meaningfully different embeddings without retraining.
6. Open questions
-
Biomarker priority list. Which parameters get trajectory features first? CEA is obvious. After that: LDH, albumin, neutrophil-lymphocyte ratio, platelet count, CA 19-9. What’s the cutoff?
-
Trajectory feature set per biomarker. Slope over what window? Baseline defined how (first value? rolling 6-month median?)? Threshold crossings against clinical reference range or per-patient personalized range?
-
Uneven sampling rates. Genomic tests are sparse (months/years apart), labs are dense (days/weeks). Imaging is intermediate. Some biomarkers are sampled regularly during chemo cycles, irregularly otherwise. How does the temporal encoder handle this? Options: time-aware Transformer, irregular-interval RNN (e.g., GRU-D), interpolation to regular grid.
-
Missing data handling. Some biomarkers are tested rarely or only when specific treatments are considered. How do we represent “we just don’t have this for Maureen”? Mask the feature? Imputation? Treat absence as informative?
-
Outcome labeling strategy. What’s the supervision signal for training a temporal encoder? Options: treatment response (RECIST), tumor marker change as proxy, RGCC re-test changes, survival, hand-labeled clinical milestones. Each has issues.
-
Validating temporal modeling with one patient. Single-patient temporal encoders overfit easily. How do we validate? Options: hold out the most recent N months (predict her current state from past); leave-one-biomarker-out (train without CEA, predict CEA trajectory); sensitivity analysis (how much does ranking shift for plausible feature perturbations).
-
Imaging-derived features. RECIST measurements, tumor size, location. Are these biomarkers in the same model, or a separate modality? If separate, how do they integrate with the temporal encoder?
-
Genomic test repeat handling. When Caris is re-run, do we add a new variant edge with timestamp, or overwrite the old one? This intersects with Phase 8.5 (variant nodes). If a known variant is no longer detected on re-test, what does that mean — biological loss, or sampling difference?
-
Snapshot freeze for reporting. When a clinical recommendation is generated, we need to commit to a “decision time” snapshot for reproducibility. How do we represent that snapshot in the data model? Materialized snapshot table? Reconstruct on demand from time-stamped raw data?
-
Cohort dependency. Tier 4 generalization requires multiple patients. Maple is currently single-patient (Maureen). When does multi-patient become realistic, and does that change Phase 13 sequencing?
7. Suggested next steps for the planning session
- Ship 13.1 (snapshot fix) immediately. It’s a two-line change in
mods_kgia_profile_service.pyplus a test. Don’t wait for the full Phase 13 plan. - Inventory available biomarker history for Maureen. Concrete count per parameter, with date range and sampling cadence. Drives prioritization in open question 1.
- Literature spike on temporal patient modeling in clinical AI. Survey of Tier 1-4 approaches in clinical applications. Particular attention to: time-aware Transformers, irregular-interval RNNs, multimodal architectures combining time-series with KGs. Output: a one-page recommendation memo for the Tier 1 vs Tier 4 sequencing.
- Design CEA as the validation anchor. Pre-register what improvement we expect from CEA trajectory features over CEA-current-value features in Phase 7.5 RGCC validation.
- Schema sketch for Tier 1. Where do trajectory features live? On
ModsBiomarkerSeriesas computed attributes? In a newmods_biomarker_trajectory_featurestable? Ties into Phase 12 schema. - Coordinate with Phase 8.5 and Phase 12. Variant-as-graph-node and patient-as-graph-node are both upstream design decisions that interact with Phase 13. Ideally the schema for all three settles together rather than retrofitting later.
8. Temporal Graph-RAG ideas, evaluated one by one (2026-10-03)
Source: Adding Temporal Reasoning to Graph RAG: Tracking Fact Freshness and Staleness (MachineLearningMastery, read 2026-10-03). The article’s whole mechanism is below, so each verdict can be checked against it:
- Dated facts. Subject → predicate → list of (object, date). A triple becomes a quadruple.
- Recency weight.
weight = 0.5 ^ (age_in_days / half_life), default half-life 365 days. A fact dated after the query date gets 1.0. - As-of query. Only facts with
fact_date ≤ query_datecome back. - Conflict resolution. All candidates for one (subject, predicate) are ranked by weight, freshest first.
- Per-domain half-life. 365 days is too slow for volatile facts; the article’s example drops to 7.
- Prompt assembly. The retriever passes the top-weighted fact, or a short ranked list with dates and weights, into the LLM’s context.
In short:
- The as-of query is the article’s most valuable idea for MODS, though not for retrieval. It is the missing piece for decision-time snapshots (§6 Q9) and for validating against Maureen’s own timeline (§6 Q6).
- Dated facts and dated prompts are worth adopting. The code mostly has them already; checking it found two gaps (§8.2).
- The decay weight needs reshaping first. Most of her data goes stale through events, not elapsed days.
- Recency-ranked conflict resolution should be rejected as a general rule. It is tie-breaking by date.
8.1 Verdicts
| # | Idea | Verdict | Where it lands |
|---|---|---|---|
| 1 | Dated facts | Adopt. Mostly in place | §8.2; new 13.1b |
| 2 | Recency weight | Adapt. A staleness class per predicate, of which exponential decay is one | §8.3; 13.2 features |
| 3 | As-of query | Adopt. Highest value | §8.4; new 13.1c |
| 4 | Freshest wins a conflict | Reject, except for state that was measured again | §8.5 |
| 5 | Per-domain half-life | Adopt, generalized into the staleness table | §8.3 |
| 6 | Dated facts in the prompt | Adopt. Never pass a lone winner | §8.2, §8.6 |
| — | Future-dated facts weigh 1.0 | Reject. A future date is a parse error or a scheduled event, never an observation | 13.1b ingest check |
The article’s storage layer (nested in-memory dicts) has nothing to offer a Postgres schema with JSONB metadata, and is not considered further.
8.2 Dated facts: what the code already does, and two gaps
Most patient edges built by mods_patient_kg_service.py already carry their date in metadata:
- expression and resistance factors:
report_date; - medications and treatment episodes:
start_date,end_date; - conditions:
onset_date,abatement_date.
The LLM snapshot (mods_snapshot_service.py) dates biomarker values (ts) and RGCC tests (collected_date, reported_date). And 13.1 has shipped (§1).
Reading the code with the article’s question in mind, does every fact know when it was true?, found two gaps:
-
Her patient node shows the oldest value of every repeated measurement (GH #701). The edge builders query with no
ORDER BY, and the insert isON CONFLICT DO NOTHINGon a relation key with no time component. In practice the oldest report was loaded first and comes back first, so it is the one kept. That is the opposite of 13.1’s latest-first rule for the KGIA profile, so the graph the foundation trains on and the profile the criterion reads describe her at different times. Counted on prod, read-only, on 2026-10-03, against a node last materialized 2026-05-18:-
RGCC. Her two draws (reported 2025-02-15 and 2025-09-19, with no collection date recorded) hold 165 results each and became 135 undated edges. All 62 edges whose two draws scored differently carry the February score. That includes all eight graph-resolved drugs from the 12 that moved at least 25 points:
- paclitaxel shows 75 (22 in September), docetaxel 70 (20) and cabazitaxel 70 (14);
- doxorubicin shows 24 (79) and etoposide 22 (71);
- daunorubicin, idarubicin and epirubicin show 20 (67).
The predicate hides the inversion too, because RGCC’s moderate and low grades both map to
partially_sensitive_to_drug. Only 15 agents changed predicate between draws, all natural substances without numeric scores. Of those, only naltrexone resolves to a graph entity, and it now holds bothpartially_sensitive_to_drugandresistant_to_drug. The other four movers do not resolve at all (Abraxane, liposomal doxorubicin, amrubicin, and mitoxantrone, which RGCC spells “Mitoxandrone”), nor do 65 of the 164 agents overall. -
Alterations. Five variants appear on more than one report with differing VAFs. Five of their edges carry the earliest report’s VAF and one matches neither. No alteration edge carries a date.
-
Expression. Expression edges are dated. Of 13 genes with a regulation call on more than one report, 12 carry the earliest report’s date. And because every report contributes edges, three genes called regulated in January 2024 but normal in September 2025 still carry the regulation edge.
So the model’s input changes only at the predicate level: naltrexone’s double edge and the three stale regulation calls. What a person or the LLM reads off her node, the scores, VAFs and dates, is the February 2025 and January 2024 picture. Validation is unaffected:
compute_metrics_crc.pyreads per draw from the functional-test tables. The fix is a representation choice, so the issue waits on a decision. The same question is §6 Q8 for repeat Caris panels, and both should get one answer.(2026-10-04: decided and implemented for RGCC, alterations, expression and resistance factors (GH #701). The rules:
- Each measurement becomes one edge. Its predicate and headline values come from the latest report, and every report is listed in
metadata.observations. - A property the reports state differently, such as a variant’s origin, is left unstated and named in
disagreeing_fields. Date does not decide it. - When the latest reports disagree on the predicate, there is no edge, and the measurement is reported as a conflict.
- A latest expression call of
normalretires an earlier regulation call. Assays never decide for each other. - Two measurements that land on one (target, predicate) are merged, with the rest kept under
also_from.
That answers Q8 for repeat panels. What a variant missing from a later report means is still open: an alteration has no “not detected” call, so its edge stays.)
-
-
The LLM snapshot’s genomics section is undated (GH #702).
_build_genomics_sectionsorts by gene and emits the variant, VAF and source report, but notreport_date. A model reading one variant at two VAFs cannot tell which came first. It is invited into the trap the mission spec’s §7.1 names: VAF across specimens tracks purity, so it is not a trajectory.
8.3 Staleness classes, not one half-life
The article’s single decay curve fits a lab value and little else in Maureen’s record. What makes a tumor fact stale is usually an event: a recurrence or a new treatment line. The RGCC draws show it. Twelve drugs moved at least 25 points between February and September 2025, as two coherent classes: taxanes down, topoisomerase-II agents up. That interval straddles the August 2025 recurrence and the BREAKWATER start. Seven months of age is a proxy; the regime change is the cause.
So “half-life per domain” generalizes to a staleness class per predicate:
| Class | A fact is current while… | Examples in her record | Article’s decay? |
|---|---|---|---|
| Never stale | always | germline genotypes, polygenic scores | No. Decay here would be an error |
| Interval | start ≤ as_of ≤ end | medications, treatment episodes, conditions with an abatement date | No. The interval answers “current?” exactly; decay blurs it |
| Epoch | no treatment-line change or progression event since it was observed | somatic alterations, tumor expression, RGCC sensitivity, imaging reads | No. After the next event it is prior-epoch: flagged, not fractionally weighted |
| Decaying | weight 0.5 ^ (age / h), with h set per marker | CA 19-9, ctDNA MTM/mL, albumin, LDH, blood counts | Yes. This is the article’s case |
Epoch boundaries come from treatment-episode start dates plus dated progression events. Two cautions:
- Assigning a predicate to a class encodes meaning. Under CLAUDE.md’s rule on vocabulary mappings, that is a judgement call. The table above is a proposal for sign-off, not a decision.
- Weights are computed on read and never stored. They depend on the as-of date, which is the same reason Gate C stores counts rather than ratios.
Where a weight could enter the model. Three slots, in the order to try them:
- Use staleness to decide which edges exist, not how heavy they are. Materialize current-epoch somatic edges, and leave prior-epoch facts out or under a distinct predicate. No model change, fully auditable, fails closed. Try this first.
- Phase 12’s
strength(f, c), constant 1.0 today, could becomes_r · ω(e). But Phase 12’s defect is sign, not magnitude (its planning doc,pdr2_phase12_per_relation_criterion_planning.md), soωwaits until a signeds_rworks. - ULTRA message passing.
GeneralizedRelationalConv.forwardaccepts anedge_weight(default ones), butBaseNBFNet.bellmanfordnever passes one, so this needs plumbing. A foundation trained on all-ones weights would see fractional weights only on her edges, at inference. Whether that behaves is untested; this is an inference, not a result. Try it last.
For 13.2’s trajectory features, decay belongs inside the feature, not on the fact. Replace “slope over 90 days” with an exponentially weighted least-squares slope, weighting each point by 0.5 ^ (age / h). A hard 90-day window over monthly draws holds about three points and jumps when one ages out; a soft window changes smoothly. That also answers part of §6 Q3 (uneven sampling).
8.4 As-of builds: the idea worth the most
An as_of date on the KGIA profile build and on materialize_patient_kg would hide everything observed after it. The raw rows are already dated (biomarker ts, report_date, collected_date, episode dates), so a profile can be rebuilt for any date on demand.
Two timelines, which the article does not distinguish. A fact has a valid time, when it was true of her body (the specimen collection date). It also has a knowledge time, when Maple could have known it (the report or ingest date). A tumor panel is typically reported weeks after its specimen was taken, and it can describe tissue from much earlier. The Altera panel, for example, was reported 2025-02-25, ten days after the 2025-02-15 RGCC date on file. Each use below needs a different timeline. Genomic rows carry only a report date, so valid time for them means adding the collection date, which is printed on each report.
Three uses:
-
Decision-time snapshots (§6 Q9), on knowledge time. A recommendation records its
as_of, and anyone can rebuild exactly the inputs it saw. That answers Q9 without a materialized snapshot table. -
Feature–label alignment, on valid time. Since 13.1, the profile always takes the latest value; the patient graph keeps the oldest (§8.2), so neither is aligned with both draws. And the February 2025 RGCC draw is scored against a profile containing the tumor panel reported in September 2025, seven months after that draw. The draws are evaluated separately; their features are not. With
as_of, each draw pairs with the profile as of its own date:- February 2025 with the January 2024 tumor profile and the February 2025 panels;
- September 2025 with the panel reported 2025-09-13, six days before the 2025-09-19 draw, and most likely run on tissue from the 2025-08-25 excision (to confirm from the report).
-
A within-patient longitudinal test. Two as-of profiles give two rankings, and two draws give two label sets. The test asks whether the change in ranking tracks the change in measured sensitivity: taxanes down, topoisomerase-II agents up. It is patient-specific, needs no second patient, and the 12 drugs that moved ≥25 points give it a sharp contrast. Its limits:
- n is small;
- only the somatic channels differ between the two profiles, since the polygenic scores are constant;
- RGCC measures circulating tumor cells, while the profile describes tissue;
- it is premature until the base ranker shows a clean signal, and smoke run #1’s clean holdout showed none.
The design costs nothing to keep ready, and it is a candidate closure criterion for §5.
As-of filtering is necessary, not sufficient. The profile must also be scoped to the colorectal primary, because her separate melanoma primary has its own specimens and dates. A date filter alone cannot tell two cancers apart. The expression builder already scopes to the index cancer (ExpressionScope, GH #212), and an as-of build has to keep that scope, not replace it.
The same idea applied to the graph. PrimeKG is a 2023 snapshot whose edges carry no dates. A time-split evaluation would hold out indications approved after the snapshot as positives and ask whether the foundation ranks them high. That needs a dated approvals list from outside the graph, such as FDA approval dates. It tests the base ranker, not personalization, but at population scale rather than on her 112 drugs. mods_kg_source records a version and an ingested_at, but no upstream release date. Recording one would also let the Explorer and the LLM say “per PrimeKG (2023)” instead of stating a fact undated.
8.5 Conflicts: the latest wins only when the thing was measured again
The article’s own example shows the hazard. Three assertions about one fact within a few weeks get weights 0.9812, 0.9775 and 0.9738. The “answer” turns on a 0.4% difference, that is, on which source wrote last. That is tie-breaking by date, which Maple refuses for anything feeding the model or a clinical view. Two cases have to be kept apart:
-
State measured again. A later lab, ctDNA draw, RGCC draw or re-biopsy describes a later state. Taking the latest as of a date is the definition of “current”, not a tie-break. 13.1 does this.
-
Conflicting assertions about the same state. Here recency is not evidence:
- the duplicate Signatera documents (21 documents, 19 draws);
- the CA 19-9 of 78 on 2026-03-16, between 23 and 22, with null rows the same day. It is a probable mis-parse, and a freshness ranker would promote it.
- trial status: the BATTMAN enrollment press releases are still live, so a retriever dating them by fetch time would rank them fresh, while ClinicalTrials.gov says terminated. Authority decides, not age.
- VAFs across specimens: a later one supersedes nothing.
Resolve by source authority, then by agreement between sources. If neither settles it, show both and refuse to pick.
8.6 Dated facts in the prompt
The article’s last step is the one Maple’s clinical LLM path can use directly:
- Every fact the LLM sees should carry its observation date, and the snapshot its as-of date.
- Facts that are prior-epoch or decaying should say so.
The article’s alternative, passing only the top-weighted fact, is the wrong half of its advice for clinical use. Hiding the prior value hides the trajectory, which §1 argues is often the signal. Pass the latest value with its predecessors, dated. Apart from the genomics gap in §8.2, mods_snapshot_service already does this.
8.7 Beyond Phase 13: Maple’s own memory
The article is about retrieval, and Maple’s vector memory is a retrieval system. It already has recency ranking, salience decay and a superseded_by stamp from consolidation merges. Migration 095 dropped its valid_from/valid_to columns as unused. Its decay is keyed on last access (by default ×0.9 per 30 days unread, floor 0.1), not on the age of the assertion.
The gap the article exposes is supersession, not decay. On 2026-10-03 the session-start digest served two notes side by side: “CLOSED (GH #371): … frequency route yields 8.48%”, and the note that overturns that conclusion (GH #404). Both sit in the pinned/high-salience set, written a day apart. No half-life can separate notes a day apart; only an explicit link can. The older note is still partly right (hm_source still cannot orient palindromes), so the link has to be per-claim, or at least visible in the digest. This belongs in memory_architecture.md, not in Phase 13.
8.8 Changes to the Phase 13 plan
| Item | Change | Status |
|---|---|---|
| 13.1 | Snapshot sort fix | Shipped (checked in code 2026-10-03) |
| 13.1b (new) | Date every patient edge, and stop keeping the oldest. RGCC, alteration and expression edges get their observation date and report id, with repeat observations kept rather than colliding (GH #701). Prod shows the oldest observation winning today (§8.2). The LLM genomics section gets report_date (GH #702). An ingest check flags any observation dated after its own ingest | patient edges fixed (GH #701, 2026-10-04); #702 and the ingest check open |
| 13.1c (new) | as_of on profile build and patient-KG materialization, on both timelines (§8.4). Recommendations record the as_of they used | not started |
| 13.2 | Exponentially weighted trajectory features, with h per marker. CA 19-9 replaces CEA as the first series. ctDNA MTM/mL follows once GH #131 lands. The staleness table (§8.3) defines “current” | design; table needs sign-off |
| §5 | Within-patient longitudinal test (§8.4, use 3) as a closure-criterion candidate, gated on a clean base signal | design |
| — | Time-split evaluation of the graph; upstream release date on mods_kg_source | option, not scheduled |
| — | One shared RGCC reader (Richard, 2026-10-04: “a centralized place to publish the RGCC data, so the logic is only in one place”). A small module returning dated per-draw rows, a latest-draw view, same-date conflicts refused, and one name-alias map. Each reader (patient graph, scoring, metro #750, embedding, calibration, viz) adopts it when next touched; no big-bang refactor | wishlist |
None of this is on the current critical path (GH #599: earn a signal, ship a list):
- The two date gaps and the
as_ofparameter are the cheapest items with direct value. - The within-patient test is the item that could help earn a signal, once there is one to earn.
Parallelization analysis
- Surfaces touched.
- Yes: backend services (
mods_patient_kg_service.py,mods_snapshot_service.py,mods_kgia_profile_service.py), tests, docs. - Only if an option is taken: a migration (an upstream release date on
mods_kg_source, unless it goes inmetadata); the MCP tools (only ifas_ofis exposed there). - No: frontend, worker handlers, DB-stored config.
- Yes: backend services (
- Parallel split.
- The two 13.1b issues are independent of each other and of 13.1c.
- 13.2 waits on the staleness-table sign-off.
- The longitudinal test waits on 13.1c and on a base signal.
- Collision hotspots.
backend/migrations.py, and only if the release date becomes a column. Nothing else on the list. - How to verify each surface.
- RGCC edges: a unit test with two draws of one drug, in the same class and in a changed class, asserting both observations survive with their dates. Then count her sensitivity edges per drug on prod.
- Snapshot: a unit test that
report_dateis emitted, newest first. as_of: a unit test that a row dated afteras_ofis excluded, and a rebuild as of 2025-03-01 that contains no September 2025 tumor data.
9. Cross-references
- PDR_PIPELINE_PLAN.md — master plan; Phase 13 stub points here.
- pdr_phase8.5_variant_kg_planning.md — variant-aware KG; complementary.
- pdr_phase0_gpkg_writeup.md — writeup template for Phase 13 closure documentation.
backend/mods_kgia_profile_service.py— site of the snapshot policy bug for 13.1.backend/mods_snapshot_service.py— patient snapshot builder; preserves full trajectory (correctly).docs/References/PDR1/patient_merge.py— paper precedent (PRS + biomarker scalars, no temporal).docs/References/PDR2/— paper precedent (KGIA loss with genetic + biomarker scalars, no temporal).