Skip to content

An access table whose denominators could hold fewer records than the file - #28

Merged
ChelseaKR merged 3 commits into
mainfrom
fix/by-access-accounts-for-every-record
Aug 29, 2026
Merged

An access table whose denominators could hold fewer records than the file#28
ChelseaKR merged 3 commits into
mainfrom
fix/by-access-accounts-for-every-record

Conversation

@ChelseaKR

Copy link
Copy Markdown
Owner

Adds ADR-0007. Stacked on #27; base is fix/yearbuilt-zero-is-not-a-year.

Not from an issue. Found while reading coverage.py for #23, and it is the same defect shape as ADR-0004's coverage exclusion and #24's audit gate: a measurement computed over a set that excluded part of what it should have included, where the exclusion is invisible in the output and the check around it is green because the excluded part is empty on the day.

What was wrong

completeness_by_access reports each DINS field twice: over records with a damage assessment, and over records recorded as Inaccessible. Both populations are read off DAMAGE, and access_field_coverages built its two denominators from the two populations that answer. A record whose DAMAGE is blank or holds a marker answers neither, so it left the table without appearing anywhere in it.

Measured against the pre-change code over four records, one assessed, one inaccessible, one with a blank damage field, one holding a marker:

records handed to the access table: 4
assessed_total     : 1
inaccessible_total : 1
sum of denominators: 2
records in no column: 2
published fields on the row: ['name', 'label', 'assessed_present', 'assessed_total',
                              'inaccessible_present', 'inaccessible_total']

Nothing published said which two records were missing, or that any were.

Why it was invisible

No record in the acquired file is in that state. access_split has counted damage_not_recorded and damage_explicit_unknown since it was written, precisely so a record like that would be reported rather than folded into the assessed population, and tests/test_measurements.py has a test for each. Both are 0 in the 2026-08-07 retrieval.

So every denominator happened to be complete. The table could not have said so, and would not have said otherwise.

The fix

AccessFieldCoverage carries a third population, derived by subtraction from the records handed in rather than by a third predicate, so the three partition the input by construction and a damage state nobody anticipated cannot fall between them. counted_records is the sum, asserted equal to the record count in the code, in the built artifact, and in the committed site/.

The artifact publishes undetermined_present, undetermined_total and undetermined_tenths_pct per field. The page states what its two columns are counted over and what is in neither, in both cases. On this retrieval:

The two columns are counted over 131,931 assessed records and 591 recorded as Inaccessible, which between them is every record in this file. A record whose damage field recorded nothing would be in neither column, since that field is what says whether a structure was reached. There are none here. The count is published per field in the JSON artifact beside this page, so a later retrieval carrying some cannot pass unremarked.

Saying that in words is the part whose absence made this invisible. Two of three populations are shown rather than three: a permanently empty pair of columns reads worse than a sentence, and the counts are in the artifact either way.

Proof each new check can fail

All seven new tests, run against the pre-change source with the new tests in place:

FAILED tests/test_measurements.py::test_the_access_table_accounts_for_every_record_it_was_handed
FAILED tests/test_measurements.py::test_the_third_population_is_counted_field_by_field_and_not_only_totalled
FAILED tests/test_measurements.py::test_an_access_share_over_an_empty_population_is_absent_not_zero
FAILED tests/test_artifacts_and_pages.py::test_the_access_table_publishes_the_population_it_leaves_out
FAILED tests/test_artifacts_and_pages.py::test_the_page_says_what_the_two_access_columns_are_counted_over
FAILED tests/test_artifacts_and_pages.py::test_the_page_names_the_records_the_access_columns_leave_out
FAILED tests/test_published_site.py::test_the_published_access_table_accounts_for_every_record
7 failed, 2 passed, 62 deselected in 0.23s

with, on the committed artifact:

E           KeyError: 'undetermined_total'
tests/test_published_site.py:145: KeyError

The sentence branch that names a non-empty third population is unreachable from any file this project has, so it is exercised directly with dataclasses.replace, the way test_the_damage_table_labels_the_two_non_value_states already does for the two damage states that never occur.

Positive controls, passing identically before and after: test_completeness_is_reported_separately_for_the_two_populations, test_inaccessible_is_a_recorded_value_kept_apart_from_assessed, and test_damage_helpers_agree_with_the_split (3 passed in the pre-change run).

Gate output

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

Exit code read from $?.

Scope, stated plainly

ADR-0007 says what this does not do. field_coverage already counts every record it is handed and is now asserted to (#26). The per-incident blocks partition the file, with records attributable to no incident counted separately; those were checked while writing this and were sound. But nothing here stops the next split from being written the way the first one was. The rule is in the record; there is no mechanism that applies it to a split nobody has written yet.

🤖 Generated with Claude Code

@ChelseaKR
ChelseaKR changed the base branch from fix/yearbuilt-zero-is-not-a-year to main August 29, 2026 00:15
@ChelseaKR
ChelseaKR force-pushed the fix/by-access-accounts-for-every-record branch from f4ffcde to 68f26ce Compare August 29, 2026 00:29
ChelseaKR and others added 3 commits August 29, 2026 09:44
… the file

completeness_by_access reports each DINS field twice, over records with a damage
assessment and over records recorded as Inaccessible. Both populations are read
off DAMAGE, and access_field_coverages built its two denominators from the two
populations that answer, so a record whose DAMAGE is blank or holds a marker
answered neither question and left the table without appearing anywhere in it.

Measured against the pre-change code over four records, one assessed, one
inaccessible, one with a blank damage field and one holding a marker:

    records handed to the access table: 4
    assessed_total     : 1
    inaccessible_total : 1
    sum of denominators: 2
    records in no column: 2

Nothing published said which two were missing or that any were.

No record in the acquired file is in that state, which is why this held. access_split
has counted damage_not_recorded and damage_explicit_unknown since it was written,
precisely so a record like that would be reported rather than folded into the
assessed population, and both are 0 in the 2026-08-07 retrieval. Every denominator
happened to be complete. The table could not have said so and would not have said
otherwise, which is the same shape as the coverage exclusion in ADR-0004 and the
audit gate in ADR-0006: a measurement over a set that excluded part of what it
should have included, with the exclusion invisible and the check around it green
because the excluded part was empty on the day.

AccessFieldCoverage now carries a third population, derived by subtraction from
the records handed in rather than by a third predicate, so the three partition the
input by construction and a damage state nobody anticipated cannot fall between
them. counted_records is the sum, asserted equal to the record count in the code,
in the built artifact, and in the committed site/. The artifact publishes
undetermined_present, undetermined_total and undetermined_tenths_pct per field.

The page states what its two columns are counted over and what is in neither, in
both cases. On this retrieval it reads: "The two columns are counted over 131,931
assessed records and 591 recorded as Inaccessible, which between them is every
record in this file." Saying that in words is the part whose absence made this
invisible.

Measured on this tree. Against the pre-change source, all seven new tests fail,
including KeyError: 'undetermined_total' on the committed artifact, and the
existing access tests pass unchanged. After, make verify exits 0: 588 tests, 100%
branch coverage, determinism byte-identical.

See ADR-0007.

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.
The rebase conflict in this function was resolved by keeping both sides, which
is right for a changelog and wrong for code. Main's #29 wrapped the table in a
focusable <section class="scroll tall" tabindex="0">; this branch still closed
it with the older </div>. Emitting both left <main> unclosed and produced four
stray end tags, which make htmlvalidate caught: the generated page was invalid
in a way no diff of the template would show.

Keeps main's </section> close, drops the orphaned </div> tail, and keeps this
branch's {access_note}. site/dins.html regenerated from data/raw/ rather than
hand-edited.
@ChelseaKR
ChelseaKR force-pushed the fix/by-access-accounts-for-every-record branch from 68f26ce to ae68f48 Compare August 29, 2026 16:45
@ChelseaKR
ChelseaKR merged commit edbfb9b into main Aug 29, 2026
@ChelseaKR
ChelseaKR deleted the fix/by-access-accounts-for-every-record branch August 29, 2026 16:45
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.

1 participant