All notable changes to this project are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
This file tracks the software version of the nearmiss repository (the version in
pyproject.toml and the git release tag). The five data contracts the project ships are versioned
independently of the software and of each other, and each has a dedicated subsection under every
release so a schema change is never buried in a code change:
- Intake report schema —
schema/report.schema.json, currently1.0.0. The intake contract for precise, pre-aggregation reports. Carried per payload in theschema_versionfield.
- Published dataset schema —
schema/dataset.schema.md(prose) together with its mirroring machine-readable JSON Schemaschema/dataset.schema.json, validated in CI and at publish time (FIX-10), currently1.1.0. The contract for the open per-citydata/published/<city-slug>.geojsonartifact (e.g.davis.geojson). Carried per file inmetadata.schema_version. [Correction history: an earlier revision claimed a "mirroring JSON Schema validated in CI" before one existed, and was corrected to "prose only"; FIX-10 has since landedschema/dataset.schema.jsonplus the contract gate, making the machine-readable mirror real.]
- Official outcome schema —
schema/official-outcome.schema.json, currently1.0.0. A sibling contract for traceable government crash/injury outcomes that must not acquire contributor-report fields or self-assessed semantics merely to fit the intake schema. - Official outcome artifact schema —
schema/official-outcome-artifact.schema.json, currently1.0.0. The private normalized batch contract binding official outcomes to adapter version, expected year, exact distribution assertion, source-byte hash, rejection/record-regression/year-regression policy, and complete accounting. - Ingestion receipt schema —
schema/ingestion-receipt.schema.json, currently1.0.0. The operational audit contract binding each source refresh to immutable raw and normalized hashes, its active commit state, controlled failure class, and prior active hash.
All five schemas follow the versioning and deprecation policy in
schema/dataset.schema.md, summarized
under Schema-versioning policy at the foot of this file. In short: PATCH =
clarifications, MINOR = backward-compatible additive changes (flag and hazard vocabularies are additive,
never silently repurposed), MAJOR = a breaking change — including adding a newly-required field.
Breaking changes are announced at least one MINOR release ahead with a deprecation window and a stated
migration path; published artifacts are immutable and hashed and are never rewritten in place.
Releases are tagged and Sigstore-signed via the tag-triggered pipeline in
.github/workflows/release.yml (FIX-11), starting with v0.2.0; conventional-commit history backs
every entry.
Note (2026-07-05, updated 2026-07-12): the 2026-07-05 revision of this note recorded that no version had ever been tagged, released, or signed and that no release workflow existed. FIX-11 (#51) landed that workflow — version/tag/CHANGELOG consistency gates, a full
make verifyre-run at the tag, CycloneDX SBOM, keyless Sigstore signing, SLSA provenance, a GitHub Release, and PyPI Trusted Publishing — andv0.2.0is the first tag to exercise it. The[0.1.0]entry below remains a versioned, verified milestone onmain, never separately tagged or published.
- BREAKING (source-adapter contract): a crosswalk manifest's free-form
bias_notesarray is replaced by a required[source.bias_profile]table that must answer all eight bias axes the data card names (route_choice,reporter_pool,app_access,language,demographic_skew,survivorship,salience,temporal_campaign).load_crosswalknow rejects a missing, blank, placeholder (n/a,none,TBD), too-short, or non-string answer, so an under-documented source fails at import instead of passing a truthiness check.Provenance.bias_notesbecomes a derived property over the profile, soas_dict()keeps its previous key and gainsbias_profile. Both shipped manifests are migrated. Hard rule 3 was previously enforced only as "the list is non-empty", which a single word satisfied. - Corrected the SimRa manifest license from CC BY 4.0 to CC BY-NC 4.0,
verified against the upstream README.
NOTICEnow records that the NonCommercial condition survives aggregation and that no SimRa-derived data is currently published. - Moved the synthetic, no-cloud quick start into the README's opening screen so
a first-time visitor can reach
make demobefore the project audit narrative.
- A production evidence-to-action thin slice: a complete fictional Decision Dossier, a browser-local CSV/JSON readiness audit, a controlled-language claim compiler, and an Atlas evidence-brief handoff that carries official FARS cells as context without promoting them to local risk. The workflow pages expose their claim boundary and local fingerprint, ship through the allowlisted hash manifest, and run under the structural, axe, RTL, and jsdom contract gates.
- Studio claim tiers are bound to the browser-local readiness result, while the dossier regenerates canonical language through a session-scoped handoff. Crafted URLs cannot supply the tier, claim, or requested-action text.
docs/adr/0013-keep-python-311-floor.md— an accepted exception ADR recording whyrequires-python = ">=3.11"stays (Debian-12-class system Pythons, the merge-blocking 3.11 CI matrix leg, the "runs anywhere Python 3.11+ runs" property) and the triggers that would revisit it.- A declared
[tool.coverage.report] fail_under = 90floor inpyproject.toml, mirroring the floormake testalready enforces on the command line so it also holds for coverage runs that omit the flag (measured 90.44% branch coverage at the time of declaration).
-
The gateway, browser-local Studio, Decision Dossier, and Conflict Atlas now share a chain-of-custody workbench interface: a cobalt evidence surface, connected source-to-action route, offset review artifacts, responsive form controls, and a consistent claim-boundary treatment replace the prior broadsheet shell. The redesign remains dependency-light, reflows without horizontal overflow at 390 px, respects reduced motion, and is included in the hash-bound public artifact and exact-live-byte deployment checks.
-
The README standards-conformance table now uses the portfolio's canonical standard names (e.g. "Code Quality" instead of
CODE-QUALITY, "Responsible-Tech Framework" instead ofRESPONSIBLE-TECH) so the automated conformance checker recognizes every declared row; the AI Evaluation row'sN/Anow carries its reason inline. Applicability calls are unchanged. -
The seed ADR is renumbered
0001-→0000-record-architecture-decisions.mdto match the canonical seed filename indocs/standards/DOCUMENTATION-STANDARD.md; its decision content is unchanged and in-repo cross-references were updated. -
A distinctive “NearMiss Conflict Atlas” identity across the national studio, retained local methods prototypes, and branded 404: a clearance-mark road symbol, asphalt/interstate/brake palette, self-hosted Overpass/Atkinson Hyperlegible/Fragment Mono typography, compact evidence-first hierarchy, and an earlier mobile map replace the previous generic dashboard treatment.
-
A narrowly scoped solo-maintainer REVIEW-GATE policy for pre-1.0 public previews. Mandatory AUTO-GATEs remain unchanged; provisional disposition requires exact synthetic/browser evidence, explicit accountable-owner residual-risk acceptance, unperformed checks, rollback, and expiry. It cannot be cited as a manual screen-reader result, ACR/WCAG conformance, or a stable release.
-
A canonical
nearmiss.chelseakr.comproduction origin on private, versioned S3 behind CloudFront OAC, ACM TLS, Route 53 aliases, deterministic route rewriting, explicit browser-safe MIME metadata, forced client revalidation, a verifier-only cache key, and exact-artifact GitHub OIDC deployment. The existingnearmiss.reportGitHub Pages mirror remains available during the transition. -
Independent mode controls for truthful interaction semantics: “Visualization focus” is required and defaults to pedalcyclists, while the complete evidence ledger retains its own all-modes filter and the state-by-mode matrix always represents the full selected year.
-
A nationwide evidence studio over the reviewed 2020–2024 FARS release index: an official Census state-boundary map, linked state × mode matrix, burden ranking, two-mode scatterplot, state comparison, inspector, and printable evidence brief all share the same suppression-safe annual cells. Deep links preserve the exact year and validated interaction state, while annual-contract, release-stage, semantic-regime, correction-ledger, and boundary provenance stay visible.
-
An immutable 2024 FARS provenance correction: contract revision 2 classifies the exact pinned archive as NHTSA's
annual_report_file, while the revision-1 artifact and release-index URLs remain byte-identical. A versioned artifact/index pair and machine-readable correction ledger bind both generations, and the public page now displays the selected year's release status explicitly. -
A stable
/fars/national/public route for the nationwide 2020–2024 evidence ledger. It is a byte-identical copy of the retained legacy page, uses root-absolute reviewed dependencies, carries one canonical URL, is promoted from the synthetic Davis demo, and is checked by both deploy and recurring live-integrity verification. -
A state-first national FARS experience with an exact 2020–2024 profile for every state and DC, strict
state/year/langshare URLs, explicit 2020–2021 versus 2022–2024 person-coding regimes, isolated historical-load failures, and suppression-safe cells that never infer or expose a zero. -
A place-first Atlas interaction hierarchy: the national map remains the default task, matrix/rank/ comparison tools move behind explicit advanced inspection, and each state evidence sheet ends with a county/local-evidence question rather than implying that a state count can be zoomed into county or corridor risk.
-
A scheduled and manually dispatchable read-only live-integrity sentinel. It rebuilds the exact
mainPages artifact, verifies every remotely retrievable manifest file and annual FARS release pin, exercises localized share URLs, and requires representative private/non-allowlisted paths to remain HTTP 404 without receiving secrets or deployment authority. -
An additive
nearmiss ingest-fars-yearoperator command for exact reviewed 2020–2024 National FARS accident/person archives. It requires an explicit registered year and contract revision, derives every source, mapping, bound, release, and regression decision from that immutable contract, activates an independently replay-verifiable private annual lineage, prints one sorted aggregate-evidence JSON line, and exposes no latest, URL, size, attempt-ID, or policy override. -
Read-only FARS lineage verification for
nearmiss coverage --fars-root. Coverage now separates a source declaration from a verified active receipt/history/raw/artifact chain, deterministically replays normalization, and grants onlyverified_official_outcomeswhen sourcefarsis also declared. It never grants triangulation or changes tiers, core counts, exposure, or publication; unloaded context/intervention declarations likewise no longer mint analytical capabilities. -
A local
nearmiss ingest-farsworkflow that takes an official NHTSA CSV/ZIP already on disk, validates its year, identities, coordinates, accounting and rejection fraction, builds a canonical private outcome artifact, and activates it through the fail-closed ingestion receipt chain. It does not download data, infer involved modes, publish precise outcomes, or grant an analytical outcome capability by itself. -
A fail-closed, source-agnostic POSIX ingestion transaction foundation with owner-only storage, content-addressed raw and normalized artifacts, an atomically replaced active receipt/commit marker, immutable historical receipts, last-known-good validation, controlled error redaction, and explicit lock retention when commit state cannot be proven. Fetch and normalization functions remain injected; this slice does not add live downloads, scheduling, or source-specific CLI orchestration.
-
A strict, offline-testable NHTSA FARS crash-level adapter and
official-outcomeschema. The adapter accepts extracted CSV or official nested ZIP exports, produces deterministic IDs and complete provenance/rejection accounting, bounds archive expansion, and keeps official fatal-crash outcomes separate from crowdsourced near-miss intake. It intentionally does not infer involved road-user modes until a later person-table join. -
An explicit GitHub Pages deployment pipeline that runs only after successful
mainCI, publishes a minimal allowlisted artifact instead of the repository root, stamps the deployed commit, hashes every payload file (excluding only the hash-manifest envelope itself), rejects symlink/path escapes, exercises artifact assembly on pull requests, and smoke-checks the live UI and aggregated dataset after deployment. -
A versioned per-city source registry and
nearmiss coverageassessment. The command reports a conservative evidence tier, actual observed/usable exposure coverage, stale and missing sources, supported capabilities, and the concrete inputs needed to unlock the next tier. Synthetic inputs are permanently labeleddemonstration;partner_cityrequires both measured coverage and an explicit partner organization plus review reference, never report volume alone.
-
docs/METHODOLOGY.mdno longer describes FIX-06 (per-hazard-type rate layers) and FIX-07 (low-confidence exclusion from the primary rate) as "PLANNED, not yet implemented" — both landed in 0.2.0 (#37, #38), but the two claim blocks were never flipped, so the methodology doc misdescribed the published method: it said low-confidence reports "are currently still counted in the primary rate" when the published rate/CI has excluded them (withrate_sensitivity_deltaandexcluded_low_confidence_fractionpublished) since 0.2.0. The corrected claims (rate-union-primary-plus-per-type-layers,low-confidence-excluded-from-primary) now cite behavioral tests as witnesses indocs/CLAIMS.mdinstead of bare source-file paths, so the claims-parity gate anchors them to executed behavior rather than file existence. -
Retire the Davis and Riverside synthetic city experiences, hotspot embed, disconnected browser submission prototype, Leaflet runtime, and synthetic published artifacts from the production site. The national FARS atlas is now the sole public product;
/web/index.htmlremains only as anoindexcompatibility redirect. All synthetic configs, known-answer fixtures, local interfaces, teaching material, and CI contracts remain in the repository for reproducibility. -
Replace broad production web/data globs with an exact national-runtime and reviewed-FARS allowlist. Deployment and the recurring live sentinel now prove that retired synthetic surfaces remain absent while continuing to hash-verify every national release and runtime dependency.
- The retained source-only embed loader now chooses only fixed same-origin paths for the Davis and
Riverside fixtures. A mutated loader
src, unknown dataset slug, or conflicting selector can no longer influence iframe navigation or send the local methods fixture to the retired public URL. - Production publication now obtains AWS authority only after rebuilding and byte-comparing the exact
reviewed artifact, through a GitHub environment restricted to the exact
mainbranch with administrator bypass disabled. The live verifier rejects wrong MIME metadata, unreviewed 404 bodies, hidden host-control objects, private-path response drift, or any manifest-bound byte mismatch. - Browser translations now build their small, audited markup vocabulary with DOM text nodes and allowlisted links instead of reinterpreting catalog or dataset text as HTML. Locale catalogs keep external keys as array values rather than object properties.
- The local map and embed accept query-selected data only as filename slugs inside
data/published/*.geojson; external origins, other directories, traversal, queries, fragments, and duplicate selectors fail closed to the Davis default. Contract tests exercise both valid selectors and malicious path cases.
1.0.0(2026-07-12) — initial official road-safety outcome contract. It separates government crash/injury outcomes from contributor intake semantics, requires source identity, date, location, severity and fatality accounting, and constrains fatal severity to a positive fatality count. The separate contract prevents downstream adapters from inventing reporter or hazard fields that an official source does not contain.
1.0.0(2026-07-12) — initial private normalized-batch contract for FARS crash-level outcomes. It binds deterministic records to the mapping version, expected year, asserted static NHTSA distribution URL, source-byte SHA-256, release label, rejection and distinct record/year regression policy, plus complete row accounting; timestamps are excluded so identical inputs and policy produce identical artifact bytes.
1.0.0(2026-07-12) — initial immutable ingestion audit contract. It records source and attempt identity, UTC attempt bounds, raw and normalized content hashes/paths, the previous active hash, activation state, and a controlled failure object. A successful receipt is also the activenormalized/current.jsoncommit marker, so activation and its evidence change atomically.
Nineteen roadmap items (the whole open PR queue, #29–#61) landed together in this release,
alongside the moderation transparency report (#65). Statistics: network-true hotspot
neighborhoods, a primary rate that names its exclusions, per-hazard-type layers, corridor
aggregation, and a publish-time null calibration. Contracts and supply chain: a
machine-readable dataset schema with a browser-consumer contract gate, hashed CI installs,
and this — the first tag-triggered, signed release. See docs/ideation/README.md § Status
ledger for the item-by-item map.
- Hashed CI installs.
make lock-devcompiles the dev toolchain (.[dev]: pytest, ruff, mypy, pip-audit, babel, ...) to a new committed, hashedrequirements-dev.lock. Every CI job in.github/workflows/ci.ymlnow installs from it withpip install --require-hashes, then the local package--no-deps -e ., instead of resolvingpip install -e ".[dev]"fresh on every run. Closesaudit-2026-07-05/nearmiss-REMEDIATION.mdP1-4. - Single-sourced version strings.
publish.py's embedded and sidecar metadata now readdataset_versionandschema_versionfrom the newsrc/nearmiss/versions.py(DATASET_VERSION,DATASET_SCHEMA_VERSION), instead of hand-duplicated"0.1.0"/"1.0.0"literals;models.Report.schema_versionreads the same module'sREPORT_SCHEMA_VERSION. No version string now exists in more than one place in the source. - Tag-triggered release pipeline. New
.github/workflows/release.yml: on avX.Y.Ztag, it re-checks version consistency (tag ==pyproject.toml== installed__version__) and the CHANGELOG entry, re-runsmake verifyat the tagged commit, builds the sdist + wheel, generates a CycloneDX 1.7 SBOM, Sigstore-signs (keyless, OIDC) the sdist, wheel, SBOM, and every published city GeoJSON, attaches SLSA build provenance, cuts a GitHub Release, and publishes to PyPI via Trusted Publishing (OIDC — no stored token). See "How to verify a release" indocs/DATA-CARD.md. Not yet exercised: no tag has been pushed and PyPI Trusted Publishing has not yet been registered for this repository — see the NOTE at the top ofrelease.yml. - Moderation transparency report (
nearmiss moderate stats). Publishes an aggregate, privacy-floored view of the moderation queue: submission totals by status (pending/approved/rejected), review-flag frequencies, rejection-reason category counts, and the median review latency in hours (received_at→decided_at). Rejection free text is never emitted — a small fixed taxonomy (duplicate,spam,identifier-leak,invalid-location,off-topic,other) buckets it first. Every per-cell count passes through the same k-anonymity floor as the published map data (min_publish_n, default 3): a non-zero cell below the floor is withheld (null) and tallied underwithheld_cells, so "how many did not make it" stays explicit without exposing a group too small to be anonymous.--out PATHwrites a dated Markdown (docs/audits/YYYY-MM-DD-moderation.mdstyle) or JSON artifact. Submissions now carry adecided_attimestamp (set on approve/reject; legacy queue entries without it load fine and are excluded from latency). - FIX-02: network-topology spatial weights for Getis-Ord Gi*.
stats/getis_ord.pypreviously decided Gi* neighbors with a straight-line (haversine) centroid distance band, contradicting METHODOLOGY §8.2's claim that neighbors are "defined on the street network ... not naive straight-line distance." A newnetwork.py(SegmentGraph) builds a segment-adjacency graph from the same polylines the pipeline already snaps reports to (two segments are adjacent when they share an endpoint — a real intersection — within the newgi_node_snap_mthreshold) and computes network-distance neighbors via a band-bounded Dijkstra;getis_ord_starnow takes a precomputed neighbor map instead of centroids and a distance band.tests/test_network.pyincludes the barrier fixture (two parallel, unconnected streets close in straight-line terms) asserting the network and Euclidean answers disagree and the network answer is what is published. Seedocs/ideation/02-large-scale-fixes.mdFIX-02. - EXP-01: publish-time null-calibration panel (
stats/calibration.py, #52). Every publish re-attacks the city's own dataset with seeded label-shuffles (exposure and geometry held fixed) and publishes the hotspot method's empirical false-positive rate alongside the map. - FIX-04: exposure trust tiers, corroboration, floor, and staleness (#53). Features carry
exposure_tier(observed/modeled/proxy/unknown) andexposure_disagreement;exposure_floorkeeps a near-zero denominator honest andexposure_staleflags an old exposure vintage. Dataset schema1.1.0(below). - FIX-06: per-hazard-type rate layers (#37).
rates_by_typegives each hazard type clearing the small-n threshold its own exposure-normalized rate + CI; the pooled top-level rate is labeled an explicit union (methods.rate_definition). - FIX-07: quality-tier sensitivity split (#38). The published PRIMARY rate excludes
low-confidence (
low_accuracy/far_snap) records;rate_sensitivity_deltareports when including them would move the rate outside its interval, andsummary.excluded_low_confidence_fractionpublishes the excluded share. - FIX-09: run manifest + pipeline-stage telemetry (#40).
publishdrops a gitignored<slug>.run.jsonprovenance manifest (input content hashes, stage timings, digest) next to every dataset;nearmiss.obsgains stage telemetry. - FIX-10: machine-readable dataset schema + contract gate (#41).
schema/dataset.schema.jsonmirrors the prose contract,publishvalidates every GeoJSON against it before writing, andweb/contract_check.mjsproves the browser consumer breaks when a required property is dropped. - FIX-13: single-sourced web i18n (#42). The web UI's strings come from the same gettext
catalogs as the brief (
web_i18n.pyregistry,tools/po2json.py->web/locales/<lang>.json);app.js's hand-maintained translation table is gone. - EXP-03: corridor-level aggregation (#55). Contiguous, same-street, independently significant
blocks merge into named corridors (
<slug>.corridors.geojson+ a brief corridor view), published alongside the block-level dataset with a MAUP transparency note; block features carry a nullablecorridor_id. - EXP-05: epsilon-DP segment×time-band prototype (#56), disabled by default and hard-gated on a
recorded privacy-SME sign-off (
dp_segment_timeconfig table;{"enabled": false}metadata for every existing config). - EXP-06: contributor data-rights tooling (#29).
nearmiss contributor export|delete|purge-expired(authorization = token possession, stated honestly) and aretention_dayswindow for the private raw store. - EXP-09: open planted-truth benchmark suite (#59).
benchmarks/ships six frozen synthetic regimes with known planted hotspots/decoys/bias traps, a scorer any hotspot tool can run, and nearmiss's own committed scorecards (benchmarks/SCORECARD.md— including where it is not perfect). - EXP-10: HR1–HR5 conformance verifier (#31).
tools/verify_dataset.pyaudits any published dataset against the five hard rules;make conformancegates every merge on it. - EXP-11: QGIS plugin with honest symbology (#57).
integrations/qgis/nearmiss_honestrenders the published dataset with rate-not-count symbology, CI + n in tooltips, and exposure-unknown never ranked; its PyQGIS-free rules are CI-tested. - EXP-13: locale scaling kit (#33). A build-only pseudo-locale gate (G9, no gettext bypass), an
RTL layout smoke test (G10), and a translate-only community runbook in
docs/I18N.md. - EXP-16: pre-registered prospective evaluation tooling (#60).
nearmiss preregisterfreezes flagged corridors to a hashed, timestamped registration with a pre-agreed scoring rule;nearmiss score-preregistrationscores it against later, held-out data (unevaluable ≠ miss). - R29/R34: per-city threshold-sensitivity + statistical-power notes (#45).
tools/sensitivity_note.pypublishes<city>-sensitivity.md(snapping/dedupe threshold grid and "how many reports until rankable" power notes) with every dataset. - Documentation audit and project-scope statement (
docs/DOCUMENTATION-AUDIT.md,docs/PROJECT-SCOPE.md, #61).
- Correct the national map's SVG coordinate transform so northern states render above southern states. Raw geographic projections now use one north-positive convention and the shared SVG fitter performs the single y-axis inversion; Alaska and Hawaii retain their correct orientation.
- Repository, package, citation, and public-page metadata now identify
https://nearmiss.chelseakr.comas the production site. Indexable HTML entry points carry consistent canonical, Open Graph URL, site identity, description, and summary-card metadata; iframe and not-found utility responses remain explicitly non-indexable without canonical URLs. - FIX-02 changes every published
getis_ord_z/getis_ord_significantvalue (a dataset content change, not a schema change) — see the per-citydataset_versionbump below. The EXP-09 benchmark scorecards are re-scored under the network weights; the reporting-bias regime honestly worsens (67% trap rate, 50% precision) andbenchmarks/SCORECARD.mddocuments why. - Pluggable
SourceAdapterframework (src/nearmiss/adapters/) with declarative TOML field crosswalks (src/nearmiss/adapters/crosswalks/), validated at load time against the intake schema's closed enums.tools/fetch_bikemaps.pyis migrated onto it, and the previously-orphaned SimRa (TU Berlin) adapter lands as its second implementation (tools/fetch_simra.py,make simra). Every adapter returns a per-sourceProvenanceblock (license, bias label, bias notes) alongside its reports; seedocs/REAL-DATA.md#source-adaptersanddocs/DATA-CARD.md#known-reporting-biases-who-is-over--and-under-represented. Adapter conformance is covered bytests/test_adapters_conformance.py. (EXP-04) src/honest_rates/(roadmap item EXP-08): the exposure-normalized rate (Byar/Wilson confidence intervals), Getis-Ord Gi* hotspot z-score with Benjamini-Hochberg FDR control, reporting-bias share comparison, and a planted-truth fixture harness are extracted into a standalone, dependency-free package with zero import ofnearmissanywhere in it — usable on any point-event dataset, not just this one.nearmiss/stats/rates.py,nearmiss/stats/getis_ord.py,nearmiss/stats/bias.py,nearmiss/spatial_index.py, and the shared parts ofnearmiss/geometry.pynow re-export it; nearmiss is its first consumer. Seesrc/honest_rates/README.mdandsrc/honest_rates/examples/potholes_demo.pyfor a non-traffic worked example. This makes the long-standing README "reusable on any point dataset" claim literally true rather than aspirational.
- No changes since
1.0.0.
1.1.0(MINOR, backward-compatible additive) — Exposure trust tiers, corroboration, an exposure floor, and a staleness flag (FIX-04,docs/ideation/02-large-scale-fixes.md).models.Exposuregainedtier(observed/modeled/proxy/unknown) and optional corroboratingsources;loaders.load_exposureaccepts both, defaulting older exposure rows totier="unknown"rather than silently promoting them. Published GeoJSON features gainedexposure_tierandexposure_disagreement(null unless corroborated by 2+ sources); thequality_flagsvocabulary gainedexposure_stale, raised when a feature'sexposure_dateis more than a configured threshold (exposure_stale_days) from the reports its rate is built from. A newexposure_floorconfig threshold treats a denominator at or below the floor asexposure_unknownrather than a giant, meaningless rate. All additions are optional/nullable; no existing field changed name, type, or meaning. Seeschema/dataset.schema.md§4.2/§4.6 anddocs/DATA-CARD.md. The same1.1.0version also carries the other backward-compatible feature-property additions that landed alongside it:rates_by_type(per-hazard-type rate layers, FIX-06) andrate_sensitivity_delta(quality-tier sensitivity split, FIX-07), both required, aggregate-only, and additive.- The same
1.1.0also adds the optional sidecar metadata fieldsegment_time_bands_dp(EXP-05 prototype): the epsilon-differential-privacy alternative to k-anonymity suppression for segment x part-of-day counts, described indocs/privacy/exp-05-dp-segment-time-bands.md.{"enabled": false}for every existing config — this ships the mechanism and its hard privacy-SME sign-off gate, not an enabled real-data release. Existing consumers see no change to any field they already read. metadata.methodsgained two new keys (getis_ord_neighbors,getis_ord_node_snap_m) — additive to the free-formmethodsprovenance object, not part of the versioned feature schema (FIX-02).
davisandriverside:0.1.0->0.1.1. Regenerated with FIX-02's network-topology Gi* weights (above); every feature'sgetis_ord_z/getis_ord_significantmay have changed relative to the prior0.1.0release, though the known-answer fixtures still recover the same planted hotspots.
Correction (2026-07-05): despite the heading and the prose below reading like a shipped release, this version has never been git-tagged, never had a GitHub Release cut, and nothing under it is signed.
pyproject.tomlcarriesversion = "0.1.0"and this section records that the work described below is implemented and verified onmainas of 2026-06-16 — but "released" in this entry means "specified and verified," not "tagged and published." Treat every "release" below as "milestone" until a realgit tag -s v0.1.0(or laterv0.1.x) exists.
A working analysis engine plus its specification and contracts, verified on main at this date.
This milestone covers the architecture, the two data-contract schemas, the full documentation set,
governance and community-health files, the CI and quality-gate scaffolding, and the implemented and
verified pipeline, statistics, publishing tooling, advocacy brief, read-only server, accessible web data
view, known-answer test fixtures, and the first published Davis demo dataset. A small set of items
remains specified-but-pending and is listed under Planned below.
The project ships as a dataset and analysis, not an app; the web view is a read-only window onto
the published data. The repository is now public (this line previously said "private during
pre-1.0 development," which stopped being true once the repo was made public; see README's status
badge). This milestone is labeled 0.1.0 / pre-1.0: the schemas are stable enough to build against
under the deprecation policy, but
the engine has so far been exercised only against the Davis demo (synthetic known-answer fixtures
plus one published demo corridor set), not calibrated against a breadth of real corridors; rate
magnitudes, exposure sources, and bias adjustments may still move between 0.1.x releases.
- Intake report schema
schema/report.schema.json(report.schema.jsonversion1.0.0, JSON Schema draft 2020-12). Defines a single incoming road-hazard / near-miss report with requiredschema_version,id(UUID, not derived from reporter identity),occurred_at(RFC 3339 with explicit offset),location,mode(e.g.cyclist),hazard_type(close-pass, door-zone, blind-corner,surface_hazard, and related categories), andseverity. The intake contract is designed to accept full submitted coordinate precision and an optional pseudonymous reporter token; such precise reports are specified as private and gitignored underdata/raw/and are never to be published as-is (HR4). - Published dataset schema
schema/dataset.schema.md— the human-readable published-dataset contract (mirrored by a JSON Schema validated in CI), including the versioning and deprecation policy. Establishes theFeatureCollectionwith versionedmetadata, per-feature rate / CI /n, per-feature exposure provenance, the additivequality_flagsvocabulary, the WGS84 / RFC 7946 geometry conventions, and the "guaranteed absent" privacy list. Note that the published dataset intentionally carries no per-reportmodefield (a quasi-identifier withheld for privacy).
The pure-typed-Python package in src/nearmiss/. Its only runtime dependency is jsonschema; it
uses a local equirectangular projection and pure-Python statistics rather than numpy/shapely/
pyproj (recorded in docs/adr/0003).
intake.py— validates each submission against the report schema before it lands in the private raw store, routing byschema_version. Intake attaches no denominators, rates, or intervals; those are computed downstream and never claimed at intake.pipeline/— pure, recorded transforms with plain, inspectable data between stages (dedupe,geocode,snap,classify,quality):- dedupe — collapses duplicate and near-duplicate submissions of the same event.
- geocode — resolves locations to coordinates. Note: today this stage is a pass-through for reports that already carry coordinates; real geocoder adapters for address-only imports are still Planned (below).
- snap-to-segment — snaps each report to a street segment, the unit of aggregation and exposure.
- classify — normalizes
hazard_typeandmodeinto the analysis vocabulary. - quality-flag — annotates reports with quality signals that carry through to per-feature
quality_flagsin the published dataset.
exposure.py— attaches an exposure denominator to each segment from documented, versioned sources, recordingexposure_sourceandexposure_dateper feature so a stale or swapped layer is visible, not silent. Segments with no available denominator are carried asexposure_unknown, not silently dropped (HR1).stats/rates.py— computes every risk figure as a rate normalized by exposure, never a raw count, and attaches a confidence interval and annto every published rate, ranking, and comparison: Byar's Poisson confidence intervals for rates and Wilson intervals for proportions. Small-sample segments are flaggedlow_sampleand shown as uncertain rather than ranked as certain (HR1, HR2).stats/bias.py— characterizes reporting bias as a first-class output: who is over- and under-represented, and what that does to the conclusions, stated plainly rather than hidden (HR3).stats/kde.py— kernel density estimation for a continuous report/risk surface, with the bandwidth and the smoothed quantity documented; a KDE of raw counts is labeled report volume, never danger.stats/getis_ord.py— Getis-Ord Gi* to identify statistically significant hot and cold spots, with the significance level and multiple-comparison correction stated, so "hotspot" means a tested cluster rather than a bright patch on a heat map.publish.py— emits the open artifacts: the aggregated open GeoJSON (full dataset-schema fields) with itssha256/methods/summary metadata sidecar, enforcing the privacy invariant described under Added — published dataset below (HR4, HR5).brief.py— generates advocacy briefs from the published dataset, carrying intervals and the bias caveats through to the prose so a brief cannot quietly overclaim.server.py— a read-only server over the published dataset.- Supporting modules:
config.py,geometry.py,models.py,loaders.py,validation.py,engine.py,util.py, anderrors.py. __main__.pyargparse CLI —nearmiss intake|pipeline|analyze|publish|brief|run|serve|version --config <cfg>. Thepipelinesubcommand accepts--dumpto emit intermediate clean records for debuggability.- Config-as-data —
config.pyloadsconfig/davis-demo.toml(cities, paths, thresholds, jitter).
- Synthetic known-answer fixtures committed under
tests/fixtures/davis/(generated bytools/make_fixtures.py): a planted hotspotseg-06(low exposure, rate20.0/1000, uniquely Getis-Ord-significant atz=3.26) and a busy decoyseg-03(the most raw reports,n=20, but a low rate of2.5that ranks 6th) — so the tests prove the engine recovers risk rate rather than report volume. - 27 pytest tests pass;
ruffis clean;mypy --strictis clean across 35 files.make demo,make verify(lint + type + test + accessibility + security),make reproduce(byte-for-byte deterministic; assertsgit diffis clean ondata/published/), andmake publishall run.
- A framework-free accessible web build in
web/(index.html,app.js,style.css): a supplementary SVG map alongside an authoritative sortable data table that is the non-visual equivalent of the map. Significance and confidence are stated in text, not color; the build has a skip link and semantic<th scope>headers. It passes the structural accessibility gatetools/a11y_check.py. The deeper axe + manual NVDA/VoiceOver audit remains Planned (below) and the ACR's manual criteria remain a conformance target.
data/published/davis.geojson— the first published open dataset, 12 street segments with the full published-dataset-schema fields (rate, CI,n,quality_flags, and per-feature exposure provenance; WGS84 / EPSG:4326 per RFC 7946), accompanied bydata/published/davis.metadata.json(sha256, methods, summary).- Privacy invariant enforced and tested (HR4): the public artifact carries no per-report
coordinate, time, reporter, mode, severity, or note, and small-
n(n < 5) hazard breakdowns are suppressed.
docs/audits/2026-06-16-verification.md— an audit artifact recording the verification of the engine, fixtures, gates, and published dataset on 2026-06-16.docs/adr/0003— the Architecture Decision Record for the pure-Python / planar-geometry decision (local equirectangular projection and pure-Python statistics;jsonschemaas the only runtime dependency).
README.md,CONTRIBUTING.md,SECURITY.md,NOTICE, and an Apache-2.0LICENSE.docs/METHODOLOGY.md(the intended approach to exposure, rates, intervals, bias, KDE, and Getis-Ord Gi*),docs/DATA-CARD.md,docs/THREAT-MODEL.md, anddocs/ACCESSIBILITY.md.docs/accessibility/ACR.md— a committed VPAT 2.5 (Rev 508) Accessibility Conformance Report. Its manual-review criteria remain a conformance target pending the deeper audit (see Planned); the shipped web view passes the structural gate today.docs/adr/— Architecture Decision Records, including0001-record-architecture-decisions.md,0002-exposure-normalization-and-confidence-intervals.md, and0003(pure-Python / planar-geometry decision).docs/audits/— the audit log directory, holding the2026-06-16-verification.mdverification record and established for further audits.
- Governance and community-health files:
CONTRIBUTING.md,SECURITY.md(disclosure process),.github/CODEOWNERS, and the conventional-commit and signed-release conventions that back this changelog.
- GitHub Actions CI (
.github/workflows/ci.yml) scaffolding that is designed to gate every change on the following jobs. Actions are pinned by version tag (e.g.@v4) and kept current by Dependabot; pinning to commit SHAs is a hardening goal, not a current fact. The jobs install the project withpip install -e ".[dev]"and run:- lint —
ruff(lint, import order, format check). Clean on the current tree. - type —
mypy --strict. Clean across 35 files. - test —
pytest. Runs against the committed synthetic fixtures with known answers (tests/fixtures/davis/): the planted hotspot is recovered by Getis-Ord Gi* and the busy decoy is correctly demoted; 27 tests pass. - accessibility —
tools/a11y_check.pyruns a structural gate on the web view today and passes. The deeper automatedaxerun and the manual NVDA/VoiceOver passes required perdocs/ACCESSIBILITY.mdare Planned (below) and tracked outside this structural gate. - security —
pip-audit --strict,gitleaks, andCodeQL.pip-auditandCodeQLneed network and run in CI; they are not yet exercised locally. - reproducibility —
make reproducerebuildsdata/publishedfrom inputs byte-for-byte deterministically and assertsgit diffis clean ondata/published/.
- lint —
.pre-commit-config.yaml— the pre-commit configuration wiring the local lint/type/format hooks.Makefile— defines the project gates and developer entry points, includingmake install(pip install -e ".[dev]", the working install today),make demo,make verify(lint + type + test + accessibility + security),make publish,make reproduce(byte-for-byte deterministic), andmake accessibility; plusmake lock(generates the reproduciblerequirements.lockviapip-compile --generate-hashes, whose output is not committed yet).- Dependency and supply-chain conventions: the working install is
pip install -e ".[dev]". A hashed lockfile,requirements.lock(generated bypip-compile --generate-hashes), is the planned reproducible-install artifact and is not committed yet.Dependabot(.github/dependabot.yml),.github/CODEOWNERS, conventional commits, semantic versioning, and signed releases round out the baseline.
These remain specified, not yet implemented. They are listed here so the design intent is recorded;
each will move to [Unreleased] (and then to a release) as it actually lands.
- Real geocoder adapters for address-only imports. The shipped
geocodestage is a pass-through for reports that already carry coordinates; resolving bare addresses against a documented, versioned reference is still to come. - More cities beyond the Davis demo, and broader real exposure layers (observed bike/ped counts or demand models for additional corridors) to replace the demo's exposure inputs.
- The deeper accessibility audit — an automated
axerun plus a manual NVDA/VoiceOver review. The committedtools/a11y_check.pygate is structural only; the ACR's manual criteria remain a conformance target until this audit is done. - Reproducible analysis notebooks —
notebooks/is still documentation only. - A committed hashed
requirements.lock— generated viamake lock(pip-compile --generate-hashes); not committed yet. - Performance benchmarking — the engine is not yet benchmarked.
- Established the supply-chain and secret-scanning baseline above (
pip-audit --strict,gitleaks,CodeQL, Dependabot, version-tag-pinned actions, signed releases) and the disclosure process inSECURITY.md.pip-auditandCodeQLneed network and run in CI only; they are not yet exercised locally. The committed hashed lock artifact (requirements.lock) remains Planned, not yet active. - Privacy-by-construction is now enforced and tested. Precise reports stay private and gitignored,
and
publish.py's privacy invariant keeps the published artifact free of any per-report coordinate, time, reporter, mode, severity, or note, and suppresses small-n(n < 5) hazard breakdowns; tests assert the "guaranteed absent" list (HR4).
1.0.0— initial published intake contract. Establishes the required fields, the full-precision-private / aggregated-public split, and the additive vocabularies forhazard_type,mode, andseverity.
1.0.0— initial published dataset contract. Establishes theFeatureCollectionwith versionedmetadata, per-feature rate / CI /n, per-feature exposure provenance, the additivequality_flagsvocabulary, the WGS84 / RFC 7946 geometry conventions, and the "guaranteed absent" privacy list.
The five schemas are versioned independently of the software and of each other; the canonical statement
lives in schema/dataset.schema.md.
Summary:
- PATCH — clarifications, doc fixes, added examples, non-semantic corrections. No consumer action.
- MINOR — backward-compatible additions only: a new optional property, a new
quality_flagsvalue, a newexposure_sourceidentifier, a new permittedhazard_breakdownkey. Existing properties keep their name, type, and meaning; flag and hazard vocabularies are additive and never silently repurposed. A consumer written for an earlier1.xkeeps working and should ignore anything it does not recognize. - MAJOR — a breaking change: removing or renaming a property, adding a newly-required field, changing a type or unit, changing the meaning of a field, changing the default CI level or the significance / correction method in a way that alters how published numbers read, or changing the geometry/CRS conventions.
Deprecation. A field slated for removal or change is marked deprecated in the schema doc and the data card, with the target removal version and migration path, at least one MINOR release before the breaking MAJOR; where feasible the deprecated field is kept alongside its replacement during the window. Every schema change is recorded under the schema subsections above with version, date, and rationale, and triggers a review of the threat model and data card. Published files are immutable, hashed, and signed; older artifacts are never rewritten in place, so a consumer can always verify exactly which schema version and which build a file conforms to.