UK AI-exposure analysis stack: exposure crosswalk, occupation targets, exposure imputation, and ESRI-style AI shock scenarios#325
Conversation
…E/APS Declare person-entity employment-count TargetSets for UK calibration from two ONS source families, parsed from tidy CSVs committed under populace/build/uk/data (all under 25 KB): - ASHE Table 14.7a (Annual pay - Gross), occupation by four-digit SOC2020, 2025 provisional edition released 23 October 2025: employee-job counts per unit group (340 targets; median annual pay carried for provenance only, since a median is not a sum constraint). - Annual Population Survey via Nomis dataset NM_17_1, Jan 2025-Dec 2025, United Kingdom: employment by SOC2020 sub-major group (table T09b, 26 targets) and, separately, in-employment counts by age band (table T01, 6 disjoint bands). The APS publishes no occupation-by-age cross-tabulation, so the two margins are declared as independent target families. Each builder returns the TargetSet plus a skipped-row report: suppressed ASHE cells, non-unit-group codes, duplicates, and unparseable counts are skipped and reported, never silently dropped; structural problems (missing columns, empty files, all rows skipped) raise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New populace.build.uk_runtime.ai_shock_scenarios implements the scenario-microsimulation method of ESRI JR16 (Doorley et al. 2026, Chapter 3) on a pandas person table carrying ai_exposure: - Employment shock (eq 3.4): TotalJobLoss allocated across occupation groups by employment-weighted C-AIOE, random within-group selection, summaries averaged over n_draws seeded draws (default 50). Displaced persons get employment_income=0, pre_shock_employment_income and an ai_displaced flag for downstream long-term-unemployed treatment. - Wage shock (eq 3.5): aggregate wage change distributed by the separate ai_complementarity score (uniform fallback with a warning), weighted mean uplift equal to the scenario parameter. - Capital shock: interest/dividend income scaled by capital_return_increase / base_capital_return (ESRI 0.004/0.01005, ~+39.8%); rental income excluded. Presets central/low/high anchor Briggs & Kodnani (7% job loss, +2.6% wages), Acemoglu (~1% job loss) and a 13% high variant; all documented as literature placeholders for the analyst to override. Extension beyond ESRI: all summaries resolved by age band (16-24 ... 65+) and an optional youth_displacement_multiplier tilting within-group selection toward 16-24 year olds while preserving the eq 3.4 group totals (default 1.0 = ESRI's age-neutral selection). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New populace.build.uk_runtime.exposure_imputation module: a QRF-based stage that attaches a numeric AI-exposure score to the person table. Design: the stack's canonical imputer (RegimeGatedQRF) is numeric-only — targets are coerced to float64 and draws linearly interpolate quantile predictions — so occupation codes are never imputed. The FRS observes SOC 2020 at major-group level (adult.tab SOC2020, merged upstream), so the primary path conditions on the true soc_major_group and refines exposure within it from shared covariates; omitting the group is a documented blind fallback that emits a UserWarning. A zero-model baseline (exposure_from_major_group: employment-weighted crosswalk mean per major group) brackets the model from below for robustness checks. - fit_exposure_imputer / impute_exposure / attach_exposure, plus an exposure_imputation_stage factory declaring the plan Stage with its LFS/APS DonorSpec - documented DONOR CONTRACT: LFS/APS-derived frame with SOC->exposure pre-joined (lazy reference to the ai_exposure crosswalk module on its own branch); UKDS-licensed microdata never committed - tests on synthetic frames: draws stay in donor support, typed design weights shift the imputed distribution, the planted education signal survives, and major-group conditioning beats the blind path on MAE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ship a pre-built crosswalk attaching occupation-level AI-exposure measures to every SOC 2020 unit group (412 rows), plus an ASHE- employment-weighted major-group table, with lookup helpers in populace.build.uk_runtime.ai_exposure: - c_aioe: complementarity-adjusted AIOE per Pizzinelli et al. (2023, IMF WP/23/216) as applied to UK SOC by Williamson et al. (2025); theta reconstructed from the published O*NET recipe (O*NET 27.3) and validated against the paper's published min/max occupations. - dsit_aioe / dsit_llm: DSIT 'Impact of AI on UK jobs and training' (Nov 2023) Annex 1 scores on UK SOC 2010, mapped to SOC 2020 via the ONS coding index. - eloundou_beta (GPTs-are-GPTs, MIT) and felten_aioe (AIOE-Data), chained US SOC 2018 -> US SOC 2010 -> ISCO-08 -> SOC 2020 via BLS and ONS crosswalks with unweighted means, flagged in mapping_quality (direct/chained/imputed-from-parent). exposure_for_soc falls back 4-digit -> 3-digit -> 2-digit and warns on unknown codes; exposure_for_major_group serves the 1-digit table for microdata that only carries SOC 2020 major groups (FRS). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…/uk-ai-exposure-analysis
…-ai-exposure-analysis # Conflicts: # packages/populace-build/src/populace/build/uk_runtime/__init__.py
…-analysis # Conflicts: # packages/populace-build/src/populace/build/uk_runtime/__init__.py
The behavioral contract (test_spec_only_country_packages) requires country packages (populace/build/uk) to contain only .json/.jsonld spec files. Relocate the ASHE/APS tidy CSVs to uk_runtime/occupation_targets_data/, mirroring the ai_exposure_data/ pattern, and update packaged_occupation_csv_path accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis
left a comment
There was a problem hiding this comment.
Strong work — clean module boundaries, provenance in every docstring, and behavioral tests that assert the actual equations (eq 3.4 group quotas, eq 3.5 θ-graded uplift, the +39.8% capital ratio) rather than just importing. The donor-weight test and the primary-vs-blind conditioning test are exactly the contracts DESIGN.md was written around. CI is green, it's mergeable, and it's additive with no downstream consumers yet, so nothing here blocks. A few methodology and provenance notes (agent-assisted review; data spot-checks reconciled — APS bands sum to the 16+ total, mapping_quality is 380/31/1, ASHE yields exactly 340 targets):
-
Crosswalk reproducibility. Only the output CSVs are committed; the ONET θ reconstruction and the US-SOC→ISCO→SOC2020 chaining live outside the repo. That's a documented choice, but the θ reconstruction is elaborate enough — and DESIGN.md's "artifacts carry their environment" pointed enough — that the builder (or a linked script) should be committed the way geography_sources ships its build_ functions. A follow-up issue is fine.
-
Two age-band schemes. The APS age targets use 16-19/20-24/25-34/35-49/50-64/65+; the scenario summaries use 16-24/25-34/35-44/45-54/55-64/65+. 16-24 reconciles to the first two APS bands, but 35-49/50-64 don't decompose into the scenario bands, so a driver can't calibrate age employment to APS and report shock incidence on the same bands above 35. Worth settling before the analysis driver lands.
-
Wage anchor attribution. The 7% displacement checks out as Goldman's (Briggs & Kodnani) 6-7% base case. I couldn't find the +2.6% wage figure in their work — their headlines are +15% productivity / +7% GDP — so please confirm where +2.6% comes from (ESRI's own calibration?) and cite that source in the module docstring and PRESETS comment. Low stakes since presets are labeled overridable anchors, but the citation should corroborate the value.
-
Self-employed sit outside every shock (the employed mask is
employment_income > 0, and self-employment income isn't in the capital columns either). That mirrors ESRI, but state it as a scope limitation in any writeup. -
Major-group coding seam:
exposure_for_major_groupwants 1-9 but the FRS side documents 1000-9000. It warns on unmatched rather than failing silently, but the integration step has to map codes or it gets all-NaN.
One unverified item, flagged as such rather than as a problem: the θ min/max anchor occupations vs IMF WP/23/216 (their site 403s). Happy to approve once the +2.6% citation is confirmed or corrected.
MaxGhenis
left a comment
There was a problem hiding this comment.
Review — provenance and conventions are excellent; two maintainer calls before merge (scope placement + rebase). Not approving yet on those grounds, not on any correctness defect.
Exposure-crosswalk provenance is exemplary. Every measure in uk_soc2020_ai_exposure.csv (all 412 SOC2020 unit groups) is attributed to a specific, real, correctly-characterized source:
felten_aioe— Felten, Raj & Seamans (2021) standardised AIOE (AIOE-Data/AIOE GitHub).eloundou_beta— Eloundou et al. (2023) "GPTs are GPTs" human-annotated beta (openai/GPTs-are-GPTs, MIT).dsit_aioe/dsit_llm— DSIT/DfE "The impact of AI on UK jobs and training" (Nov 2023), Annex 1, the official UK SOC2010 mapping of the Felten scores.c_aioe(primary) — complementarity-adjusted AIOE per Pizzinelli et al. (2023, IMF WP/23/216) and the Williamson et al. (2025, J Labour Market Res 59:30) UK/Ireland application.- The US-SOC-2018 → SOC-2010 (BLS Nov 2017) → ISCO-08 (BLS 2012) → SOC-2020 (ONS SOC2020 Vol 2 coding index) chain is fully documented, with many-to-many links flagged
chained/direct/imputed-from-parentinmapping_quality.
The one reconstructed measure, complementarity_theta, is handled with real rigor and full disclosure: theta isn't openly published at occupation level (IMF/IGEES request-only), so it's rebuilt from the WP/23/216 recipe on ONET 27.3 and validated to reproduce the paper's published min/max occupations (US SOC 51-9031 / 29-1022) with a documented ~+0.03–0.04 vintage offset. No licensing issue — it derives from CC BY 4.0 ONET plus the published method, not the request-only data itself. Worth a note to swap in the exact IMF/IGEES theta if it ever becomes available.
Occupation-target provenance is equally clean. ASHE Table 14.7a → employment_jobs per SOC2020 4-digit (median pay carried for diagnostics only — correctly not a sum target); APS via Nomis NM_17_1, table T09b (sub-major occupation) and T01 (age band) declared as independent margins because the APS publishes no occupation×age cross-tab. CSV schemas match the docstrings and the magnitudes are right (16+ employment 33.3m ≈ UK total; SOC 11 2.62m). Bad rows are skipped-with-reason, structural problems raise.
Conventions match the UK build. exposure_imputation uses a proper DonorSpec (LFS Five-Quarter Longitudinal EUL) with a documented donor contract and fit_weighted predictors; occupation_targets compiles a populace.calibrate.TargetSet. Consistent with the DonorSpec/Stage + TargetSet patterns the rest of the UK stack uses.
Neutrality on the scenarios is right. ai_shock_scenarios is framed explicitly as scenario analysis, not causal/predictive estimation, implementing ESRI JR16 (Doorley et al. 2026) eqs 3.4/3.5 with parameters presented as overridable literature anchors (Briggs & Kodnani 2023 / Goldman central, Acemoglu 2025 low). It sets employment_income=0 + ai_displaced=True and leaves benefit/unemployment modelling to the downstream PE-UK run — clean separation. The by-age-band resolution and youth_displacement_multiplier are a documented, principled extension of ESRI's random within-group selection.
Two things for a maintainer to decide before merge:
- Scope/placement. This is ~+3980 lines of research-analysis infrastructure (an exposure crosswalk + shock-scenario engine) landing in
populace-build/uk_runtime.occupation_targetsunambiguously belongs (population calibration). The exposure crosswalk + scenario engine are an analysis layer riding on the microdata — reasonable to host here, but big enough that whether it lives in corepopulace-buildvs. a separate research module is a call for the maintainers, not something to wave through in a routine review. - Rebase. CONFLICTING/DIRTY — a large branch touching
uk_runtime/__init__.py; it needs a rebase onto current main and the conflict resolution won't be purely mechanical. CI was green on the last push (test + wheels, 3.13/3.14).
Minor: the exposure major-group aggregation weights by ASHE Table 14 (2021 provisional) while the calibration targets use ASHE 2025 — different vintages for different purposes, defensible but worth aligning to the latest release; and the shipped CSVs are tidy derivations whose raw→tidy step lives outside the repo, so a checked-in derivation script would make the crosswalk fully reproducible from primary sources.
Not merging (review-only per request).
…s, builders, and study pipeline Review fixes (PR #325): - Correct preset citations: +2.6% wages is ESRI JR16's reading of Briggs & Kodnani (2023) — the median wage-change estimate of their surveyed studies (JR16 fn.3, §3.2) — not a Goldman headline; low re-cited to Acemoglu (2025, Economic Policy 40(121)); high 13% re-anchored to Brynjolfsson, Chandar & Chen (2025) "Canaries in the Coal Mine?" with a cohort-specificity caveat and JR16's own 10% grid ceiling documented (grid: 1-10% x +1-5%, +0.4pp capital always on, per Cazzaniga/Pizzinelli et al. 2024). - Reconcile APS age targets onto the scenario reporting bands (16-24/25-34/35-44/45-54/55-64/65+) via Nomis NM_170_1 five-year bands (T01 does not publish the scheme); sums reconcile to the 16+ total within publication rounding. Scenario summaries now accept an age_bands override. - exposure_for_major_group accepts FRS adult.tab 1000-9000 coding. - Major-group exposure weights regenerated on ASHE 2025 (was 2021), from the packaged occupation-targets CSV via the new builder. - New ai_exposure_sources.py (geography_sources pattern): pinned source URLs and build_* derivations for theta (with published min/max anchors asserted), the DSIT and US-measure chains, the exposure table, and the executable ASHE-weighted major-group aggregation. - Employment-shock summary reports excluded_self_employed_weighted; the employees-only scope is documented. - Klein & Teeselink (2025) now calibrates a documented youth multiplier (5.8/4.5 ~= 1.29) and a central_youth_tilted preset; defaults stay at 1.0. Study pipeline (previously listed as follow-ups): - frs_occupation.py: Stage producing soc_major_group from the licensed FRS adult.tab (SERNUM/BENUNIT/PERSON join to PE-UK person ids), slotting before exposure_imputation_stage. - ai_shock_runner.py: end-to-end driver (dataset -> exposure -> JR16 scenario grid -> PolicyEngine-UK write-back -> Exchequer/poverty/Gini deltas by income decile and age band), engine-free pure summary math, local_runner conventions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ntime __init__ export union) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks for both reviews — all points addressed in the two new commits (3b72a3e + merge 27c6ae3): +2.6% wage anchor — confirmed and re-cited. It comes from ESRI JR16 itself: they adopt Briggs & Kodnani (2023), where 2.6pp is "the median wage change estimate of a number of studies surveyed by the authors" (JR16 fn.3, §3.2) — not a Goldman headline. Docstring and Age bands — reconciled onto the scenario scheme. Nomis T01 doesn't publish 35–44/45–54/55–64, so the APS age targets are now derived from the companion NM_170_1 five-year bands (Jan–Dec 2025, same geography/measure), summed to 16-24/25-34/35-44/45-54/55-64/65+. Values reconcile exactly with T01 where the schemes coincide (16+, 16-19+20-24, 65+) and the six bands sum to the 16+ total within publication rounding. The shock summaries also accept an Major-group seam — ASHE vintage — major-group weights regenerated on ASHE 2025 from the packaged occupation-targets CSV (max per-measure delta ≤0.078, rank orderings preserved); Reproducibility — new Self-employed — employment summary now reports Klein & Teeselink now calibrates rather than decorates: Follow-ups pulled into this PR: Rebase — merged current main; the Scope/placement — your call stands: Suite: 1073 passed locally; ruff clean. 🤖 Generated with Claude Code |
|
Ran the pipeline end-to-end locally (conda py3.13, PR branch, PolicyEngine HF data: 1. Bug:
So 2. Enhanced FRS caveat: the SOC join doesn't survive the clone/re-sample step.
🤖 Generated with Claude Code |
Summary
Adds the four building blocks for a UK analysis of generative AI's fiscal and distributional impact by age/seniority (ESRI JR16 replication for the UK, extended along the age dimension), all under
populace.build.uk_runtime:1. AI-exposure crosswalk (
ai_exposure.py+ packaged CSVs)c_aioe(primary, Felten AIOE × Pizzinelli complementarity adjustment),complementarity(θ),dsit_aioe,dsit_llm,eloundou_beta,felten_aioe.mapping_quality+ fan-out recorded per row (31 direct / 380 chained / 1 parent-imputed).exposure_for_soc()with 4→3→2-digit fallback;exposure_for_major_group()(1-digit, ASHE-2021-employment-weighted) for microdata that carries only FRS major-group SOC.2. Occupation calibration targets (
occupation_targets.py+ packaged CSVs)TargetSets (entity=person) with full provenance; bad rows skipped-and-reported, never silently dropped.3. Exposure imputation stage (
exposure_imputation.py)soc_major_groupas the lead predictor (refine-within-group), a warned blind fallback, and a zero-model baseline (exposure_from_major_group).plan.Stagewhoseconsumesincludessoc_major_group, so runs are blocked before the FRS adult.tab SOC merge.4. AI shock scenarios (
ai_shock_scenarios.py)youth_displacement_multiplier(default 1.0 = pure replication) tilts within-group displacement toward 16–24 while preserving eq-3.4 group totals; all summaries resolved by age band. ESRI's stated limitation (random within-group selection ignores younger workers' higher transition risk) is quoted in the docstring.Tests
48 new tests across the four modules, all passing (
uv run --package populace-build --group dev pytest);ruff check/formatclean;uk_runtimeimport verified (171 exports, all__all__names resolve). Packaged data files are all <50 KB, open-licensed (OGL v3 / MIT / CC BY 4.0), with sources cited in module docstrings.Not included (follow-ups)
References
The design replicates and extends the methodology of these papers:
youth_displacement_multiplierextension: GenAI adoption reduces junior employment (~9% after six quarters) while senior employment is unaffected. PDF / SSRN 5425555🤖 Generated with Claude Code