Skip to content

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_TOKENS contains batten, neuronal ceroid, cln1, retinopathy and malaria, and score_paper hard-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.