Frozen 2025 onsite–town descriptive comparison protocol

Adopted 5 September 2026 before computing annual or monthly paired concentrations. The attached annual-protocol-review.md is normative in full; the rules below fix implementation details.

Scope: all 365 labelled 2025 dates, three metrics (PM25 daily mean in ug/m3, NO2 daily mean in ppb, and NO2 daily maximum hourly average in ppb), shifts -1/0/+1 hours, RAW_VALUE and ROUNDED_VALUE. Produce 6,570 rows, including missing/ineligible rows, 2,190 distinct town pollutant windows, 234 annual/monthly summaries, 234 common-date clock diagnostics and 39 primary precision diagnostics. Periods are 2025 and 2025-01 through 2025-12. Do not calculate concentration ratios, calibration factors, source attribution, significance or objective attainment. No observed values will be filled, clipped or excluded for smoke. The May frozen analysis remains unchanged.

Inputs: use original per-month Table B.1 CSV transcriptions mapped by exact field headers, with PDFs, native JSON and source-context files frozen alongside them. The independently checked consolidated annual CSV is not the calculation input. Five pollution columns occur before November; PM1's addition does not change PM25's mapping. Fields: pm25_24h_ug_m3, no2_24h_ppb, no2_daily_max_1h_ppb. Accept exact finite decimal tokens, printed dash plus optional a-z footnote, or censored <number; retain explicit_missing/censored/not_reported separately and halt on unexpected tokens/conflicting dates. Preserve the source token, footnote letter, PDF and printed page, row, field/column, URL, report month, PDF/CSV hash. All original dates and field headers must be present; unexpected missing rows halt. Non-numeric site cells have null numeric value.

Town: original verified 2024 and 2025 station archives, E304570/NAPS101603, PM25 ug/m3 and NO2 ppb. Native DATE and TIME are interval-ending hours 1–24; start=DATE+(ending hour-1), end=start+1h; DATE_PST must equal the reconstructed end. Reject duplicates, unexpected absent rows inside acquired years, unit/station mismatches, malformed or nonfinite numeric tokens; NA is observed missingness. Raw and rounded availability must agree. Check complete 2024/2025 hourly calendars for both pollutants. All 24 expected hourly records are retained in lineage with file/line/native identifiers, tokens, instruments, interval bounds and fixed-PST/UTC window bounds.

Primary window is [label 00:00, next label 00:00) in fixed PST UTC-8. Each diagnostic shifts the entire 24-hour window, not a reconstruction of daylight-saving civil days. January 1 -1h uses verified 2024 December 31 hour24. December 31 +1h includes expected 2026 January1 hour1, marked outside_acquired_verified_coverage; its town aggregate and pair are null. All other windows require 24 present source rows. At least18 original numeric town hours are also required. Record town_source_rows, town_valid_hours, source_window_available, town_eligible, onsite_numeric, pair_eligible separately. NA rows count as source rows. Calculate town aggregate only when both town gates pass, even if onsite missing. Contrast=site-town only for eligible pair. Exclusion reasons are cumulative, ordered: onsite_ if not numeric; outside_acquired_verified_coverage if structural gate fails; town_fewer_than_18_numeric_hours if numeric count<18. No extra exclusion reason is introduced for an NA hour when daily gates pass.

Arithmetic: Python Decimal precision50, values stored as decimal strings with no rounding until display. Aggregate observed numeric town hours using arithmetic mean or maximum. Each summary pools eligible daily pairs directly for its period. Equal daily weight, including when numeric hour counts differ. Store expected/pair counts, included/excluded date lists and all exclusion reasons/counts, means of paired onsite/town values, mean/median/mean absolute contrast, min/max and all tied dates, positive/negative/zero signs and strict >0.1 or <-0.1 counts, minimum/maximum/total town numeric hours among pairs. These are matched-day summaries, not hourly annual means; NO2 maximum means remain averages of daily maxima. Empty means/extrema/hour ranges are null; empty date lists remain empty and counts zero.

Clock diagnostic: intersection of pair-eligible dates across all3 shifts for each metric/stream/period; use that common mask for every shift. Retain ordinary primary mean separately and primary mean on common dates. Report common dates/count, mean contrast and mean town on common dates, maximum absolute town difference versus primary and every tied date. Precision diagnostic: raw/rounded primary-window eligible intersection per metric/period; report mean rounded-minus-raw town value, max absolute difference and every tied date. No favourable-shift selection. Plot gaps remain gaps; any clock envelope exists only on dates with all3 shifts eligible.

Verification: independent calculation from frozen native town archives and original monthly CSVs, without producer outputs/code before independent output freeze. Compare all paired values/gates/provenance, summaries, masks, missingness, clock and precision values, all year/month boundaries and tied dates. Numeric tolerance at most1e-40 for Decimal operation-order rounding; report exact equality separately. Cross-check the complete May subset against its existing558 frozen records using equivalent fields. Public findings require successful independent review. Source-specific Level0 QA, possible instrument changes, missing site hourly counts and unconfirmed daily clock remain in interpretation; do not assert representativeness or plume validation.