Skip to content

Nothing was built in year 0 - #27

Merged
ChelseaKR merged 2 commits into
mainfrom
fix/yearbuilt-zero-is-not-a-year
Aug 29, 2026
Merged

Nothing was built in year 0#27
ChelseaKR merged 2 commits into
mainfrom
fix/yearbuilt-zero-is-not-a-year

Conversation

@ChelseaKR

Copy link
Copy Markdown
Owner

Closes #24. Adds ADR-0006. Stacked on #26; base is fix/notes-that-claim-a-denominator.

What was wrong

YEARBUILT counted the literal 0 as a recorded construction year. 12,148 of the 132,522 DINS records hold it, 9.2% of the file and 11.9% of everything that field counted as present, and every one sat inside its 77.0% recorded share. No structure standing in a California wildfire was built in year 0, so this published an absence as a value.

0 is now a declared unknown marker on Basis.INFERRED:

Before After
Recorded value 102,091 (77.0%) 89,943 (67.9%)
Recorded as unknown 0 12,148
Empty cell 30,431 30,431

Why the audit could not have caught it

docs/MARKERS.md covers every field that declares a vocabulary, and tests/test_schema.py enforces that. A numeric field deciding that its zeros are measurements declares nothing at all: no markers, no codes, no domain. YEARBUILT was outside the audit by construction, and the gate reading the audit was green the whole time over a set that excluded exactly the field that needed reviewing.

tests/test_schema.py now fails when a numeric field publishes recorded zeros that docs/MARKERS.md does not cover.

The evidence

docs/MARKERS.md section 7, read off the acquired file in data/raw/ (hashes in PROVENANCE.md):

Observation Value
Records holding 0 12,148 (9.2% of the file)
Distinct incidents / counties / fire years 186 / 45 / 8
Incident groups holding it at all 199 of 436
Incident groups where every record holds it 55 (largest: 446 of 446, Valley 2020)
Holding it and also carrying an APN 11,779 of 12,148
Holding it whose assessed improved value is also 0 5,527 (45.5%)
Holding a real year whose assessed improved value is 0 391 of 89,943 (0.4%)

Not one bad load: 186 incidents across eight fire years, scattered inside incidents as often as it takes a whole one. And it travels with the other parcel number being zero at a hundred times the rate a real year does, which is what a parcel record with nothing on file looks like. The APN is present on 97% of them, so the join found a parcel and the parcel had no year.

Confidence is high that a 0 is not a construction year and moderate on what kind of absence it stands for, which is what the audit row says.

Eight other records hold years that are also implausible (four 1, three 89, one 1543). Left as recorded values on the same reasoning as FIRE_NUM's bare 0 in section 5: eight cells is a scatter of typos, not a convention.

The other five numeric fields

Section 7 covers all of them, including the two considered and left alone:

Field Zeros Reading Declared
NOOUTBUILDINGSDAMAGED 59,628 a count of none no, the zero is the measurement
NOOUTBUILDINGSNOTDAMAGED 58,888 a count of none no
NOOFCARSONPROPERTY 55,831 a count of none no
NUMBEROFUNITPERSTRUCTURE 58,411 contested no, and the reason is recorded
ASSESSEDIMPROVEDVALUE 6,613 contested no, and the reason is recorded
YEARBUILT 12,148 no year on the parcel record yes, marker 0, inferred

NUMBEROFUNITPERSTRUCTURE's zeros land on 39,438 single residences and 15,946 minor structures while its positives concentrate on multiple residences (1,443 of 2,007). A zero on a utility structure is a coherent observation; a zero on a single residence is not obviously one. The file does not settle it, so nothing is declared and the count stays published.

ASSESSEDIMPROVEDVALUE's zeros travel with YEARBUILT's at a hundredfold, but a parcel assessed at zero improved value is inside the field's own range, and the zeros spread across 126 incidents and every damage band. Not declared, and flagged in the audit as the entry to revisit first.

Proof each new check can fail

Measured on this tree. Against the pre-change audit:

tests/test_schema.py::test_every_published_zero_in_a_numeric_field_is_written_up[dins-coverage.json] FAILED
E   AssertionError: dins-coverage.json: these numeric fields publish recorded zeros that
E   docs/MARKERS.md does not cover, so nothing says whether a zero there is a measurement
E   or a placeholder: [('NUMBEROFUNITPERSTRUCTURE', 58411), ('NOOUTBUILDINGSDAMAGED', 59628),
E   ('NOOUTBUILDINGSNOTDAMAGED', 58888), ('NOOFCARSONPROPERTY', 55831),
E   ('YEARBUILT', 12148), ('ASSESSEDIMPROVEDVALUE', 6613)]
1 failed, 4 passed, 252 deselected in 0.06s

It is separately run against a payload no audit covers (test_the_zero_gate_rejects_a_numeric_field_nobody_reviewed) and against a numeric field with no zeros to explain (test_the_zero_gate_says_nothing_about_a_field_with_no_zeros_to_explain).

Positive controls, passing identically before and after: test_every_published_zero_in_a_numeric_field_is_written_up[perimeters-coverage.json] (green in both states, because GIS_ACRES publishes no zeros, so the gate is not just failing on everything) and test_a_recorded_zero_is_counted_as_a_value_and_as_a_zero, which is the policy for NOOUTBUILDINGSDAMAGED and is deliberately unchanged.

The gate stacked underneath this one also earned its keep: declaring the marker without rebuilding site/ failed test_the_published_note_is_the_note_the_registry_holds[dins-coverage.json], naming YEARBUILT.

Gate output

$ make verify
...
Required test coverage of 90% reached. Total coverage: 100.00%
============================= 581 passed in 8.45s ==============================
...
determinism: build/run-one and build/run-two are byte-identical (5 files)
MAKE_VERIFY_EXIT=0

Exit code read from $?.

Counts that moved with it

Twenty-eight of the fifty-four measured fields now declare a vocabulary, twelve published and sixteen inferred. README.md, docs/MARKERS.md, schema.py, render.py and the published pages all say so, and tests/test_schema.py holds them to it. site/ is rebuilt from the acquired files.

🤖 Generated with Claude Code

@ChelseaKR
ChelseaKR changed the base branch from fix/notes-that-claim-a-denominator to main August 29, 2026 00:15
@ChelseaKR
ChelseaKR force-pushed the fix/yearbuilt-zero-is-not-a-year branch from 010ff7a to 3b8c801 Compare August 29, 2026 00:29
ChelseaKR and others added 2 commits August 29, 2026 09:42
YEARBUILT counted the literal 0 as a recorded construction year. 12,148 of the
132,522 DINS records hold it, 9.2% of the file and 11.9% of everything that
field counted as present, and every one sat inside its 77.0% recorded share.
No structure standing in a California wildfire was built in year 0, so this
published an absence as a value.

0 is now a declared unknown marker on Basis.INFERRED. The field publishes
89,943 recorded values (67.9%) and 12,148 recorded-as-unknown. The 30,431 empty
cells do not move.

The evidence is in docs/MARKERS.md section 7 rather than here. Read off the
acquired file: the zeros are in 186 incidents, 45 counties and eight fire years;
199 of 436 incident groups hold at least one and 55 hold nothing else, the
largest being 446 of 446 records on Valley 2020; 11,779 of the 12,148 carry an
APN, so the parcel join found a parcel and the parcel had no year rather than
the join failing; and 5,527 of them (45.5%) also carry an assessed improved
value of 0, against 391 of the 89,943 records holding a real year (0.4%).

Section 7 also covers the other five numeric fields that publish recorded
zeros, including the two considered and left alone. NOOUTBUILDINGSDAMAGED,
NOOUTBUILDINGSNOTDAMAGED and NOOFCARSONPROPERTY are inspector counts where a
zero is the measurement. NUMBEROFUNITPERSTRUCTURE (58,411 zeros) and
ASSESSEDIMPROVEDVALUE (6,613) each have a reading in which the zero is real and
the file does not settle it, so neither is declared and both carry the numbers a
later reader would need to decide otherwise.

The audit gate could not have caught this. docs/MARKERS.md covers fields that
declare a vocabulary, and a numeric field deciding its zeros are values declares
nothing at all, so YEARBUILT was outside the audit by construction and the gate
reading the audit was green over a set that excluded the one field that needed
reviewing. tests/test_schema.py now fails when a numeric field publishes
recorded zeros that docs/MARKERS.md does not cover.

Measured on this tree. Against the pre-change audit the new gate fails with
[('NUMBEROFUNITPERSTRUCTURE', 58411), ('NOOUTBUILDINGSDAMAGED', 59628),
('NOOUTBUILDINGSNOTDAMAGED', 58888), ('NOOFCARSONPROPERTY', 55831),
('YEARBUILT', 12148), ('ASSESSEDIMPROVEDVALUE', 6613)] and the perimeters
artifact passes, since GIS_ACRES publishes no zeros. After section 7 it passes,
and it is separately run against a payload the audit does not cover and against
a field with no zeros to explain.

Declaring the marker without rebuilding site/ was caught by the note-drift gate
added in the previous commit, which named YEARBUILT. site/ is rebuilt from
data/raw/; the published counts move exactly where predicted.

Twenty-eight of the fifty-four measured fields now declare a vocabulary, twelve
published and sixteen inferred. README.md, docs/MARKERS.md, schema.py, render.py
and the published pages all say so, and tests/test_schema.py holds them to it.

make verify exits 0: 581 tests, 100% branch coverage, determinism byte-identical.

Closes #24. See ADR-0006.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rebase conflict in site/dins.html was resolved by taking one side, which is
not a resolution for a generated file. This is make site's output from data/raw/,
so the committed HTML now matches its source rather than a merge of two
independently generated copies.
@ChelseaKR
ChelseaKR force-pushed the fix/yearbuilt-zero-is-not-a-year branch from 3b8c801 to 4dea7d1 Compare August 29, 2026 16:43
@ChelseaKR
ChelseaKR merged commit 19980d6 into main Aug 29, 2026
1 check passed
@ChelseaKR
ChelseaKR deleted the fix/yearbuilt-zero-is-not-a-year branch August 29, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

YEARBUILT counts 12,148 zero-value records as a genuine recorded year; the field was never reviewed for a placeholder marker

1 participant