feat(provenance): resolve the last 5 unknown waves from evidence (0 unknown)#596
Merged
Merged
Conversation
Every wave dir that ships a SOURCE.org now records a terminal answer: 123
stamped, 0 unknown. Each of the five resolutions is validated against the
directory's actual contents and the live WB catalog -- a year match alone
proves nothing (that is what put the wrong id in Nigeria/2018-19).
Resolved to a catalog id:
* Niger/2011-12 -> 2050. Data/ contains a directory literally named
NER_2011_ECVMA_v01_M_Stata8 -- the WB idno itself.
* Senegal/2021-22 -> 6278. 58 ehcvm_*_sen2021 files; the only SEN EHCVM-2
entry (4297 is the 2018-19 wave).
* Nepal/2003-04 -> 74. Matched on idno + year range only: this wave's
Data/ is EMPTY (Nepal's microdata is NSO-hosted, not WB). So provenance
is KNOWN while the data is ABSENT -- two different facts, and this work
exists to stop conflating them.
Reclassified unknown -> none (a terminal state, not a gap):
* GhanaSPS/2013-14, GhanaSPS/2017-18. EGC-ISSER (Yale/ISSER) panel waves 2
and 3, not WB datasets. A GHA query across the ENTIRE WB catalog (all
collections, 152 entries) returns exactly one GSPS study -- 2534, the
2009-2010 wave -- and nothing later; the file naming (01a_consent.dta,
00_comm_info.dta) is the EGC-ISSER instrument. Leaving these 'unknown'
would mean someone re-searches them forever: the same absent-vs-unknown
black hole this work closes, one level down.
New #+PROVENANCE_VALIDATION: field records how strongly an id is corroborated
-- content-validated (the wave's own data files confirm it) vs catalog-only
(catalog metadata alone; no local data to check, as for Nepal). "Right for
the right reason" and "right by coincidence" must not look identical.
_declared_waves() now folds wave_folder_map values in: Country.waves is not
always a list of directory names (Nigeria's are rounds -- 2010Q3, 2011Q1;
Tanzania's are logical waves), and provenance is recorded per directory.
Caught by the new zero-unknown test.
Rebased onto development, which deleted the stray Benin/2018-2019/ duplicate
this branch surfaced. Country.waves verified identical to pristine for all 47
countries. Full suite: 3638 passed, 0 failed (test_currency.py::
test_feature_ghana_per_wave deselected -- it fails identically on development).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtARb9se2un1DLFJrrpjHe
ligon
added a commit
that referenced
this pull request
Jul 12, 2026
…time Ethan's rulings on #597: South Africa takes the GHS **and** the IES + LCS (28 waves). Each series is now acquired for a STATED reason: GHS (21) roster, housing, education, employment. NO consumption module -- it cannot populate `food_acquired`. IES (5) income + expenditure + acquisition diary. LCS (2) living conditions + expenditure diary. IES and LCS are what feed the demand-estimation path. So the ZAF pin becomes r"_(IHS|GHS|IES|LCS)_" -- 29 rows, zero over-match, against the 320 ZAF rows `datafirst` actually returns. CAPABILITY PRE-POPULATES `not_asked` ------------------------------------ When SA-GHS lands, `South Africa/food_acquired/2015` grades `absent`, someone files it as a gap, and a probe (label sweep + sibling differential + questionnaire) eventually rediscovers a fact the WB catalog told us the day we acquired it. That is archaeology to recover knowledge we already had. `lsms_library/capability.py` records what a *series* measures when it is acquired. A `SeriesCapability` names the features it `provides` and `lacks`; `proposed_absent_verdicts()` turns each `lacks` entry into rows for `.coder/coverage/absent_verdicts.csv` in the schema `load_verdicts()` already reads. The probe sweep then only adjudicates cells inherited WITHOUT a capability record -- a shrinking set, not a permanent tax. AND IT MAY NOT CLOSE A CELL ON A CATALOG BLURB ---------------------------------------------- This is the whole discipline. A capability asserted from a cataloguer's topic list is NOT C4. Closing a cell on it would be the Albania mistake with better paperwork -- an unevidenced negative is unfalsifiable, and therefore permanent whether or not it is true. So each record carries a `validation` level, and the verdict is DERIVED from it rather than chosen by the caller: catalog-only WB topics / abstract -> `unsure` (no close) data-validated C1 label sweep negative -> `unsure` (no close) questionnaire-validated C4: the questionnaire read -> `not-asked` (closes) `data-validated` deliberately does not close either: a negative label sweep is exactly as consistent with `asked-not-distributed` as with `not-asked`. That is precisely why C4 is mandatory. Every record shipped here is `catalog-only`, so every one proposes `unsure`. The 21 GHS `food_acquired` rows it would emit are all `unsure`, `checks_run` empty, evidence prefixed `[catalog-only; C4 NOT run]`. Upgrading a series to `questionnaire-validated` -- one RA, one PDF -- is what converts them into a permanent `not-asked`: a bounded human step per SERIES, not per cell. `capability.audit()` enforces the invariant; `tests/test_capability.py` proves a catalog-only record cannot close a cell *through the real `load_verdicts()`*, and that the questionnaire upgrade does. No verdict rows are written -- SA is not acquired. The mechanism is proven, the shape is up for review. Rebased onto development: #596 resolved the Nepal/Niger/Senegal/Nigeria provenance gaps I had reported, and #598's `blocked` tier supersedes my data_info.yml edit (its emptied "Countries Without Microdata" map is taken wholesale). Census: 79 rows, 54 missing in-remit waves. Refs #597. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NtARb9se2un1DLFJrrpjHe
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #595 (merged). Closes out the 5 waves that PR left as
unknown.Note: #595 was merged and its branch deleted while I was preparing this, so this is a new PR on a new branch rather than a push to #595.
The 5 unknowns are now 0
You did the search; I re-validated every candidate independently — against the directory's actual contents and the live WB API — before applying. A year match alone proves nothing; that is exactly what put the wrong id in
Nigeria/2018-19. All five checked out.Resolved to a catalog id (3)
Niger/2011-12Data/contains a directory literally namedNER_2011_ECVMA_v01_M_Stata8— the WB idno itself. Confirmedid=2050, idno=NER_2011_ECVMA_v01_M, 2011-2012.Senegal/2021-22ehcvm_*_sen2021files (ehcvm_conso_sen2021,ehcvm_individu_sen2021, …). ConfirmedSEN_2021_EHCVM-2_v01_M, the only SEN EHCVM-2 entry (4297 is 2018-19).Nepal/2003-04NPL_2003_LSS-II_v01_M, idno + years unambiguous — but see below.Reclassified
unknown→none(2)GhanaSPS/2013-14andGhanaSPS/2017-18— EGC-ISSER (Yale/ISSER) panel waves 2 and 3.I re-ran the absence check independently rather than take it on faith, since "absent" is precisely the claim that goes wrong quietly:
2534, the 2009-2010 wave — and nothing later.EGC-ISSERkeyword sweep surfaces 8 hits, none of them a later Ghana panel wave.01a_consent.dta,01b2_roster.dta,00_comm_info.dta) is the EGC-ISSER instrument.noneis the right terminal state. Agreed on the reasoning: leaving themunknownmeans someone re-searches them forever — the same absent-vs-unknown black hole this work exists to close, one level down.Nepal: provenance known, data absent — kept as two facts
You flagged this and you were right to.
Nepal/2003-04/Data/holds zero files, so the id is catalog-matched but not content-validated. Rather than bury that in prose, it gets a structured field:#+PROVENANCE_VALIDATION:—content-validated(the wave's own data files confirm the id: Nigeria, Niger, Senegal) vs.catalog-only(catalog metadata alone; no local data to check: Nepal)."Right for the right reason" and "right by coincidence" must not look identical in the record — that conflation is the whole bug this series is about.
A bug the new test caught
Writing
test_no_shipped_source_org_is_left_unknownsurfaced a real defect in my own_may_writeguard:Country.wavesis not always a list of directory names. Nigeria's are survey rounds (2010Q3,2011Q1) and Tanzania's are logical waves (2008-09…2014-15); both map onto directories viawave_folder_map. So 14 perfectly real wave folders looked "undeclared"._declared_waves()now foldswave_folder_mapvalues in. No output change (all those dirs already had aSOURCE.org, so they took the safe update path), but it was a live footgun for the next wave added.Final state
Verification
Country.wavesidentical to pristine for all 47 countries (the feat(provenance): record each wave's WB catalog id; match discover_waves on it #595 invariant — not regressed).test_currency.py::test_feature_ghana_per_wavedeselected — it fails identically ondevelopment; pre-existing, unrelated).test_no_shipped_source_org_is_left_unknown(asserts all 123 record a terminal answer),test_evidence_resolved_waves(pins the 4 id resolutions + their validation levels),test_ghana_sps_egc_isser_waves_are_none_not_unknown(pinsnone, notunknown). 30 tests intest_wave_provenance.py; 71 withtest_data_access.py.development, which includes theBenin/2018-2019/deletion (facdc7b6) — the stray duplicate feat(provenance): record each wave's WB catalog id; match discover_waves on it #595 surfaced.Remaining
The 12 skipped dirs are not declared waves: Bulgaria ×5, Afghanistan ×2, Brazil/1996-97, KenyaLPS ×4. They hold data but the library does not expose them as waves, so they are deliberately left unstamped (stamping would create waves — the
SOURCE.org-is-load-bearing trap from #595). Worth a separate look: either wire them up as real waves, or delete them asBenin/2018-2019was.🤖 Generated with Claude Code
https://claude.ai/code/session_01NtARb9se2un1DLFJrrpjHe