Mode-3 case schema + UK battery: UKMOD/EUROMOD lane groundwork - #49
Mode-3 case schema + UK battery: UKMOD/EUROMOD lane groundwork#49vahid-ahmadi wants to merge 3 commits into
Conversation
|
Dual-gate review: NEEDS-FIXES. Blockers: (1) arbitrary misses can be blessed — |
|
All three blockers fixed in 838cc45.
Tests: Note: the battery now spans policy years {2025, 2026} — |
Review — Mode-3 case schema + UK batteryDisciplined contract PR. The closed vocabularies are enforced in code, not just SCHEMA.md prose: unknown oracle / classification / country / person-key / household-key / case-key all raise ( Should address
Suggestions
Reviewed with Claude Code assistance. |
|
All four review items addressed in a1d57d7:
Suite: 176 passed / 4 skipped; ruff clean. |
Re-review — all addressed ✅Went back through my prior notes against the new commits; everything's resolved:
Nice — no remaining concerns from my side. |
DTrim99
left a comment
There was a problem hiding this comment.
Re-reviewed — all prior findings are addressed with tests (the classify→CaseResult from_classification seam, variable_class persisted, schema_version, and real-date date_of_birth validation). Approving.
Groundwork for #41, designed as the single case-level schema mode 3 shares with the TAXSIM lane (#5) — country on the case and oracle on the result row keep it engine-agnostic. - sources/ukmod-cases/SCHEMA.md: the case-diff contract — engine-neutral household input vocabulary, result-row shape, the closed fail-loud classification set (match_exact / match_within_tolerance / pe_gap / oracle_difference / policy_scope_mismatch / rounding / unclassified), and per-variable-class tolerances (currency GBP 0.01/week = 0.52/year; booleans exact). - sources/ukmod-cases/battery/cases.json: 14 curated UK hypothetical households, inputs + expected focus only (no fabricated expected values): UC standard/housing, lone parent + LHA, two-child limit with the pre-April-2017 protection, London benefit cap, taper + work allowance, minimum income floor, Pension Credit single/couple, mixed-age couple routed to UC, Scottish bands + Scottish Child Payment, HICBC mid-taper, NI threshold edges with salary sacrifice, carer CA/UC interaction, and the GBP 100k personal-allowance taper. - scorecard_db/case_diffs.py: CaseSpec/CaseResult dataclasses (closed enums raise on unknown values, models.py doctrine), battery loader, and the classify() first-pass classifier (above tolerance defaults to unclassified — adjudication, never a flattering bucket). - tests/test_case_schema.py: battery validation, unique ids, inputs-only invariant, classifier and result-row edge cases. - data/lanes.json (+ app mirror): ukmod-cases registered -> cataloged; connector run pending the UKMOD environment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hild-limit cases Three review blockers from the dual-gate pass: 1. Adjudicated misses need their paperwork: CaseResult now requires the judged-against tolerance on match_within_tolerance rows (0 < abs_diff <= tolerance, tolerance stored so table changes never re-bless old rows) and a non-empty annotations writeup on the adjudicated-only classes oracle_difference / rounding (ADJUDICATED_ONLY); tolerance is forbidden elsewhere and annotations must be a list of non-empty strings. 2. Closed-schema list types: benefit-unit adults/children must be lists of person-id strings and expected_focus must be a list — bare strings no longer validate by iterating as characters. 3. The two-child-limit case is split into an honest pair: a policy-year-2025 multiple-birth case where the exemption sits on the third child and strictly changes entitlement (the limit was abolished 6 April 2026, so only a pre-abolition year can exercise it), plus a 2026 abolition counterpart of the same family where an engine still applying the limit under-pays one element. SCHEMA.md documents the tightened contract; tests extended to 37 in test_case_schema.py (suite 164 passed / 4 skipped). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ma_version, real-date DOB validation Review items from the 2026-08-18 pass: - CaseResult.from_classification() is the one wiring path from classify to a stored row: derives abs_diff, threads the exact tolerance classify judged against, persists variable_class. Six end-to-end seam tests. - variable_class persisted on CaseResult (closed vocabulary) so stored rows are auditable without inferring the rule from the variable name. - SCHEMA_VERSION = 1 on the battery file and every result row; a mismatched or missing version raises, so breaking changes migrate explicitly. - date_of_birth must be a real calendar date (2026-13-40 now fails). Suite: 176 passed, 4 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
a1d57d7 to
8adbc93
Compare
Part of #41 — the schema/battery half of the UKMOD/EUROMOD mode-3 lane. Designed per #5 as the single case-level schema shared with the TAXSIM lane:
countryon the case andoracle(ukmod|taxsim) on the result row keep it engine-agnostic, so mode 3 gets one schema, not two.What's here
sources/ukmod-cases/SCHEMA.md— the case-diff contract. A case ={case_id, description, policy_year, country, household, expected_focus, rationale}with an engine-neutral, closed household input vocabulary (people with ages/incomes/disability + carer flags, benefit-unit structure, region/tenure/rent) that each lane's connector maps to its engines' variables. A result row ={case_id, variable, pe_value, oracle_value, oracle, engine_version, oracle_version, computed_at, abs_diff, classification}. Classification is a fail-loud closed set —match_exact / match_within_tolerance / pe_gap / oracle_difference / policy_scope_mismatch / rounding / unclassified— withoracle_differenceandroundingreachable only by adjudication with a writeup. Tolerances per variable class: currency £0.01/week (0.52/year annual comparisons), booleans exact.sources/ukmod-cases/battery/cases.json— 14 curated UK hypothetical households, inputs + expected focus only (no fabricated expected values; both sides of every comparison come from engine runs later). Coverage: single unemployed on UC; lone parent + 2 children with LHA rent; two-child limit binding with a pre-April-2017 protected child; London benefit cap; 55% taper + work allowance; minimum income floor; Pension Credit single + couple; mixed-age couple routed to UC; Scottish bands + Scottish Child Payment; HICBC at the £70k mid-taper; NI threshold edges (£12,570 / £50,270) with salary sacrifice; carer CA + UC carer element; £100k personal-allowance taper. Round, auditable inputs with a rationale per case.scorecard_db/case_diffs.py—CaseSpec/CaseResultdataclasses in themodels.pyfail-loud style (closed enums raise on unknown values; benefit units must partition people; owner-occupiers carry no rent;abs_diffmust reconcile), the battery loader, andclassify()— the first-pass classifier whose above-tolerance default isunclassified, never a flattering bucket.tests/test_case_schema.py— battery loads/validates, unique country-prefixed ids, inputs-only invariant, focus coverage, classifier and result-row edge cases (28 tests).data/lanes.json(+app/public/datamirror) —ukmod-casesadvancedregistered → cataloged.Out of scope (remaining for #41)
The JRC connector run itself (needs the UKMOD environment): vocabulary→UKMOD input mapping, executing the battery on both engines, appending
CaseResultrows, and publishing the miss table.Verification
PYTHONPATH=. uv run --with pytest pytest tests/ -q— 156 passed, 4 skippeduvx ruff format --check scorecard_db tests pipeline— cleandata/lanes.jsonregenerated with the feed's exact serialization; app mirror byte-identical🤖 Generated with Claude Code