Skip to content

A field note is published output, and two of them were false - #26

Merged
ChelseaKR merged 1 commit into
mainfrom
fix/notes-that-claim-a-denominator
Aug 29, 2026
Merged

A field note is published output, and two of them were false#26
ChelseaKR merged 1 commit into
mainfrom
fix/notes-that-claim-a-denominator

Conversation

@ChelseaKR

Copy link
Copy Markdown
Owner

Closes #23. Adds ADR-0005.

What was wrong

FieldSpec.note reads like a comment and is not one. artifacts.py copies it verbatim into site/data/*-coverage.json beside the counts it describes, so it is the only prose a machine reader of these artifacts gets handed with the numbers.

WHEREFIRESTARTEDONSTRUCTURE told a reader its blank rate was "read against the affected subset rather than the whole file". There is no such subset anywhere in this pipeline. field_coverage (src/perimeter/coverage.py:66) derives a field's total from the records it is handed and dins_report (coverage.py:237) hands it all 132,522. The false sentence shipped inside the same JSON object as the 2.8% it purported to describe.

WHATDIDFIRESTARTFROM carried the shorter form, "Collected only where the structure was affected", with nothing in this repository behind it.

Why not PR #25's replacement

PR #25 removed the false clause correctly and replaced it with "Collected only where the structure was affected per CAL FIRE's dictionary", on both fields. docs/MARKERS.md:181 records a collection restriction for one of the two, quoting D4 as Only recorded for Affected 1-9% damage category: a named damage band, not "affected" generally. grep -rn "Only recorded for" returns that line and no other, and docs/MARKERS.md:182 gives WHATDIDFIRESTARTFROM's source as D3, D4. D4 adds "May not be reliably determined" with no restriction at all.

So this PR quotes D4's actual wording, on the one field whose audit line records it, and says for the other field what the audit actually records. Both notes now say separately what CAL FIRE restricts and what this project counted, because those are two different facts.

The gates

Nothing mechanical read a field note until now. Every other published fact here has a reader: provenance has test_provenance.py, the standards pin has test_standards_conformance.py, a number in page prose has test_pages_html.py. tests/test_field_notes.py adds four, each written as a function that is separately run against input it must reject, per ADR-0004:

  1. A note may not claim a population narrower than the file. A phrase list, and named as one in the docstring: it reads prose and can be evaded by wording it does not carry.
  2. Every field's total is every record it was handed. In the code over the fixtures, in both published artifacts, and in every one of the 451 per-incident blocks. This is the half of gate 1 that holds whatever the wording.
  3. A quotation in a note must be transcribed in docs/MARKERS.md on that field's own line. Double quotes mean source text, single quotes mean a value. This is the gate that rejects PR docs(schema): clarify affected structure note for fire origin fields (fixes #23) #25's attribution.
  4. The note in schema.py and the note in the published artifact are one sentence.

Proof each new check can fail

Measured on this tree.

Against the pre-change registry (tests/test_field_notes.py added, schema.py untouched), gate 1 fails and the other thirteen pass:

tests/test_field_notes.py::test_no_note_claims_a_population_narrower_than_the_file FAILED
...
E   AssertionError: these notes describe a denominator the pipeline does not compute;
E   field_coverage counts every record it is handed:
E   [('WHEREFIRESTARTEDONSTRUCTURE', 'affected subset'),
E    ('WHEREFIRESTARTEDONSTRUCTURE', 'rather than the whole file'),
E    ('WHEREFIRESTARTEDONSTRUCTURE', 'read against the')]
========================= 1 failed, 13 passed in 0.08s =========================

With schema.py corrected and site/ not yet rebuilt, gate 4 fails on real drift:

E   AssertionError: dins-coverage.json publishes a note schema.py no longer holds for
E   ['WHEREFIRESTARTEDONSTRUCTURE', 'WHATDIDFIRESTARTFROM']; rebuild site/ from the
E   acquired files (make site) so the reader gets the sentence the registry actually
E   stands behind
========================= 1 failed, 13 passed in 0.11s =========================

Gates 2 and 3 pass in both states, so each is additionally run against input it must reject, in the same file:

  • test_the_total_gate_rejects_a_field_counted_over_less_than_the_file
  • test_the_quotation_gate_rejects_the_attribution_pr_25_proposed
  • test_the_quotation_gate_rejects_a_quotation_nobody_transcribed
  • test_the_drift_gate_rejects_an_artifact_left_behind_by_an_edit

Positive controls, passing identically before and after: test_the_prose_gate_rejects_the_sentence_it_exists_to_catch, test_every_field_is_counted_over_every_record_it_was_handed[dins], and test_every_published_field_total_is_the_published_record_count[dins-coverage.json].

Gate output

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

Exit code read from $?, not from the tail.

The published artifact

site/ is rebuilt with make site from the acquired files in data/raw/. The rebuild changes exactly two lines of site/data/dins-coverage.json, both of them notes:

 site/data/dins-coverage.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Nothing else in the published measurement moved, which also re-verifies that the committed site/ was current against firep25_1 and the 2026-08-07 DINS retrieval.

🤖 Generated with Claude Code

`FieldSpec.note` is copied verbatim into site/data/*-coverage.json beside the
counts it describes, so it is published output. Two of them were false.

WHEREFIRESTARTEDONSTRUCTURE said its blank rate was "read against the affected
subset rather than the whole file". `field_coverage` derives a field's total
from the records it is handed and `dins_report` hands it all 132,522, so the
sentence described a measurement that has never existed, and it shipped inside
the same JSON object as the 2.8% it was describing. The note now quotes what D4
actually says, "Only recorded for Affected 1-9% damage category", names it as
CAL FIRE's restriction on collection rather than this project's on counting,
and says the counts are over every record.

WHATDIDFIRESTARTFROM carried the shorter form, "Collected only where the
structure was affected", with nothing in this repository behind it.
docs/MARKERS.md:182 gives its source as D3 and D4 and records only that D4 adds
"May not be reliably determined". The note now says that. It does not borrow
the other field's restriction: D4's "Affected 1-9%" is a named damage band
recorded on one field's audit line and not the other's, and writing it as
"where the structure was affected" would widen it to four damage bands under
CAL FIRE's name. That is why PR #25 was closed and why the false clause was
still here to remove.

Nothing in this repository read a field note until now. tests/test_field_notes.py
adds four gates, each also run against input it must reject, per ADR-0004:

  1. a note may not claim a population narrower than the file
  2. every field's total is every record it was handed, in the code, in the
     published artifacts, and per incident
  3. a quotation in a note must be transcribed in docs/MARKERS.md on that
     field's own line
  4. the note in schema.py and the note in the published artifact are one
     sentence

Measured on this tree. Against the pre-change registry, gate 1 fails with
[('WHEREFIRESTARTEDONSTRUCTURE', 'affected subset'), (..., 'rather than the
whole file'), (..., 'read against the')] and the other thirteen tests in the
file pass. With schema.py corrected and site/ not yet rebuilt, gate 4 fails
naming both fields. After `make site`, all fourteen pass; `make verify` exits 0
with 577 tests and 100% branch coverage.

site/ is rebuilt from the acquired files in data/raw/, and the rebuild changes
exactly two lines of site/data/dins-coverage.json: the two notes. Nothing else
in the published measurement moved, which also re-verifies that the committed
site/ was current.

Closes #23. See ADR-0005.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ChelseaKR
ChelseaKR force-pushed the fix/notes-that-claim-a-denominator branch from f290572 to ddf067c Compare August 29, 2026 16:42
@ChelseaKR
ChelseaKR merged commit 4385957 into main Aug 29, 2026
5 checks passed
@ChelseaKR
ChelseaKR deleted the fix/notes-that-claim-a-denominator branch August 29, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant