Repository navigation
Conversation
…d portfolio facts (#1074) A new runtime module, fsa_student_loan_targets, binds Federal Student Aid's portfolio tables (the six packages/ed_fsa packages of chronicle#347) to the SCF education-debt stage's person columns, federal_student_loan_balance and federal_parent_plus_loan_balance: the balance and borrower count of each state and DC, of five borrower age bands and of nine debt-size bands, and the Parent PLUS balance beside the balance of every other loan type. Every family binds at FY2026 Q2 (31 March 2026), the latest quarter all four bound tables publish, as a stock unaged at the 2024 build period (target aging's new declared stock_at_source_reference_date alignment). Not Reported is allocated to the reported locations, Puerto Rico and Other leave with their share, and the age, debt-size and loan-type rows bind as shares of the one in-scope control ($1,679.1B, 44.31M borrowers). Repayment plans, the portfolio summary and loan-type recipient counts stay diagnostic, each with its reason in the receipt. compile_us_fiscal_target_registry binds the facts only with student_loan_targets, which the fiscal-refresh release tool sets with --scf-education-debt; the release materializes the rows from the stored person columns, guards their restated age and balance bands, records every fact's disposition in us_source_coverage.json and keeps the opt-in family off the target-parity contract. Default compiles and releases are unchanged. The ED Federal Student Aid provider joins the calibration label registries. experiments/fit_1074_fsa_calibration.py refits the published default with and without the family (plus L2-anchored arms) on Modal through experiments/modal_1074.py, against the pinned feed plus chronicle#347's FSA rows, now built at its merge commit on Chronicle main. Squashes #1357's earlier history onto #1246's review-round head. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…chronicle#347 (#1074) Modal run 20261023T191527Z-4b6bfce9a at 4b6bfce. chronicle#347's export at its merge commit (acbedb5) is byte-identical to the PR head's (sha256 58774e11...), so the candidate feed and the test fixture are unchanged. With loans for a child's education on co-resident adult children, the host gains young borrowers (1,246 borrower records under 25, from 561) and its Parent PLUS share falls from 16.8% to 10.7%, but holds the same borrowers (21.2M) and borrower households (16.4%) at the published weights. Arm B now meets 95% of the FSA targets within 5% (92% before) and every age band; B + L2 78% (76%). Borrower households' share of weight after calibration is 33.7% (B) and 32.3% (B + L2), against 33.0% and 31.5% before and 18.2% of SCF families: the routing moves balances within households, so it does not shrink the doubling. Non-FSA capped error: B 3.62% (3.66%), B + L2 3.85% (3.83%), A unchanged at 3.30%. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DTrim99
force-pushed
the
fsa-student-loan-calibration-targets
branch
from
October 23, 2026 20:22
61803e7 to
3b28865
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebased on #1246's review round (2026-10-23)
3eb32320bas two commits: the feature (4b6bfce9a) and the refreshed receipt and docs (3b2886597).--checks pass on Impute federal student loan balances from the 2025 SCF (#1073) #1246's re-derived values (42,368/42,368 configuration fields).us_runtimemodule to be classified.fsa_student_loan_targets.pyis now a reviewed non-registry module, and its band sort uses a named key instead of a lambda subscript the scanner cannot resolve. Nothing else changes.acbedb5on PolicyEngine/chronicle main, not the fork head. The export is byte-identical (sha25658774e11…), so the candidate feed (d8e3d0fd…, 40,639 rows) and the test fixture are unchanged.4b6bfce9a(Modal run20261023T191527Z-4b6bfce9a; a run at the pre-fix commit gave the same numbers):c94516ee1)What moved, in plain words:
Part of #1074 (the student debt pilot, PolicyEngine/us-student-debt#1). Draft, stacked on #1246 (base
scf-2025-education-debt). PolicyEngine/chronicle#347 merged on 2026-10-22; this waits on the next Chronicle release and the US feed re-pin; nothing here pins a fork. No release is built, published or certified.What binds
A new runtime module,
us_runtime/fsa_student_loan_targets.py, turns Federal Student Aid's portfolio facts into 132 targets on the two person columns #1246 imputes. A person's federal balance isfederal_student_loan_balance + federal_parent_plus_loan_balance, and a borrower is a person whose federal balance is positive. FSA's borrower tables count a Parent PLUS borrower as the parent, which is where the stage puts those balances.Diagnostic only, each with its reason in the receipt:
Reference period
Every family binds at FY2026 Q2 (31 March 2026), the latest quarter all four bound tables publish:
Holding every table to one date makes every family describe one portfolio on one day. The Q1 and Q3 rows stay in the receipt as
other_quarter.The build period is the H5's, 2024. The FSA levels are stocks, applied unaged:
period_alignment = stock_at_source_reference_date. It is neither aged nor a period-contract violation, and only targets that declare it are affected.source_period = 2026-03.Not Reported, Puerto Rico and Other: one control total
The location table has no total row. The state targets are scaled to the national in-scope total:
FSA's tables disagree slightly: the borrower tables total $1,696.3B and the recipient-based loan-type table $1,723.8B. So the age bands, the debt-size bands and the two loan-type rows bind as their table's share of the in-scope control,
control x row / partition, the #940 pattern. All four families then sum to the same total. The age table's own "not reported" row is in no partition, so the reported bands absorb it.Units, all from Chronicle's declared scales:
How it is wired
compile_us_fiscal_target_registry(..., student_loan_targets=True)binds the facts;ed_fsafacts never bind through dynamic dispatch. The release tool sets the flag only with--scf-education-debt, the only builds that carry the columns. Default compiles and releases are unchanged.fsa_stored_student_loan_balancesreads the stage's stored person columns (fsa_student_loan_household_values), because the pinned policyengine-us does not define them yet.student_loan_balance_bandrule. Both are accepted only on that materializer._without_opt_in_student_loan_targets), so a default build and a--scf-education-debtbuild cannot fail each other.us_source_coverage.jsongainsfsa_student_loan_target_receipt, with every FSA fact's disposition and the controls.ED · Federal Student Aidand four categories (federal_student_loan_balance,federal_student_loan_borrowers,parent_plus_loan_balance,loan_balance_excluding_parent_plus) join the calibration label registries.docs/us-fact-to-target.mdhas the rules, the measured limits and the re-pin steps.generate_us_bundle_from_constants.py --checkandspec_engine_coverage.py --checkpass at Impute federal student loan balances from the 2025 SCF (#1073) #1246's head: 42,368/42,368 configuration fields.Fit
experiments/1074-fsa-calibration-receipts.mdholds the receipt, with the JSON inexperiments/receipts/. The setup:populace-us-2024-spm-20260915, 57,240 households, with the Impute federal student loan balances from the 2025 SCF (#1073) #1246 stage run at seed 0.national_statesurface, materializer (one engine batch), loss weights and cap. That is 6,102 non-FSA targets plus the 132 FSA targets, with 0 dropped.calibratewith the release's 1,500 epochs, learning rate, 5x weight cap and conserved mass.l2_lambda = 0.03).It is a re-solve of the published support, not a release build.
FSA targets. Mean absolute relative error, with the share of targets within 5% in parentheses:
B's largest misses, mostly on borrower counts:
The rest of the surface. Mean absolute relative error over the 6,102 non-FSA targets, each miss capped at 100% as the loss caps it:
Adding the family costs 0.31 points without the anchor and nothing measurable with it (+0.00). The families it moves most without the anchor are TANF (+2.6 points), SSI federal recipients (+1.8), JCT tax expenditures (+1.7), BEA proprietors' income (+1.4) and SOI capital gains (+1.1). With the anchor no family worsens by more than 0.5 points, and BEA proprietors' income, SSI federal recipients and SOI capital gains improve by 1.2 to 1.6.
How calibration closes the SCF gap, and what it does to the borrower share and the Parent PLUS mix
The SCF-imputed host holds about half of FSA's portfolio at the published weights: $0.86T and 21.2M borrowers, against $1.68T and 44.3M. That is about what the SCF records ($1.0T, 27.2M) and what SCF totals usually show. The stage gives balances to reference persons, spouses and co-resident adult children, and Parent PLUS to the reference person. So the host is missing borrowers rather than balances: its mean balance per borrower, $40.5K, is already close to FSA's $37.9K.
Calibration does not change any balance. It reweights:
Racial composition of borrowers, before and after calibration
Borrower households by the reference person's race (SCF
racecl4; the SCF column pools the five implicates):Borrowers as people, by their own race: White non-Hispanic 60.2% → 57.2%, Black non-Hispanic 16.6% → 18.4%, Hispanic 13.5% → 14.7%, other 9.6% → 9.7%.
So calibration moves the composition by up to 3 points:
The levels move much more than the composition. The share of households with debt roughly doubles in every group, as the borrower share does: Black non-Hispanic households go from 24.9% to 48.6% (SCF 24.3%) and White non-Hispanic from 15.3% to 30.2% (SCF 17.2%). Race tables of levels inherit the borrower-share issue above. Race tables of composition are less affected.
These tables go to PolicyEngine/us-student-debt#4 before any race breakdown.
Targets that do not compile or bind
For review
--target-family-loss-multiplier ed_fsa=…);Dashboard
The dashboard (calibration-diagnostics) builds its families from a release's
calibration_diagnostics.jsonhierarchy, with no registry to edit there. The rows carry:ed_fsa, labelled "ED · Federal Student Aid";ed_fsa.federal_student_loan_balance,…borrowers,…parent_plus_loan_balanceand…loan_balance_excluding_parent_plus;So the family appears with its release fit as soon as a published release carries it, which is Max's call. Arm B's schema-8
calibration_diagnostics.jsonis on the Modal volumemicrocosm-1074-cache(results/20261023T191527Z-4b6bfce9a/diagnostics/, sha256380c3221…). It could be staged topopulace-us-stagingfor the staging page; I have not uploaded it.What waits on chronicle#347
acbedb5). After the next Chronicle release the US pin moves to a commit with the sixpackages/ed_fsapackages:us/chronicle_feed_scope.json, build year 2026;tools/build_us_chronicle_feed.pyand re-pinus/chronicle_feed.json;--scf-education-debt.chronicle.consumer_fact.v4; the current pin is v3. The pin'sconsumer_fact_schema_versionswould list both, unless the whole feed is re-cut at the newer commit.tests/fixtures/us_fsa_portfolio_chronicle_347_facts.jsonl.gzholds 244 of the draft's rows. They are byte-identical to the Linux export at Belgian selection: country-specific L0 from the full base (dense-informed init) once the BE target surface productionizes #347's merge commitacbedb5(and at its head81e3f98, which reads its raw files from R2). Refresh it if a later Chronicle commit restates the facts.51aa40f(reproduced byte for byte, sha256965a29ac…), add the FSA rows built atacbedb5, and use the spliced file (sha256d8e3d0fd…, 40,639 rows) directly. No repository file names it.Modal runs (
experiments/modal_1074.py, appmicrocosm-1074-experiments, volumemicrocosm-1074-cache)20261023T191527Z-4b6bfce9a20261023T200336Z-3b2886597tests, ruff, test-plan verify, spine-blindness and FSA suites, all passed20261023T191540Z-4b6bfce9atests,specpassed; its extra probe stalled on the environment-only tests and was cancelled20261023T181926Z-2468fba7520261023T182913Z-2468fba75--check, and the engine-free shared and US suites; found the spine-blindness classification fixed in 4b6bfce20261023T182014Z-2468fba75tools.run_engine_test_categories): 448 passed, 6 skipped20261023T181958Z-2468fba75tests,contract: passed20261022T194252Z-c94516ee120261022T184056Z-fd7b279ce20261022T164311Z-399e6fffetests,contract,spec, all passed20261022T170743Z-0ed9d7eef20261022T162443Z-9772bd15b,20261022T162425Z-9772bd15b,20261022T163137Z-4af1cab30has_marketplace_health_coverage), and the two test pins belowThe runs before 2026-10-23 are on the pre-rebase history (#1246's older head), which the squash replaced. The receipt commit after 4b6bfce changes only the receipt, docs, the changelog fragment and a test docstring.
Tests
test_us_fsa_student_loan_targets.py(25 tests). They pin:ed_fsa. The compile-signature pin admitsstudent_loan_targets, the restated-rule pin admits the balance band, and the US spine-blindness inventory classifiesfsa_student_loan_targets.py.3b2886597(run20261023T200336Z-3b2886597): thetestsstep (786 passed, 7 skipped, plus 51 builder tests),ruff check .,ci_test_plan.py verify, and the spine-blindness and FSA suites (523 passed);4b6bfce9a(run20261023T191540Z-4b6bfce9a): generator and coverage--check(42,368/42,368 configuration fields; 41/41 inventory checks), plus 252 spec-engine tests;2468fba75, before the spine-blindness fix:tests,contractpassed (engine-contract 37); the CI engine-us job passed (448, 6 skipped); the engine-free shared and US suites (about 14,300 tests) failed only the three spine-blindness checks now fixed and six tests that cannot run in the Modal image (Python 3.14.2's worker-recycling guard, nopsbinary, running as root, subprocess timeouts under 7 workers), in files this PR does not touch. The UK and node-backed orrery tests were not run.main, so this stacked PR gets it when it is retargeted after Impute federal student loan balances from the 2025 SCF (#1073) #1246 merges.🤖 Generated with Claude Code