Papers layer — recovery ledger¶
Landscape: gns561-cca-autophagy-001 · Layer: papers · Opened: 2026-08-24
Task: OGUR-115 / T3.3a · Contract: mvp-scope.md §3.2
Headline¶
308 of 582 frozen papers recovered — 0.529.
This is the first number the papers layer has ever had. It is not a gate, and this document does not propose one. Read the two qualifiers below before comparing it to anything.
| Denominator | 582 — every frozen row, all identifier-resolved |
| Recovered | 308 (159 frozen-OpenAlex, 149 frozen-PubMed) |
| Recovery | 0.529 |
| Fresh harvest | 19,973 rows (10,211 PMIDs · 9,762 OpenAlex W-ids) |
| Measured | same day as the harvest — both 2026-08-24 |
| Cost | $0 · no LLM · no DB write |
| Harvest complete | no — see below |
Artifacts, all under archived_data/gns561_explore/: papers_overlap_GNS561.json ·
scientific_outputs_GNS561_harvest_meta.json · scientific_outputs_GNS561_harvest_ids.csv
The harvest is not complete, and that is recorded¶
meta.complete is false. Two OpenAlex queries returned nothing for some year
slices: anchor for 2016–2021, and immunity for 2016. Both are believed to be real
zeros rather than outages — GNS561 barely appears in the literature before 2022 — but
neither adapter can tell a failed request from an empty year, since both encode a
request exception as []. So the slices are enumerated in meta.empty_slices and the
run is marked incomplete rather than published as a clean ten-year sweep.
This was caught in review. The first harvest committed to the PR contained the same two
empty slices and reported nothing: the guard totalled rows per query across its
eleven slices, so a slice returning zero was invisible as long as any other slice
returned rows. Publishing --allow-empty-slices is now an explicit, recorded act, and
it is deliberately a different flag from --allow-empty-source — a source that
returned nothing at all still aborts.
Two things that make 0.529 not comparable to a Gate R number¶
1 · This is a retrieval ceiling, not end-state recovery. The frozen 582 is
post-curation — every on-thesis survivor of a ~880-row harvest, per
lit_curate.prepare_corpus. This measurement is against a pre-curation harvest,
because T3.3a is harvest-only and curation lands in T3.3b. Curation can only remove
rows. So the number a curated pipeline scores can only be lower than 0.529, never
higher. A ceiling misread as a floor sets the gate too high.
2 · The two harvests are not the same shape. The frozen corpus came out of a multi-round discovery loop with many generated concept queries. This is one round of 28 fixed queries. A miss can therefore mean "our fixed query set is narrower than the discovery loop's" — a real retrieval gap, and simultaneously not a porting defect.
Classified misses¶
Every miss is classified. Nothing is residual-unknown.
| class | n | what it means | evidence |
|---|---|---|---|
retrieval-gap |
186 | Resolves upstream, inside the window, our queries did not return it | all 151 miss-PMIDs re-probed at PubMed; every one still resolves |
matcher-gap |
87 | The harvest did return the paper — under an identifier the frozen side does not carry | title-key join via abstracts.title_key |
known-unreachable |
1 | Published before the window, and genuinely absent from the harvest | frozen date < today − 3650d |
upstream-drift |
0 of the PMID-bearing misses | Identifier no longer resolves | 151 PMIDs probed, complete, none found |
The upstream check is partial, and the artifact says so. 123 of the misses carry
only an OpenAlex W-id, and _search_works has no by-identifier path that does not also
take a search term — so those were not probed. 50 of them sit in retrieval-gap.
The artifact records upstream_probe: {pubmed: complete, openalex: not-probed,
unprobed_misses: 123} rather than a single boolean, because one flag covering both
would let a partial check read as a complete one. "Every miss classified" is true;
"every miss's cause independently confirmed upstream" is not, and only the first is
claimed.
Membership is tested before the window. known-unreachable asserts this
configuration could not retrieve the row, and a row sitting in the fresh harvest
refutes that whatever its date column says. The two dates are not the same
measurement either — PubMed filters on [PDAT] while the frozen date is
ArticleDate/publication_date, which differ by months for an e-pub-ahead-of-print
paper. Ordering the window first published a known-unreachable for a paper the
harvest had in hand (caught in review; oblongifolin C, frozen date 2016-08-04,
present in the harvest under PMID 27499017).
The 87 matcher-gap rows have one cause, and it is cheap to fix¶
72 of 87 are frozen OpenAlex rows, and 73 of 87 were found in the fresh harvest
via PubMed. The same paper carries a PMID on the fresh side and only a W-id on the
frozen side, so the two cannot join even though both retrievals succeeded. The cause is
in the adapter: _SELECT_FIELDS in ogur/sources/openalex.py requests neither ids
nor doi, so an OpenAlex row can never carry its own PMID.
One of them is the flagship asset paper — "GNS561, a new lysosomotropic small molecule, for the treatment of…".
Adding ids to _SELECT_FIELDS would convert most of these into hits, taking recovery
to roughly 395/582 = 0.679 with no change to the query set. That is an adapter
change and therefore out of scope for T3.3a; it is handed to T3.3b as a named,
sized fix rather than left to be rediscovered.
Harvest configuration¶
scripts/eval/gns561_discovery/lit_harvest.py — 14 PubMed MeSH queries + 14 OpenAlex
concept queries, 10-year window, deterministic, no LLM.
Arms map one-for-one onto lit_curate.THEMES (THEME_TO_ARM pins this, and a test
fails if the two drift): anchor · lysosomotropic · liver · pancreatic ·
upstream · tfeb · selective · mapk · immunity · counter · adjacency.
Year-sliced retrieval, both sources. _search_pubmed passes sort="date", so a
flat retmax over a ten-year window returns the N most recent papers and truncates
the older half outright — measured, ("Autophagy"[MeSH]) AND ("Neoplasms"[MeSH]) has
~11k in-window hits, which at Polygon's retmax=200 would have been about the last
eight weeks of publication and nothing else. Each calendar year is asked separately, at
100 rows per source per slice. Both slices use parameters the adapters already accept
([PDAT] range; extra_filter="to_publication_date:…"), so ogur/sources/* is
unchanged.
Counter-evidence arm — 3,668 rows, the largest single arm. For an autophagy inhibitor the against-the-thesis case is not symmetric with Polygon's. Three distinct literatures: autophagy as tumour-suppressive (BECN1 haploinsufficiency — blocking it may promote tumourigenesis); the HCQ oncology trials that read out negative on target engagement; and on-target toxicity (PPT1/CLN1 loss-of-function, chloroquine retinopathy).
Input for T3.3b.
scope_fit._OFF_INDICATION_TOKENScontainsbatten,neuronal ceroid,cln1,retinopathyandmalaria, andscore_paperhard-vetoes any paper carrying one without an oncology token. So the counter arm's most decision-relevant papers are pre-marked for deletion by the curator. The harvest does not curate, so this is not T3.3a's defect — but T3.3b's contract makes eligibility a config knob, and this is the case that knob exists for.
Reproducing¶
uv run python scripts/eval/gns561_discovery/lit_harvest.py --allow-empty-slices
uv run python scripts/eval/gns561_discovery/lit_overlap.py --probe-upstream
The second command works on a clean checkout without the first. The full harvest is
gitignored (its abstracts run to ~33 MB), so the scorer falls back to the tracked
scientific_outputs_GNS561_harvest_ids.csv and still resolves the window through
scientific_outputs_GNS561_harvest_meta.json — verified with both the full harvest and
the discovery cache removed: 308/582, misses 186 / 87 / 1, window
2016-08-26 [harvested_at 2026-08-24 - 3650d]. Only --probe-upstream needs the
network.
The scorer anchors its window to the harvest's own harvested_at, not to the day it
runs. Derived from date.today() the classification drifts — on these inputs a
2016-08-26 window start yields 1 known-unreachable and a 2017-08-26 start yields 5 —
so a re-run next year would rewrite the causal ledger with no input having changed. The
provenance of the boundary is published as window_start_from.
The upstream probe fails closed: _search_pubmed returns [] on any request
exception, which is byte-identical to "none of these ids exist", so a batch resolving
nothing is reported as an incomplete probe rather than as drift. An unproven
retrieval-gap is a weaker claim than a false upstream-drift, and only one of the
two misstates the upstream record.
A re-run will not reproduce 19,973 rows exactly, and is not intended to: both
sources anchor their window to the run date, and upstream adds papers continuously. The
harvest is pinned by its queries, not by its output. Its harvested_at is recorded
in the meta JSON so a later render cannot restamp a stale corpus as current.
The 282 frozen OpenAlex rows carry no identifier in the committed corpus — oa_id was
dropped when it was written. They are resolved from
scientific_papers_GNS561_v1_ids.csv, a tracked 582-row sidecar (54 KB) carrying
row_index, pmid, oa_id, title_key. That file is what makes the headline re-checkable:
measured on a checkout with the discovery cache removed, the scorer reproduces
308/582 from the sidecar, where without it the same command reports 146/300 — a
different, smaller measurement wearing the same name. The scorer prints which source
answered (sidecar / cache / pmid-only) and records it as id_source in the
artifact.
Regenerate the sidecar with --emit-frozen-ids on a machine that still holds
archived_data/.eval_cache/v2 (untracked, ~159 MB); the frozen corpus is immutable, so
it never goes stale. Four of the 282 titles map to more than one W-id and first-wins is
used — at most a ±4-row effect on 582, not worth a tie-breaker at this stage.
Decision¶
Open. The founder decides the papers gate on this ledger. Nothing here is pre-negotiated, and the two qualifiers above are the reason the raw 0.529 should not be compared directly against the ≥0.80 per-layer figure used for layers whose fresh side is a curated pipeline.