Skip to content

Add an intensive-margin hours response for parents in work - #8

Closed
vahid-ahmadi wants to merge 7 commits into
subsidy-take-up-flagfrom
intensive-margin
Closed

vahid-ahmadi wants to merge 7 commits into
subsidy-take-up-flagfrom
intensive-margin

Conversation

@vahid-ahmadi

@vahid-ahmadi vahid-ahmadi commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Closes #6. Stacked on #7.

Max asked for an intensive-margin response using a −0.042 childcare price elasticity of hours worked, derived from Brewer et al. (2020). This adds it.

What it does. For each adult in the responsive population (youngest child 1-4, not excluded by the OBR framework) who is in work with a positive out-of-pocket childcare cost, the proportional change in that cost is multiplied by the elasticity to give a change in hours, and earnings move with hours at a constant wage. Out-of-pocket cost is childcare expenses less Tax-Free Childcare (or the subsidy), less the paid care that new free hours displace, less the realised Universal Credit childcare support — the UC award minus the award with the element's coverage rate set to zero, the same counterfactual the baseline benchmark uses, since the element's face value is mostly withdrawn by the taper for working families. The revenue is not an assumed rate: each leg is rerun with the changed earnings and the revenue is the extra earnings less the rise in household net income. The household gain is allocated back to the adults whose hours produced it and laid over the distribution alongside the participation response, so the Household effects view moves with it. Low and high scale both margins by the same multiplier.

Results, 2027-28 central, UK. Parents in scope (1.26m; 1.02m see a price change) face a 58% mean fall in out-of-pocket cost under both legs together, giving +2.4% hours: 30,565 FTEs, £1.67bn of earnings, £0.67bn back to the exchequer, against £0.02bn from the participation margin. Combined dynamic cost £6.64bn → £5.95bn (10.4% offset; 3.2% low, 21.1% high). Free hours alone: 2,073 FTEs, £0.07bn (only displaced paid care moves the price). Subsidy alone: 29,401 FTEs, £0.63bn. UC families sit in scope with a near-zero price change, because the subsidy leg pays them nothing.

Caveats, recorded in sources.py, the README and on the dashboard. I checked the derivation against the paper. The +0.600 is Table A.3 panel B (file page 41) and is measured over all mothers with zeros for non-workers; 14.319 is Table 1's all-mothers mean (file page 17). So −0.042 is a total-hours elasticity that already contains the participation channel — the paper's own +3.5pp employment effect at part-time hours accounts for most of the +0.6 — and adding it to the extensive margin counts that channel twice. The treatment was also 12.5 extra free hours a week (file page 8), not a 100% price fall. The dashboard says both.

Also in this PR. _build_with_earnings has to call reset_calculations() before set_input: employment_income is a derived variable upstream, and the other order silently left earnings unchanged (revenue came out equal to earnings). Three CostTab strings used --x, a JavaScript decrement that mutated the loaded results by £1 and flipped the displayed sign; now -x. The extended-flag comparison from #7 now compares static costs, since that scenario does not carry the hours margin. Methodology tab, assumptions.margin, the module docstring and the CLI description all now describe both margins.

Tests. Eight unit tests on a stub simulation (price change, rerun earnings, revenue, household allocation for one and two earners, country reconciliation, scale, no-change, the 100% bound, and a tapered-UC family whose price is measured on realised support rather than the element) and an artifact gate that the dynamic cost equals static less both margins, each margin's country split reconciles, and revenue sits strictly between zero and earnings. dynamic_cost.labour_supply_offset_bn keeps its meaning (participation only); hours_offset_bn and total_offset_bn are added beside it.

66 tests pass. Run time is up by about 16 simulations a year for the reruns and UC counterfactuals.

@vercel

vercel Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
free-childcare-reform Ready Ready Preview Sep 2, 2026 11:55am UTC

Request Review

@juaristi22

Copy link
Copy Markdown
Collaborator

Program Review

Source Documents

Critical (Must Fix)

  1. [C1] The hours response treats a maximum UC element as cash received. hours_response.py:57-61 subtracts the full uc_childcare_element from childcare cost. The repository itself correctly documents at sources.py:239-253 that this variable is a component of the UC maximum before the earnings taper and that most of its face value never reaches households. For tapered working families, the code therefore understates the baseline out-of-pocket price and misstates the free-hours and combined price changes, hours, earnings, and revenue. Compute realized incremental UC support - for example, household UC with the childcare element minus a zero-coverage counterfactual - and add a tapered-UC regression test.

Should Address

  1. [A1] Public methodology still says no intensive margin exists. sources.py:498-503 emits "extensive ... only"; MethodologyTab.jsx:137-146 says only the extensive margin is modelled; and MethodologyTab.jsx:213-218 says hours changes are not modelled. Update these and the participation-only Bettendorf description so the methodology, machine-readable assumptions, and results agree.
  2. [A2] The elasticity source links to the cover, not the evidence pages. sources.py:468-476 names Table 1 and Table A.3 but provides no file-page fragments. Add direct references to file page 17, file page 41, and file page 8 for the treatment caveat.

Suggestions

  1. [S1] Add a two-earner allocation test. The current stub proves allocation for one responding adult. A two-earner household would cover proportional allocation and country reconciliation when both adults change hours.

PDF Audit Summary

Category Count
Confirmed correct 3 inputs/caveats
Mismatches 0
Mismatches rejected 0
Unmodeled items 1 pure intensive-margin estimate (correctly disclosed)

The paper confirms 12.5 additional weekly free hours on file page 8, the 14.319 final-sample mean on file page 17, and the 0.600 hours effect plus zero-for-nonworkers note on file page 41. The -0.042 arithmetic is accurate. Issue #6 explicitly requests the caveated, overlapping sensitivity and the Cost tab discloses it, so the known overlap is not treated as an implementation defect.

Validation Summary

Check Result
Methodological source Values and caveats corroborated
Reference quality Correct source, missing direct page links
Code patterns 1 incorrect realized-support calculation
Documentation 1 stale methodology block
Test coverage Missing tapered-UC case; core mechanics otherwise covered
Python tests 63 passed
Ruff Passed
CI status Passing
Branch status 1 ahead, 0 behind PR #7 head

Review Severity: REQUEST_CHANGES

Next Steps

Replace the UC maximum-element subtraction with realized incremental UC support, add the tapered-UC regression, update stale methodology text and page-specific references, then regenerate artifacts. Fixes can be applied with the fix-pr workflow.

…hodology up to date

Addresses María's review of #8: C1 (the UC childcare element's face value is
mostly withdrawn by the taper, so realised support is the award difference
against an abolition counterfactual), A1 (stale extensive-only text in the
methodology tab and machine-readable assumptions), A2 (file-page references
for the Brewer et al. figures) and S1 (a two-earner allocation test).
@vahid-ahmadi

Copy link
Copy Markdown
Contributor Author

Addressed in d1af0a9 (rebased onto #7's fix first).

C1 — realised UC support. You're right; I had subtracted the face value of a maximum-amount component. realised_uc_childcare_support now takes the UC award with the element minus the award with the coverage rate set to zero — the same counterfactual the baseline benchmark uses — per benefit unit, on both the baseline and each reform run (four extra simulations a year). The price is then expenses − TFC/subsidy − realised UC support. Tapered-UC regression added: a family with a £6,800 element but a £1,200 award difference pays £5,200, not £0, and the price change is measured on that.

Effect on the results: the mean price fall for parents in scope drops from 79% to 58%, because UC families now carry a real out-of-pocket base and the subsidy leg pays them nothing, so their price change is close to zero. Central 2027-28: 30,565 FTEs (was 41,448), £1.67bn earnings, £0.67bn revenue (was £0.90bn), combined dynamic cost £5.95bn (was £5.72bn). Offset share 3.2% / 10.4% / 21.1% low/central/high. README and dashboard figures updated.

A1 — MethodologyTab no longer says the intensive margin is unmodelled; it has its own bullet describing the elasticity, the realised-UC price, the rerun, and the overlap with participation. The Bettendorf sentence is reframed as the same overlap point. assumptions.margin now says both margins and notes the overlap.

A2 — sources.py cites file pages 41 (Table A.3), 17 (Table 1) and 8 (12.5-hour treatment); the source URL now carries #page=41.

S1 — two-earner test added: both adults respond, the household gain is allocated 35:15 by extra earnings, totals and the England split reconcile.

66 tests pass.

@juaristi22

Copy link
Copy Markdown
Collaborator

Program Review

Source Documents

Prior Finding Status

  • [C1] RESOLVED. The hours response now subtracts realised UC childcare support: the UC award difference against a zero-coverage counterfactual. The counterfactual is built for the baseline and each reform leg, including displaced expenses, and a tapered-family regression covers the distinction.
  • [A1] STILL OPEN (narrow remainder). The dashboard methodology and machine-readable assumptions are fixed, but cli.py:15 and pipeline.py:12-14 still describe only an extensive-margin response. Say “both participation and hours margins” (or equivalent).
  • [A2] RESOLVED. The source metadata identifies file pages 8, 17, and 41 and links directly to page 41, where the primary effect is reported.
  • [S1] RESOLVED. The new two-earner test checks proportional allocation and UK/country reconciliation.

Critical (Must Fix)

None.

Should Address

  1. [A1] Finish updating the two stale overview strings. These do not affect results, but they contradict the now-correct public methodology and CLI behavior.
  2. [A3] The PR description still publishes the superseded method and results. It says the price is net of the UC childcare element and reports a 79% price fall, 41,400 FTEs, £0.90bn revenue, and £5.72bn dynamic cost. The corrected artifact reports 58.26%, 30,565 FTEs, £0.670bn, and £5.949bn. Update the body, including the free-hours/subsidy leg results and test summary, before merge.

Suggestions

None.

PDF Audit Summary

The prior 43-page audit remains valid: file pages 8, 17, and 41 support the treatment, mean, and effect used in the elasticity. No source-derived input changed.

Validation Summary

Check Result
Realised UC calculation Correct counterfactual wiring; prior C1 resolved
Methodology and assumptions Correct except two overview strings
Source references Prior values confirmed; page references added
Targeted tests 9 passed
Artifact mirror and reconciliation Passed
Diff hygiene Passed
Current GitHub CI Passing
Branch status 2 ahead, 0 behind PR #7

Review Severity: COMMENT

Next Steps

Update the two overview strings and refresh the PR description. The substantive implementation is ready on this targeted re-review.

@vahid-ahmadi

Copy link
Copy Markdown
Contributor Author

A1 remainder fixed in 548f0bf (pipeline module docstring and CLI description now say both margins). A3: PR description rewritten to the corrected method and figures.

The Reform tab's labour supply control is now two checkboxes: the extensive
margin, with its low/central/high elasticity when on, and the intensive
margin, at its one setting. Every combination is emitted by the pipeline so
the household view has an exact block for each, and switching the intensive
margin off returns the figures published before it existed.
… and make the two margins switches

Three bullets misdescribed the free-hours leg's work incentive: the 15 hours
cancel for a non-working family, so their gain to work is unchanged; it falls
for working parents on the 30-hour offer, who would keep 15 hours if they
stopped working. The two-year-old targeted offer is now acknowledged.
@vahid-ahmadi
vahid-ahmadi deleted the branch subsidy-take-up-flag September 2, 2026 11:58
vahid-ahmadi added a commit that referenced this pull request Sep 2, 2026
…hodology up to date

Addresses María's review of #8: C1 (the UC childcare element's face value is
mostly withdrawn by the taper, so realised support is the award difference
against an abolition counterfactual), A1 (stale extensive-only text in the
methodology tab and machine-readable assumptions), A2 (file-page references
for the Brewer et al. figures) and S1 (a two-earner allocation test).
vahid-ahmadi added a commit that referenced this pull request Sep 2, 2026
* Add an intensive-margin hours response for parents in work

Closes #6.

* Measure the childcare price on realised UC support, and bring the methodology up to date

Addresses María's review of #8: C1 (the UC childcare element's face value is
mostly withdrawn by the taper, so realised support is the award difference
against an abolition counterfactual), A1 (stale extensive-only text in the
methodology tab and machine-readable assumptions), A2 (file-page references
for the Brewer et al. figures) and S1 (a two-earner allocation test).

* Say both margins in the two overview strings

* Cut the labour supply explanation to four bullets

* Switch the two labour supply margins on independently

The Reform tab's labour supply control is now two checkboxes: the extensive
margin, with its low/central/high elasticity when on, and the intensive
margin, at its one setting. Every combination is emitted by the pipeline so
the household view has an exact block for each, and switching the intensive
margin off returns the figures published before it existed.

* Tighten the dashboard copy, correct the free-hours incentive bullets, and make the two margins switches

Three bullets misdescribed the free-hours leg's work incentive: the 15 hours
cancel for a non-working family, so their gain to work is unchanged; it falls
for working parents on the 30-hour offer, who would keep 15 hours if they
stopped working. The two-year-old targeted offer is now acknowledged.

* Regenerate results after the sources text change

* Title the dashboard for its two legs

* Regenerate results on the rebased history

* Point the site-shell check at the new title

This branch was successfully deployed

1 active deployment
Preview — a16cad2c Deployed Sep 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants