You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The three-state measurement is already generic: Cell/CellState in cells.py know nothing about fire, FieldSpec.classify and field_coverage in coverage.py operate on any FieldSpec, and require_columns refuses any file per row. What is not generic is everything around them — FRAP_FIELDS and DINS_FIELDS are Python literals inside schema.py, perimeters.py and dins.py are bespoke report builders, render.py is 1,132 lines of two hand-written pages, and cli.py takes exactly --frap and --dins.
This makes the reviewed field registry a declared file rather than a Python literal, and adds a verb that measures a tabular file against one: registry in, three-state coverage artifact out, with the same fail-closed behaviour (SchemaDriftError, SentinelDriftError, outside_published_domain, marker_basis, integer-only shares). The two shipped registries are re-expressed in that format as the proof it is a seam rather than a second branch, and the acceptance test is that site/data/perimeters-coverage.json and site/data/dins-coverage.json come out byte for byte unchanged.
Why this project
The repository's whole argument is that publishers already state where their data is incomplete and nobody publishes the arithmetic behind those sentences. That argument is not about wildfire. It is true of every civic dataset with a coded-value domain and a nullable column, and this is the only implementation of it that counts a published Unknown code as a third state rather than folding it into either neighbour.
Right now that method is locked to two files. Somebody who wants it pointed at their own layer has to fork schema.py. A declared registry turns the reviewed judgment — the domains, the markers, the basis, the finding-of-absence values — into the artifact it already is in docs/MARKERS.md, and makes "a third layer" a registry plus a page rather than a fourth module.
It is also the test ADR-0006 asks for in the sibling project's idiom: a seam is proven by a new source landing without the existing outputs moving.
Concrete seams
src/perimeter/schema.py — FieldSpec (line 70), Basis (line 51), require_columns (line 194), FRAP_FIELDS (line 286), DINS_FIELDS (line 453). The dataclass is already the format; this gives it a serialization and a loader that refuses an unreviewed registry.
src/perimeter/records.py — classification at the edge is already registry-driven and should not need to change; if it does, that is the finding.
src/perimeter/coverage.py — field_coverage/field_coverages are generic; perimeter_report and dins_report are not, and the generic path needs a report that is only the field table plus whatever the registry declares.
src/perimeter/artifacts.py, src/perimeter/render.py, src/perimeter/cli.py — where the bespoke half lives.
docs/MARKERS.md — a registry file makes the audit and the registry the same object, which is the current promise ("schema.py is the registry itself; this document is the audit of it").
Where the generic path stops. The JSON artifact generalizes cleanly. A page does not: render.py carries the three-state key, the marker lists, the counterfactuals and the publisher's quoted caveats, and a generic page that renders a registry with no quotes would publish a coverage number stripped of the sentence it answers — the thing this project exists to refuse. It may be right for the generic path to stop at the artifact and say so.
Whether a registry is publishable without a marker audit. Every FieldSpec here carries a basis and a note because somebody reviewed it. A loader that accepts a registry with basis: unreviewed makes it easy to produce a well-formed measurement nobody has reviewed.
Relationship to open work
This is not the schema work in #63, which publishes a contract for the artifacts this code already emits, and not the library work in #64, which is about the acquisition path. Both are compatible with it and #63 in particular becomes the output contract of the generic path.
Size
Multi-session, and the largest of the current proposals. The registry format and loader are a couple of days; the honest cost is render.py and deciding item 2.
What
The three-state measurement is already generic:
Cell/CellStateincells.pyknow nothing about fire,FieldSpec.classifyandfield_coverageincoverage.pyoperate on anyFieldSpec, andrequire_columnsrefuses any file per row. What is not generic is everything around them —FRAP_FIELDSandDINS_FIELDSare Python literals insideschema.py,perimeters.pyanddins.pyare bespoke report builders,render.pyis 1,132 lines of two hand-written pages, andcli.pytakes exactly--frapand--dins.This makes the reviewed field registry a declared file rather than a Python literal, and adds a verb that measures a tabular file against one: registry in, three-state coverage artifact out, with the same fail-closed behaviour (
SchemaDriftError,SentinelDriftError,outside_published_domain,marker_basis, integer-only shares). The two shipped registries are re-expressed in that format as the proof it is a seam rather than a second branch, and the acceptance test is thatsite/data/perimeters-coverage.jsonandsite/data/dins-coverage.jsoncome out byte for byte unchanged.Why this project
The repository's whole argument is that publishers already state where their data is incomplete and nobody publishes the arithmetic behind those sentences. That argument is not about wildfire. It is true of every civic dataset with a coded-value domain and a nullable column, and this is the only implementation of it that counts a published
Unknowncode as a third state rather than folding it into either neighbour.Right now that method is locked to two files. Somebody who wants it pointed at their own layer has to fork
schema.py. A declared registry turns the reviewed judgment — the domains, the markers, the basis, the finding-of-absence values — into the artifact it already is indocs/MARKERS.md, and makes "a third layer" a registry plus a page rather than a fourth module.It is also the test ADR-0006 asks for in the sibling project's idiom: a seam is proven by a new source landing without the existing outputs moving.
Concrete seams
src/perimeter/schema.py—FieldSpec(line 70),Basis(line 51),require_columns(line 194),FRAP_FIELDS(line 286),DINS_FIELDS(line 453). The dataclass is already the format; this gives it a serialization and a loader that refuses an unreviewed registry.src/perimeter/records.py— classification at the edge is already registry-driven and should not need to change; if it does, that is the finding.src/perimeter/coverage.py—field_coverage/field_coveragesare generic;perimeter_reportanddins_reportare not, and the generic path needs a report that is only the field table plus whatever the registry declares.src/perimeter/artifacts.py,src/perimeter/render.py,src/perimeter/cli.py— where the bespoke half lives.docs/MARKERS.md— a registry file makes the audit and the registry the same object, which is the current promise ("schema.pyis the registry itself; this document is the audit of it").What must be decided first
render.pycarries the three-state key, the marker lists, the counterfactuals and the publisher's quoted caveats, and a generic page that renders a registry with no quotes would publish a coverage number stripped of the sentence it answers — the thing this project exists to refuse. It may be right for the generic path to stop at the artifact and say so.FieldSpechere carries a basis and a note because somebody reviewed it. A loader that accepts a registry withbasis: unreviewedmakes it easy to produce a well-formed measurement nobody has reviewed.Relationship to open work
This is not the schema work in #63, which publishes a contract for the artifacts this code already emits, and not the library work in #64, which is about the acquisition path. Both are compatible with it and #63 in particular becomes the output contract of the generic path.
Size
Multi-session, and the largest of the current proposals. The registry format and loader are a couple of days; the honest cost is
render.pyand deciding item 2.Proposed with AI assistance.