Skip to content

Commit f5e6493

Browse files
Add ENR take-up model, remove CTR, add OBR validation test
- ENR (entitled non-recipient) flags computed at microdata extraction time via a baseline simulation pass in write_clean_csvs; baked into clean CSVs - Three-way take-up: reported receipt → always receives; ENR → full take-up rate; new entrant → new_entrant_rate (default 0.3) - Remove CTR (council tax reduction) national modelling entirely — too many local schemes to model; taken as reported (zero in model) - Remove CTR from parameters, simulation outputs, API, and frontend - OBR validation test runs by default, skips gracefully if data/frs_clean absent (e.g. CI); checks ±generous tolerances on key 2025/26 aggregates - Modal: add eu-west-1 region, fix concurrent inputs decorator order, fix FRS volume upload to store at volume root
1 parent 73df376 commit f5e6493

19 files changed

Lines changed: 318 additions & 241 deletions

api/modal_app.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,19 @@ def run_simulation(year: int, reform_json: Optional[str] = None) -> dict:
102102
try:
103103
result = subprocess.run(
104104
cmd, capture_output=True, text=True, timeout=120,
105-
env={**os.environ, "PARAMETERS_DIR": PARAMETERS_DIR},
105+
cwd="/app", # binary resolves parameters/ relative to cwd
106106
)
107107
except subprocess.TimeoutExpired:
108108
raise HTTPException(504, detail="Simulation timed out")
109109
if result.returncode != 0:
110-
raise HTTPException(500, detail=f"Simulation failed: {result.stderr[:2000]}")
110+
raise HTTPException(500, detail=f"Simulation failed: cmd={cmd} stderr={result.stderr[:2000]}")
111111
return json.loads(result.stdout)
112112

113113
def get_baseline_params(year: int) -> dict:
114114
result = subprocess.run(
115115
[RUST_BINARY, "--year", str(year), "--export-params-json"],
116116
capture_output=True, text=True, timeout=10,
117+
cwd="/app",
117118
)
118119
if result.returncode != 0:
119120
raise HTTPException(500, detail=f"Failed to load params: {result.stderr[:500]}")
@@ -132,7 +133,7 @@ async def cache_baselines():
132133
REFORM_SECTIONS = [
133134
"income_tax", "national_insurance", "universal_credit",
134135
"child_benefit", "benefit_cap", "housing_benefit",
135-
"tax_credits", "council_tax_reduction", "scottish_child_payment",
136+
"tax_credits", "scottish_child_payment",
136137
"pension_credit", "state_pension",
137138
]
138139

@@ -145,7 +146,6 @@ class SimulateRequest(BaseModel):
145146
benefit_cap: Optional[dict[str, Any]] = None
146147
housing_benefit: Optional[dict[str, Any]] = None
147148
tax_credits: Optional[dict[str, Any]] = None
148-
council_tax_reduction: Optional[dict[str, Any]] = None
149149
scottish_child_payment: Optional[dict[str, Any]] = None
150150
pension_credit: Optional[dict[str, Any]] = None
151151
state_pension: Optional[dict[str, Any]] = None
@@ -159,7 +159,6 @@ class SimulateMultiYearRequest(BaseModel):
159159
benefit_cap: Optional[dict[str, Any]] = None
160160
housing_benefit: Optional[dict[str, Any]] = None
161161
tax_credits: Optional[dict[str, Any]] = None
162-
council_tax_reduction: Optional[dict[str, Any]] = None
163162
scottish_child_payment: Optional[dict[str, Any]] = None
164163
pension_credit: Optional[dict[str, Any]] = None
165164
state_pension: Optional[dict[str, Any]] = None
@@ -232,10 +231,11 @@ async def health():
232231
# Startup caches all 7 year baselines — give it enough RAM
233232
memory=4096,
234233
cpu=4,
235-
# Keep one container warm so the dashboard is snappy
236-
keep_warm=1,
237234
timeout=300,
235+
# EU West (Ireland) for lower latency from UK callers
236+
region="eu-west-1",
238237
)
238+
@modal.concurrent(max_inputs=10)
239239
@modal.asgi_app()
240240
def api():
241241
return _make_fastapi_app()

api/upload_frs.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ def upload(local_dir: Path) -> None:
2727

2828
volume = modal.Volume.from_name(FRS_VOLUME_NAME, create_if_missing=True)
2929

30-
print(f"Uploading FRS clean CSVs from {local_dir} → Modal Volume '{FRS_VOLUME_NAME}':{REMOTE_PATH}")
30+
# Files are stored at the volume root (e.g. "persons.csv").
31+
# The volume is mounted at FRS_MOUNT_PATH in the container, so
32+
# container path /data/frs_clean/persons.csv == volume root persons.csv.
33+
print(f"Uploading FRS clean CSVs from {local_dir} → Modal Volume '{FRS_VOLUME_NAME}' (root)")
3134
with volume.batch_upload(force=True) as batch:
3235
for csv_file in local_dir.glob("*.csv"):
33-
remote = f"{REMOTE_PATH}/{csv_file.name}"
34-
batch.put_file(str(csv_file), remote)
35-
print(f" {csv_file.name}{remote}")
36+
batch.put_file(str(csv_file), csv_file.name)
37+
print(f" {csv_file.name} → /{csv_file.name}")
3638

3739
print("Done. Volume contents:")
38-
for entry in volume.listdir(REMOTE_PATH):
40+
for entry in volume.listdir("/"):
3941
print(f" {entry.path}")
4042

4143

app/lib/constants.ts

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -863,68 +863,6 @@ export const SLIDERS: SliderConfig[] = [
863863
format: "percent",
864864
},
865865

866-
// ── Council Tax Reduction ───────────────────────────────────
867-
{
868-
key: "ctr_taper_rate",
869-
label: "Taper Rate",
870-
section: "Council Tax Reduction",
871-
path: ["council_tax_reduction", "taper_rate"],
872-
min: 0,
873-
max: 1,
874-
step: 0.01,
875-
format: "percent",
876-
},
877-
{
878-
key: "ctr_pa_single_under25",
879-
label: "Personal Allowance (single <25, weekly)",
880-
section: "Council Tax Reduction",
881-
path: ["council_tax_reduction", "personal_allowance_single_under25"],
882-
min: 0,
883-
max: 150,
884-
step: 1,
885-
format: "currency",
886-
},
887-
{
888-
key: "ctr_pa_single_25plus",
889-
label: "Personal Allowance (single 25+, weekly)",
890-
section: "Council Tax Reduction",
891-
path: ["council_tax_reduction", "personal_allowance_single_25_plus"],
892-
min: 0,
893-
max: 200,
894-
step: 1,
895-
format: "currency",
896-
},
897-
{
898-
key: "ctr_pa_couple",
899-
label: "Personal Allowance (couple, weekly)",
900-
section: "Council Tax Reduction",
901-
path: ["council_tax_reduction", "personal_allowance_couple"],
902-
min: 0,
903-
max: 300,
904-
step: 1,
905-
format: "currency",
906-
},
907-
{
908-
key: "ctr_child_allowance",
909-
label: "Child Allowance (weekly)",
910-
section: "Council Tax Reduction",
911-
path: ["council_tax_reduction", "child_allowance"],
912-
min: 0,
913-
max: 200,
914-
step: 1,
915-
format: "currency",
916-
},
917-
{
918-
key: "ctr_family_premium",
919-
label: "Family Premium (weekly)",
920-
section: "Council Tax Reduction",
921-
path: ["council_tax_reduction", "family_premium"],
922-
min: 0,
923-
max: 50,
924-
step: 0.5,
925-
format: "currency",
926-
},
927-
928866
// ── Scottish Child Payment ──────────────────────────────────
929867
{
930868
key: "scp_weekly_amount",
@@ -959,7 +897,6 @@ export const SECTIONS = [
959897
"Benefit Cap",
960898
"Housing Benefit",
961899
"Tax Credits",
962-
"Council Tax Reduction",
963900
"Scottish Child Payment",
964901
];
965902

parameters/2023_24.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,6 @@ tax_credits:
100100
wtc_min_hours_single: 30.0
101101
wtc_min_hours_couple: 24.0
102102

103-
council_tax_reduction:
104-
taper_rate: 0.20
105-
personal_allowance_single_under25: 67.25
106-
personal_allowance_single_25_plus: 84.80
107-
personal_allowance_couple: 133.30
108-
child_allowance: 77.78
109-
family_premium: 17.85
110103

111104
scottish_child_payment:
112105
weekly_amount: 25.00

parameters/2024_25.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,6 @@ tax_credits:
102102
wtc_min_hours_single: 30.0
103103
wtc_min_hours_couple: 24.0
104104

105-
council_tax_reduction:
106-
taper_rate: 0.20
107-
personal_allowance_single_under25: 71.70
108-
personal_allowance_single_25_plus: 90.50
109-
personal_allowance_couple: 142.25
110-
child_allowance: 83.73
111-
family_premium: 18.53
112105

113106
scottish_child_payment:
114107
weekly_amount: 26.70

parameters/2025_26.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,6 @@ tax_credits:
153153
wtc_min_hours_single: 30.0
154154
wtc_min_hours_couple: 24.0
155155

156-
council_tax_reduction:
157-
# Council Tax Reduction Schemes (Prescribed Requirements) (England) Regs 2012
158-
# SI 2012/2885
159-
taper_rate: 0.20
160-
personal_allowance_single_under25: 71.70
161-
personal_allowance_single_25_plus: 90.50
162-
personal_allowance_couple: 142.25
163-
child_allowance: 83.73
164-
family_premium: 18.53
165156

166157
scottish_child_payment:
167158
# Social Security (Scotland) Act 2018; SSI 2020/351

parameters/2026_27.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,6 @@ tax_credits:
103103
wtc_min_hours_single: 30.0
104104
wtc_min_hours_couple: 24.0
105105

106-
council_tax_reduction:
107-
taper_rate: 0.20
108-
personal_allowance_single_under25: 71.70
109-
personal_allowance_single_25_plus: 90.50
110-
personal_allowance_couple: 142.25
111-
child_allowance: 83.73
112-
family_premium: 18.53
113106

114107
scottish_child_payment:
115108
weekly_amount: 26.70

parameters/2027_28.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,6 @@ tax_credits:
101101
wtc_min_hours_single: 30.0
102102
wtc_min_hours_couple: 24.0
103103

104-
council_tax_reduction:
105-
taper_rate: 0.20
106-
personal_allowance_single_under25: 71.70
107-
personal_allowance_single_25_plus: 90.50
108-
personal_allowance_couple: 142.25
109-
child_allowance: 83.73
110-
family_premium: 18.53
111104

112105
scottish_child_payment:
113106
weekly_amount: 26.70

parameters/2028_29.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,6 @@ tax_credits:
102102
wtc_min_hours_single: 30.0
103103
wtc_min_hours_couple: 24.0
104104

105-
council_tax_reduction:
106-
taper_rate: 0.20
107-
personal_allowance_single_under25: 71.70
108-
personal_allowance_single_25_plus: 90.50
109-
personal_allowance_couple: 142.25
110-
child_allowance: 83.73
111-
family_premium: 18.53
112105

113106
scottish_child_payment:
114107
weekly_amount: 26.70

parameters/2029_30.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,6 @@ tax_credits:
101101
wtc_min_hours_single: 30.0
102102
wtc_min_hours_couple: 24.0
103103

104-
council_tax_reduction:
105-
taper_rate: 0.20
106-
personal_allowance_single_under25: 71.70
107-
personal_allowance_single_25_plus: 90.50
108-
personal_allowance_couple: 142.25
109-
child_allowance: 83.73
110-
family_premium: 18.53
111104

112105
scottish_child_payment:
113106
weekly_amount: 26.70

0 commit comments

Comments
 (0)