Repository navigation
Conversation
…ine#310) Six ed_fsa packages read the Federal Student Aid Data Center portfolio workbooks (fetched 2026-10-12; Last-Modified 2026-10-08) and emit 461 administrative facts at their published levels, scaled from $ billions and counts in millions or thousands: - PortfolioSummary.xls: Direct Loans, FFEL, Perkins and Total, FY2026 Q1 to Q3 (Q3 total $1,724.5B; 42.3M unduplicated recipients). - Portfolio-by-Location.xls: 50 states, DC, Puerto Rico, "Other" and "Not Reported" ($52.2B) as of Mar. 31, 2026. Other and Not Reported are statistical-scope rows, not allocated to states. - Portfolio-by-Age.xls and Portfolio-by-Debt-Size.xls: FY2026 Q1 and Q2, borrowers, with age and balance bounds from the Definitions sheets. - PortfoliobyLoanType.xls: FY2026 Q1 to Q3; Stafford (Combined) and its unique recipient count sit in their own record sets. - DLPortfoliobyRepaymentPlan.xls: all six data sheets, FY2026 Q1 to Q3 (through Jun 30, 2026, before RAP). NA** / NA*** recipient cells emit no fact and are guarded on the matching dollars row. The ACS package reads S1501 for the 2024 1-year vintage (2025 1-year is not released) from the data.census.gov table API, through a new census_acs_s1501_attainment_json_rows parser that also accepts the api.census.gov array. It emits 676 state facts: the 18 to 24 universe (four categories) and the 25 and over universe (seven categories, with associate's and bachelor's degrees separate). Every package is year-keyed to its file (2026 for FSA, 2024 for ACS), so the default 2023 bundle skips all seven; its pins move only for that. Raw R2 keys are recorded at their canonical content-addressed locations and still need `chronicle publish-raw` by a holder of R2 credentials. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Closes #310
Summary
Seven publisher-backed source packages for the EqRC student debt pilot (PolicyEngine/us-student-debt#1). Microcosm consumes them in PolicyEngine/microcosm#1074 (calibration targets) and PolicyEngine/microcosm#297 (attainment). Chronicle stores the published levels. Aging, reconciliation across FSA tables, the treatment of "Not Reported", and target selection stay in Microcosm.
Federal Student Aid portfolio (
packages/ed_fsa/)All six files were downloaded on 2026-10-12 from
https://studentaid.gov/sites/default/files/fsawg/datacenter/library/<file>. Every file hasLast-Modified: Thu, 08 Oct 2026, so FSA re-posted them after the issue was written (2026-09-30). The latest period in each file is still the one the issue lists, and the issue's headline numbers match ($1,724.5B, 42.3M recipients, $52.2B Not Reported). studentaid.gov stalls non-browser user agents, so the files were fetched withcurland a browser UA (see Notes).Each package emits every FY2026 quarter in its file. Earlier quarters are not emitted as facts, but they stay in the parsed source cells. A fact's period is the month in which the quarter ends (
period_type: month).period_coverageholds the snapshot date and the FSA label, for exampleFederal Fiscal Year 2026 Q3, as the MSD quarter-end packages do. Dollars are published in $ billions and scaled withvalue_scale: 1e9. Counts are published in millions (1e6), or in thousands for the location table (1e3).ed-fsa-portfolio-summary-fy2026-q3ed-fsa-portfolio-by-location-fy2026-q2statistical_scopegeographies (ed_fsa:location_other,ed_fsa:location_not_reported) and are not allocated to states. The table has no total row.ed-fsa-portfolio-by-age-fy2026-q2agebounds. "24 and Younger" has no lower bound, because the Definitions sheet describes bands ("Younger than 18", "19 to 24") that the table does not publish. "Not Reported" carriesage_reporting_status == not_reported.ed-fsa-portfolio-by-debt-size-fy2026-q2federal_student_loan_balancebounds taken from the Definitions sheet (≥ lower, < upper). The table writes "80K-100K" and the Definitions sheet writes "80K to 100K". The guards keep the table's spelling.ed-fsa-portfolio-by-loan-type-fy2026-q3subtotal,federal_student_loan_type_group == stafford), so they don't mix with the loan-level counts.ed-fsa-dl-portfolio-by-repayment-plan-fy2026-q3NA***(DL) andNA**(FFEL, and federally managed Q1); those cells produce no fact, and a guard on the matching dollars row records the marker. The workbook does not define the asterisks. Federally managed "Other" recipients appear as 0.09M from Q2.Spot values: Q3 total $1,724,500,000,000; Direct Loans $1,564.8B; SAVE (DL, Q3) $354.3B and 6.52M recipients; California $156.7B and 3,919,500 borrowers; age 35–49 (Q2) $685.1B; Parent PLUS (Q3) $115.0B and 3.5M recipients.
Census ACS S1501 (
packages/census/acs_s1501_state_2024/)api.census.gov/data/2025/acs/acs1returns 404, and the Census Bureau's 6 August 2026 update says their release date "is being determined"./api/access/data/table?id=ACSST1Y2024.S1501&g=010XX00US$0400000). It wraps the api.census.govgroup(S1501)array in{"response": {"data": [...]}}. api.census.gov now redirects requests without a key to its missing-key page, so the template's endpoint can't be refetched without a key.census_acs_s1501_attainment_json_rowsaccepts both the wrapped and the bare array. Like the S0101/S2201 parsers, it unpivots each state into long rows withageandeducational_attainmentcolumns, so row-backed constraints are checked against the source row.S1501_C01_001E–005E): a total plus 4 categories. This universe does not separate associate's degrees; they are inside "Some college or associate's degree".006E–013E): a total plus 7 categories (less than 9th grade; 9th–12th, no diploma; high school; some college, no degree; associate's; bachelor's; graduate or professional).acs_s0101_state_2024template. The detail rows add up to each total in this release.Code changes
chronicle/sources/rows.pyandchronicle/source_package.py: the S1501 parser and its dispatch. SevenSOURCE_PACKAGE_ALIASESentries.federal_student_loan_program,..._type,..._repayment_plan,..._portfolio,..._status_group,..._partial_financial_hardship,federal_student_loan_balance,age,educational_attainment), because the consumer contract rejects variables in theed_fsa.namespace. The groupby dimension ids stay namespaced (ed_fsa.loan_typeand similar).packages/ed_fsa/build_packages.pyandpackages/census/acs_s1501_state_2024/build_package.pygenerate the YAML (precedent:packages/statbel/.../build_package.py). Every guard, header and label string is read from the publisher file, so the next FSA quarter can be regenerated rather than hand-edited.tests/test_chronicle_bundle.py: each package is keyed to its file's year (2026 for FSA, 2024 for ACS) and has noartifact_yearpin, so the default 2023 bundle skips all seven. Onlyskipped_source_count(10 → 17) and the skipped list move. No fact, key or warning count changes.Chronicle Governance
ledger-source-ingestor. As in Add BEA NIPA Table 7.8 line 17 (B4923C): employer contributions for private group health insurance #331 and Add IRS SOI Historic Table 2 TY2023 state AGI-band facts #291, the change also touchesdb/data/**and the alias map / parser dispatch inchronicle/source_package.py, which the role file does not list.chronicle validate-packageis valid for all seven, at--year 2026(FSA) and--year 2024(ACS). At--year 2023each reports onlyNo source artifact for year 2023, which is the bundle's skip condition.chronicle build-suiteis valid for all seven, with lineage coverage 1.0 and 0 agent-acceptance errors. The only warning isconcept_alignment_validation_skipped, because no Axiom root was passed.ageandeducational_attainmentconstraints are evidenced.tests/test_chronicle_ed_fsa_portfolio.pyre-reads every selected cell withxlrdand checks fact = cell × scale exactly (Decimal) for all 461 facts.tests/test_chronicle_census_acs_s1501.pychecks all 676 facts against the raw JSON by(GEO_ID, variable).consumer_facts.jsonlwas written for every package, andvalidate_consumer_fact_contractpasses.build-bundle --year 2026over the six FSA packages gives 461 facts with 0 errors, 0 warnings and 0 aggregate or semantic duplicate keys.build-bundle --year 2024over S1501 + S0101 state + S0101 national gives 1,630 facts with 0 errors, 0 warnings and 0 duplicates.test_chronicle_boundaries.py,test_chronicle_facts_only.pyandtest_chronicle_governance.pypass locally, except one boundaries test that fails on Windows only (acharmapdecode error, also on unmodified files). There is no derived, aged or reconciled value; every fact is one publisher cell times a declared unit scale.ledger-source-fidelityis pending andledger-boundaryis pending. CI does not run them;.github/workflows/ci.ymlhas no judge step. They need an independent reviewer run, as on Add BEA NIPA Table 7.8 line 17 (B4923C): employer contributions for private group health insurance #331. The author has not self-certified them.Tests
tests/test_chronicle_ed_fsa_portfolio.py(28 tests) andtests/test_chronicle_census_acs_s1501.py(9 tests). Both pass. They cover pinned counts, cell-by-cell value checks, headline values, constraints, NA markers, Other/Not Reported, no artifact for other years, a guard that rejects a changed expectation, and a selector moved to the previous quarter.test_chronicle_source_rows.py,test_source_package_alias_drift.py::test_repo_alias_map_matches_packages_on_disk,test_chronicle_consumer_contract.py,test_issue_261_dimension_labels.pyandtest_issue_266_geography_names.py.ruff check chronicle policyengine_chronicle db scripts testspasses.test_chronicle_bundle.py) locally; CI runs it. The new pins come from_source_unavailable_for_yearon each package at 2023.core.autocrlfrewriting pinned CSVs,charmapdecoding, drive letters parsed as URL schemes, symlink privileges, and wrangler. None of them involve these packages. For the boundary, restamp, concept and label suites, I confirmed the same failures on the base commit. The new raw files are binary XLS or a single-line JSON, so autocrlf cannot change them, and the staged blobs hash to the manifest SHA-256s.Notes for the reviewer
ledger-rawkeys, the same oneschronicle publish-rawwrites. The consumer contract refuses facts withoutraw_r2_uri. I don't have R2 credentials, so nothing has been uploaded. Before merge, someone with access should runchronicle publish-raw --root db/data/ed_fsaandchronicle publish-raw --root db/data/census/acs_s1501_state_2024, then verify withchronicle verify-raw.python-httpx(and thereforechronicle fetch-artifactandLEDGER_SOURCE_ARTIFACT_FETCH=1). The bytes are committed, so builds don't need to fetch them.quarter.🤖 Generated with Claude Code