Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog.d/1074-fsa-student-loan-targets.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The US build can calibrate the SCF-imputed federal student loan balances to Federal Student Aid's portfolio tables (microcosm#1074, the student debt pilot). A new runtime module, `fsa_student_loan_targets`, binds the FSA facts Chronicle stores (PolicyEngine/chronicle#310) to the 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. A borrower is a person whose own plus Parent PLUS balance is positive. Repayment plans, the portfolio summary and the loan-type recipient counts stay diagnostic only, each with its reason in the receipt.

Every family binds at one reference date, the latest quarter every bound table publishes (FY2026 Q2, 31 March 2026), as a stock unaged at the 2024 build period: target aging gains a declared `stock_at_source_reference_date` alignment, which only these targets carry. FSA's "Not Reported" row is allocated to every reported location in proportion to its reported value; Puerto Rico and "Other" are outside the host universe and leave with their share. The 51 allocated state levels make one in-scope control, and the age, debt-size and loan-type rows bind as their table's shares of it, so every family sums to the same total.

`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 stage's stored person columns, accepts their age and balance bands in its restated-filter guard only on that materializer, records every FSA 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, and the pinned US feed carries no FSA facts until it moves to a Chronicle commit with the packages. The `ED · Federal Student Aid` provider and its four categories join the calibration label registries, so the family shows on the calibration dashboard once a release carries it.

Fit receipts are in `experiments/1074-fsa-calibration-receipts.md`, generated by `experiments/fit_1074_fsa_calibration.py` on Modal through `experiments/modal_1074.py` against a candidate feed (the pinned feed plus chronicle#347's FSA rows, built at its merge commit on Chronicle main).
83 changes: 83 additions & 0 deletions docs/us-fact-to-target.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,89 @@ binds them:
band. `experiments/940-state-agi-bands/` measures pre-calibration support
per state and band on the #982 fix's offline outputs.

### Federal Student Aid's portfolio binds the student loan balances (microcosm#1074)

Federal Student Aid publishes its federal student loan portfolio quarterly.
Chronicle stores each table as published (PolicyEngine/chronicle#310); the
alignment across tables is Microcosm's, in
`us_runtime/fsa_student_loan_targets.py`. The targets bind the person columns
the SCF education-debt stage writes (microcosm#1073):
`federal_student_loan_balance` (own loans) and
`federal_parent_plus_loan_balance`. A person's federal balance is the sum, and a
borrower is a person whose federal balance is positive.

- **Which rows.** The borrower-based tables: by location (the 50 states and DC,
balance and borrowers), by age (five bands on the person's age) and by debt
size (nine bands on the person's federal balance, lower edge inclusive). From
the loan-type table, the Parent PLUS balance and the balance of every other
loan type. Each fact's disposition, bound or not, is in
`fsa_student_loan_target_receipt` and in a release's
`us_source_coverage.json` (`fsa_student_loan_target_receipt`).
- **Diagnostic only.** Repayment plans: the SCF records only an income-driven
yes/no, the build has no plan column, and FSA counts plan recipients loan by
loan. The portfolio summary: recipient totals by loan program, which the
balances do not record. Loan-type recipient counts: loan-level, so a person
with two loan types counts twice. Stafford (Combined): a subtotal.
- **The reference date.** The latest quarter every bound table publishes, FY2026
Q2 (31 March 2026) in the chronicle#347 packages: the location workbook
publishes only that date, so the age, debt-size and loan-type tables bind at
it too, and every family describes one portfolio on one day. The facts are stocks the
build carries as they stand at that date, unaged at the 2024 build period
(`period_alignment = stock_at_source_reference_date`, factor policy 5 in
`target_aging`): no income-growth series describes a loan stock, and the
analysis reads the columns in 2026.
- **Not Reported, Puerto Rico and Other.** The location table has no total row.
Its "Not Reported" row ($52.2B, 1.95M borrowers) holds FFEL and Perkins loans
whose commercial lenders need not report an address. It is allocated to every
reported location in proportion to that location's reported balance (or
borrowers, for the count). Puerto Rico and "Other" (an address outside the
listed states and territories) are outside the host universe, the 50 states
and DC the CPS samples, and leave with their share. The 51 allocated state
levels sum to the **in-scope control**: $1,679.1B and 44.31M borrowers at the
chronicle#347 packages, against $1,696.3B and 44.76M in the table.
- **One control.** FSA's tables disagree slightly (the borrower tables total
$1,696.3B, the recipient-based loan-type table $1,723.8B). The age and
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 state AGI band pattern. A
table's own "not reported" age row is in no partition, which allocates it
across the reported bands. So the state, age, debt-size and loan-type families
each sum to the same total.
- **Opt-in.** `compile_us_fiscal_target_registry(student_loan_targets=True)`
binds the facts; the release tool sets it with `--scf-education-debt`, the
only builds that carry the columns. The release materializes the rows from
the stored person columns (`fsa_student_loan_household_values`), because the
pinned policyengine-us does not define them yet. The release's
restated-filter guard accepts the age and balance bands only on that
materializer. While the stage is opt-in the family stays off the one-surface
target-parity contract (`_without_opt_in_student_loan_targets`).
- **Known limits, measured.** The receipt
(`experiments/1074-fsa-calibration-receipts.md`) refits the published
default with and without the family. The stage gives balances to reference
persons, spouses and co-resident adult children (a loan for a child's
education is the adult child's own unless only a parent could have borrowed
it), and Parent PLUS to the reference person. The SCF misses independent
adults who live on their own or in someone else's household, so the host
holds about half of FSA's borrowers ($0.86T and 21.2M at the published
weights). Calibration closes that by reweighting: borrower households'
weight doubles and their share of household weight goes from 16.4% to
33.7%, against 18.2% of SCF families. Routing child loans to adult children
moved balances between people in the same households, not into new ones, so
it did not shrink that doubling; it did give the host young borrowers (1,246
borrower records under 25, from 561), and the under-25 borrower band now
fits. FSA's Parent PLUS row counts only unconsolidated Parent PLUS loans (a
consolidated one sits in "Consolidation"), while the SCF's loans for a
child's education include consolidated ones, so that target is a lower bound
on the concept it binds; it pulls the Parent PLUS share from 10.7% to 6.8%.
- **When the facts reach the pin.** The US pin moves to a Chronicle commit that
carries the six `packages/ed_fsa` packages: add their (record set, period)
pairs to `us/chronicle_feed_scope.json` (build year 2026), rebuild the feed,
re-pin `us/chronicle_feed.json`, and regenerate the two parity resources with
the FSA families declared as reviewed exclusions that bind only with
`--scf-education-debt`. The fixture
`tests/fixtures/us_fsa_portfolio_chronicle_347_facts.jsonl.gz` holds rows of
the chronicle#347 export the tests pin, byte-identical at its merge commit
(`acbedb5`); refresh it if a later Chronicle commit restates the facts.

## 4. Keep the exclusion register honest

`US_FISCAL_TARGET_SUPPORT_EXCLUSIONS` is keyed by `source_record_id` and its
Expand Down
Loading