# Changelog

User-visible changes to the Verzi Healthcare Data API and dashboard. Format follows [Keep a Changelog](https://keepachangelog.com/); dates are ISO-8601 UTC.

The full commit history is at [github.com/verzihealth/cms-star-ratings](https://github.com/verzihealth/cms-star-ratings). Anything not user-visible (refactors, test-only changes, internal tooling) is deliberately omitted from this log.

---

## Unreleased

### Added
- **MSSP financial and quality results, PY2013 to PY2024.** We held 5,361 Shared Savings Program ACOs and 182K participants but no financial results for any of them. The gap is closed. New `mssp_financial_results` holds **5,476 ACO-years** with benchmarks, expenditures, shared savings and losses, quality scores, provider counts by type, beneficiary demographics, risk scores, and utilization. New `mssp_quality_measures` holds **127,374 per-measure results** across 74 distinct measure codes. Three endpoints: `GET /vbc/financials` (search and rank, including `sort=losses`), `GET /vbc/financials/summary` (program rollup by year and track), and `GET /vbc/acos/{aco_id}/financials` (one ACO's full history plus cumulative earned savings). The PY2024 load reconciles exactly with the CMS fact sheet on four independent figures: 476 ACOs, 10,326,340 assigned beneficiaries, $4.12B in performance payments, and 75% of ACOs earning them.
- **Rates are normalized to percent across all 13 MSSP performance years.** CMS switches these columns between a 0-1 fraction and a 0-100 percentage from file to file, and the `%` suffix does not track the switch: PY2024 dropped the suffix but kept the 0-100 scale. Left as published, a PY2018 savings rate of 0.0144 and a PY2024 savings rate of 4.215 describe comparable performance but sort 300 places apart. Savings rate, minimum savings and loss percentages, share and loss rates, quality score, and the dual and long-term-institutionalized percentages are all resolved to percent. Every row also carries `source_row`, the CMS row exactly as published, so the raw values remain available.
- **`qual_score_note` records why a quality score is absent.** PY2014 publishes "P4R" for 119 first-year ACOs, meaning they were scored on reporting the measures rather than on performance. That token now has its own column, so it is distinguishable from CMS small-cell suppression.
- **ACO REACH refreshed to PY2024, with PY2021 to PY2024 history and the full published column set.** REACH was frozen at one performance year (PY2023) since launch. It loaded a fraction of what CMS publishes. The layer now holds **421,523 participant records across four performance years** (was 128,548 for one year), **399 financial and quality records** (was 132), and **578 entity-years** covering PY2021 to PY2026 (was 74 for one year). Participant records gain participation dates, the APO election, and all 14 benefit-enhancement waiver flags. The flags cover telehealth, the SNF 3-day stay, post-discharge home visits, concurrent hospice care, and the rest. Financial results grow from 39 columns to 115. The new columns give provider counts by type (PCP, specialist, NP, PA, CNS, FQHC, RHC, CAH), every quality subscore including CAHPS and the 11 stewardship measures, beneficiary demographics, the inpatient expenditure split, and utilization rates. `GET /vbc/reach` and `GET /vbc/reach/{entity_id}` accept a `year` parameter. The participant list is now paged with `provider_limit`, `provider_offset`, and `provider_total`. One entity reached 18,506 participants in a single year.
- **`GET /accuracy/discipline-coverage`** — we now publish how much of the nation's practitioner discipline we actually hold, per state, benchmarked against the NPDB Public Use File (the only national count of state licensure actions and exclusions). Two classes are reported separately because they are different populations: exclusions (federal LEIE + state Medicaid lists) currently cover **52.1%** of NPDB-reported excluded practitioners for 2023–2025, while state board licensure actions cover **0.3%** — Colorado is the only board feed loaded so far. The gaps are published alongside the wins. Unauthenticated, like the rest of `/accuracy`. The benchmark file carries no names or NPIs; only aggregate counts are published, and state counts below 11 are withheld.
- **Primary-source state license data: Washington, Connecticut, Colorado** — the first state-board licenses loaded from the states' own files rather than NPPES self-attestation. All three come from official state open-data portals with daily refresh, no accounts, fully scriptable. New sources `wa_doh` (2,444,206 credentials), `ct_dph` (785,467 health-profession credentials), and `co_dora` (1,524,923 individual licenses) at `/sources`, with NPI linkage backfilled from NPPES license-number matching (digits + last name, confirmed against first name in WA; digit + name match in CT/CO).
- **Colorado disciplinary actions** — the CO DORA file carries structured discipline (case number, action, effective/complete dates). Of 51,848 distinct disciplinary records, the 3,266 that resolve to an NPI load into `license_disciplinary_actions`; the rest carry no NPI and are not matched by name. First state-board disciplinary source beyond exclusion lists.
- **Washington NPI coverage tripled** — WA licenses now resolve to 136,556 providers, up from 43,373, lifting NPI-linked active clinicians across the three states from 22.9% to 28.7% (40% within WA). The previous match required NPPES and WA DOH to agree on the license prefix, but they use different codes for the same credential, so most licenses could never link. Precision improved at the same time: 47 NPIs that had been attached to two different people are gone, and every linked row now agrees with NPPES on first name. Affects `/licenses/{npi}`, `/licenses/{npi}/disciplinary`, and the `/provider/{npi}` dossier.
- **WA/CT/CO refresh daily** — the three state license sources now run on a new daily pipeline cadence (11:00 UTC), each downloading its own file. `/sources/{id}/history` shows the daily trail.
- **IRS 990 coverage extended to tax years 2020–2023** — the bulk XML parser previously reached only 2024-index filings because the IRS index CSVs for earlier years omit the batch-file column needed to locate each filing's XML. That mapping is now recovered by reading each archive's directory listing directly, without downloading the multi-gigabyte archives. Result: **21,439 Form 990 filings across 5,270 healthcare EINs** (was 3,541 / 3,375) and **144,944 executive-compensation records** (was 24,027), feeding `/systems/{id}/financials` and `/systems/{id}/financials/executives`.
- **Executive-to-provider links now populate** — `executive_compensation.matched_npi` and the `irs_990_schj` attribution signals had been empty since launch (two bugs: candidates were filtered by a state that was never recorded for the filing organization, and names were parsed in the wrong order). Fixed: **44,763 executives now resolve to an NPI** (14,070 high-confidence, 30,693 medium) producing **44,025 new employment-attribution signals**. Matching stays conservative — an executive whose name fits more than one provider is left unmatched rather than guessed, so common names like "Robert Grossman" (17 NPIs nationally) produce no link.
- License Intelligence: primary-source verification links. Each license on `GET /licenses/{npi}` and each alert on `GET /licenses/alerts/recent` now carries a `verify_at` block with the state board lookup and disciplinary-page URLs. `GET /licenses/state/{state}` gains a `board_directory` block. Directory: 222 board portals across all 50 states + DC, link-checked; dead links are excluded.
- **Oregon joins the exclusion layer (34 states)** — OR publishes names + per-person exclusion-letter PDFs only (no dates, no NPI), served from a SharePoint list the loader reads through the public SOAP endpoint. Thin, but official — and it completes coverage of every state with an obtainable list. Letter URLs are kept in each record's raw payload.
- **`GET /ma-events/recent` now returns data** — a new SEC 8-K parser scans every logged healthcare 8-K for M&A item codes (Entry into Material Agreement, Completion of Acquisition/Disposition) and loads typed events into `ma_events`: acquisition announced, acquisition completed, and divestiture, each linked to its SEC filing. Target names and deal values are extracted only when confidently matched — otherwise the event carries the filing link and item facts without guessing. Initial load: 24 events (2023–2026) across Tenet, CHS, UHS, Acadia, Select Medical, Surgery Partners, Addus, Astrana, Pennant, Encompass, and Enhabit, including Astrana's $708M Prospect Health acquisition.
- **Physician employer-move detection is live** — `physician_employer_moves` now carries 207,725 arrival/departure events, derived from three PECOS reassignment snapshots (2026-06-10, 2026-08-13, 2026-08-17). The view had been empty since launch because only one snapshot existed; this week's DAC/PECOS refreshes supplied the comparison points.
- **Exclusion Screening page on the dashboard** (`/exclusions` at www.healthcaredata.io) — screen a provider by NPI against the federal LEIE + all 33 state lists with a single verdict banner, or search state lists by name (most state records carry no NPI). Shows live coverage stats (states loaded, record counts, share of state-excluded NPIs absent from the federal list). Backed by new unauthenticated `/demo/exclusions/*` endpoints (check, search capped at 25 rows, coverage).
- **Exclusion Screening published at `/resources/exclusion-screening`** — product overview for the combined federal + state exclusion layer: per-state coverage table, methodology (primary sources, conservative NPI validation, `oig_reciprocal` handling), and the API surface. Listed in the `/resources` catalog.
- **State exclusion endpoints under `/exclusions`** — the state Medicaid exclusion layer is now queryable directly: `GET /exclusions/comprehensive/{npi}` (one-call federal LEIE + all 33 state lists; the top-level `excluded` flag covers both), `GET /exclusions/state/{npi}` (state-list records for an NPI), `GET /exclusions/state/search` (name/state/entity-type search — most state records carry no NPI, so name search is the primary screening path), and `GET /exclusions/state/coverage` (which states are loaded, per-state counts, LEIE overlap, last-load timestamps). `POST /exclusions/screen` now also returns `state_excluded` hits alongside the federal ones, and `GET /exclusions/crossref/{npi}` carries `state_exclusion_records` plus a risk flag when an NPI is state-excluded but absent from the federal LEIE.
- **State Medicaid exclusion lists — batch 2: 12 more states, 8,598 records (33 states, 85,697 total)** — DE, NE, GA, NV, IA, AK, HI, AZ, KS, NC, WV, WY join the `state_exclusions` layer from their primary state sources. Seven of these publish PDF tables; the pipeline runtime now bundles pdfplumber, so all 33 states — including NJ from batch 1 — sync monthly. Minnesota is the one newly-confirmed dead end (Radware captcha on the DHS page); Oregon stays deferred (names-only SharePoint list). Only remaining unbuilt states are the five blocked ones (MA, NH, MN, ME, FL) and thin OR.
- **State Medicaid exclusion lists — 21 states, 77,099 records** — new `state_exclusions` table loaded from each state's primary public source (CA 22.5K, TX 13.4K, NY 9.0K, PA 6.7K, LA 5.9K, MI, NJ, AR, OH, AL, MD, SC, KY, CO, MO, WA, MS, ND, MT, IN, TN). These lists are additive to the federal OIG LEIE: 69.8% of the 11,286 distinct state-excluded NPIs do not appear in LEIE at all. Records with a valid NPI also materialize into `license_disciplinary_actions` (visible in `/licenses/{npi}/disciplinary` and `/licenses/alerts/recent`); rows a state marks as federal-OIG mirrors are flagged `oig_reciprocal` and excluded from that materialization to avoid double-counting. Each state is a registered source (`{st}_medicaid_exclusions`) with independent load history at `/sources`; 20 states sync monthly in the pipeline (NJ's PDF source loads via script until the Lambda bundles a PDF parser). NPI handling is conservative: state-published NPIs only, validated by check digit — no name matching.
- **`facility_affiliations` on DAC provider records and the `/provider/{npi}` dossier** — all facility types a clinician is affiliated with (hospital, home health, hospice, nursing home, dialysis, IRF, LTCH), with CCN and facility type. The existing `hospital_affiliations` field keeps its shape and now carries hospital names joined from our facilities data.
- **Authorized Official employment signals** — every NPPES Type 2 organization names an Authorized Official; when that name uniquely matches exactly one individual NPI nationally, an attribution signal now links the two (687,084 signals, source `nppes_authorized_official`, confidence 0.60). Signals carry a new `employer_npi` identifier field — the first signal source whose employer is identified by NPI rather than EIN or PAC ID — visible in `/provider/{npi}/employment.attribution_signals[]`.
- **License-state corroboration signals** — 1.24M signals (source `nppes_address_match`, type `state_license_address`, confidence 0.70) marking providers whose active license state matches their PECOS employer's primary state. This signal class existed in code but had never successfully emitted — its signal type was never whitelisted and its upsert broke when the license natural key changed; both fixed.


### Changed
- **DAC, PECOS reassignment, and NPPES refreshed** after 66 days stale — DAC to the 2026-07-31 file (2.01M rows), reassignment re-derived (1.77M), NPPES to the August file (9.73M providers, now including Authorized Official name/title/credential on 1.96M organizations). `/sources` freshness reflects the reloads.
- **Source freshness now distinguishes "no refresh commitment" from "late"** — sources with no expected rhythm (hand-curated mappings, static reference crosswalks, as-needed enrichment, declared-but-not-yet-live loaders) previously fell back to monthly staleness thresholds and showed permanently stale badges at `/sources`. They now report an explicit `exempt` freshness with the reason, and never degrade `/status`. A genuinely late scheduled source still turns `/status` amber or red exactly as before.
- **`ca_medical_board` source entry declares its real cadence** — the entry is NPPES-derived (no state loader exists yet) but declared weekly cadence, so it drifted to "critical" by construction and kept `/status` red for two months. Now monthly, matching the NPPES reload cycle that actually feeds it; the notes field says so explicitly.
- **Hospital Financial & Regulatory Deep-Dive** — a coordinated set of additions closing several hospital financial/regulatory gaps in one pass. See `docs/HOSPITAL_FINANCIALS.md` (published at `/resources/hospital-financials`) for the full writeup.
  - **HCRIS deep worksheet parse: balance sheet, charity care, cost centers, DSH** — the HCRIS loader previously extracted 8 numeric coordinates from the same NMRC file we already download quarterly; it now extracts across four more worksheet domains. New tables: `hcris_balance_sheet` (Worksheet G — full balance sheet with derived current/quick/debt-to-equity ratios), `hcris_uncompensated_care` (Worksheet S-10 — charity care charges/costs, bad debt, total uncompensated care), `hcris_cost_centers` (Worksheet A + C Part I — charges, total costs, and salary/non-salary split for ~20 named cost centers plus all other reported lines). New `hcris_reports` fields: `operating_income`, `other_income`, `total_salaries`, `dsh_patient_pct`, `dsh_payment_amount`, `medicare_days`/`medicaid_days`/`medicare_discharges`/`medicaid_discharges` (populating the previously-unused `medicare_days_pct`/`medicaid_days_pct` columns). New endpoints: `GET /cost-reports/{ccn}/balance-sheet`, `GET /cost-reports/{ccn}/charity-care`, `GET /cost-reports/{ccn}/cost-centers`. Every coordinate was empirically validated by reconciling internal worksheet subtotals and cross-checking against a real hospital's published financials — every headline figure matched exactly or within trivial rounding.
  - **Hospital Readmissions Reduction Program (HRRP) penalties** — new `hrrp_penalties` and `hrrp_measures` tables loaded from the CMS IPPS Final Rule HRRP Supplemental Data Files (FY2021–FY2026). Three new endpoints under `/vbc/`: `GET /vbc/hrrp/{ccn}` (payment adjustment factor history), `GET /vbc/hrrp/{ccn}/measures` (per-condition excess readmission ratios for AMI, COPD, HF, pneumonia, CABG, THA/TKA), and `GET /vbc/hrrp` (search/rank hospitals by penalty severity). `payment_reduction_pct` is always derived from the payment adjustment factor rather than trusted from CMS's own column, whose unit is inconsistent across fiscal years in the source files.
  - **Case Mix Index (CMI) and wage index** — new `ipps_impact` table loaded from the CMS IPPS Final Rule Impact File (FY2023–FY2026). New endpoint `GET /hospitals/{ccn}/case-mix-index` returns the full fiscal-year history (CMI, transfer-adjusted CMI, wage index, DSH patient percentage, resident-to-ADC ratio); `GET /hospitals/{ccn}` now includes a compact `case_mix_index` block with the latest year. CMI is always resolved to the current MS-DRG grouper version for that fiscal year, so a schema change isn't needed when CMS increments the grouper annually.
  - **Hospital service lines, accreditation, and teaching status** — CMS Provider of Services (POS) file now feeds a new `facility_services` table (~50 curated clinical service-line flags — cardiac cath lab, open heart surgery, NICU, transplant services, imaging, behavioral health, and more) plus new `facilities` columns for accreditation agency/dates and medical school affiliation. New endpoint `GET /hospitals/{ccn}/services`; `GET /hospitals/{ccn}` now includes `accreditation_agency`, `accreditation_expiration_date`, and `medical_school_affiliation`. Accreditation-organization and teaching-status code labels were empirically validated (not pulled from an official crosswalk, which wasn't accessible) against known accreditor market share before shipping — unverified codes are stored raw rather than guessed. `scripts/enrichment/enrich_from_pos.py` gains two new always-refresh enrichment passes (service availability and accreditation legitimately change over time, unlike the existing backfill-only legal-name/bed-count passes).
  - **CBSA (metro/micro area) population estimates** — new `cbsa_population` table loaded from Census Bureau annual estimates (2020-2025). `GET /hospitals/{ccn}` now includes a `cbsa_population` block (latest year + year-over-year growth); new endpoint `GET /market-intelligence/cbsa/{cbsa_code}/population` for the full history.
  - **ClinicalTrials.gov integration** — new `clinical_trials`, `clinical_trial_sites`, and `clinical_trial_investigators` tables loaded from the free ClinicalTrials.gov API v2 (193,782 US-located studies, 2.37M site records, 188K investigators). Site-to-facility and investigator-to-NPI matching uses a conservative normalized name + location match (109,684 sites and 82,639 investigators matched with zero ambiguous guesses — ties are left unmatched rather than resolved arbitrarily). New endpoints: `GET /clinical-trials` (search), `GET /clinical-trials/{nct_id}` (detail with matched sites/investigators), `GET /hospitals/{ccn}/clinical-trials` (facility rollup by status/phase), `GET /provider/{npi}/clinical-trials` (investigator's trials). `GET /provider/{npi}` now includes a compact `clinical_trials` dossier block.
  - **Facility square footage, depreciation, and EBITDA** — extends the same HCRIS Worksheet A/B-1/C parse: `hcris_cost_centers` now carries `square_footage` per cost center (Worksheet B-1 reuses the identical cost-center line numbers already validated), and `hcris_reports` gains `total_facility_sqft`, `depreciation_expense` (Worksheet A-7 Part III), and a derived `ebitda` (operating income + depreciation). Closes the EBITDA gap explicitly left open in the initial HCRIS deep-parse work, which needed a depreciation figure not yet located at the time. All figures validated exactly or within trivial rounding against a real hospital's published numbers.
- **Enterprise contact-sales tier** on the dashboard pricing section — the $1,499/mo tier is renamed "Scale" and a new custom-priced Enterprise tier covers the employment + financial intelligence lines, custom data cuts, security review, and MSA. Procurement note added: 100% public and licensed data, no PHI, no BAA required.

### Fixed
- **Column lookups now fall back to a case-insensitive match.** CMS re-cases column names between files: the MSSP savings rate is `Sav_rate` in twelve files and `Sav_Rate` in PY2021, and the ambulance expenditure is `CapAnn_AmbPay` except in PY2014, where it is `CapAnn_ambpay`. The loader's fixed set of name variants missed both and produced silent NULLs for a whole year. Found by checking every field against every file header rather than by spot-checking.
- **ACO REACH participant records were dropped on load.** The table held a unique key on ACO, performance year, and participant name. CMS publishes one row per participation spell instead. A clinician whose capitation arrangement changes mid-year is several rows with several start dates. The key discarded 16% to 33% of each year's published rows. The key is removed. The loader now collapses only rows that are identical in every column. A REACH participant list is a set of dated spells, not a set of names.
- **Hospital affiliations were empty on DAC and dossier endpoints** — CMS removed the `hosp_afl_*` columns from the DAC national file in 2026; the affected endpoints read those columns and silently returned no affiliations. They now read the Facility Affiliation dataset (2.25M rows). `GET /dac/hospital/{ccn}/affiliates` matches providers through the same dataset.
- **Weekly CMS quality data resumed after a months-long silent stall** — CMS replaced its provider-data archive API in 2026. The old endpoint froze its index around Feb–April and later returned 503, so the weekly sync saw "no new data" and hospital, nursing home, hospice, and other CMS quality sources stopped updating (hospitals and hospice last refreshed 2026-02). All downloaders now use the new archive API, and the stalled quarters are backfilled.
- **`/status` and `/sources` no longer hide staleness on sources without load-tracking history** — a source with no recorded load timestamp used to classify as "never loaded" and was excluded from the health rollup, which is how the stall above went unflagged. A source that has never recorded a load is now judged by its age since registration on the same cadence thresholds, so it turns stale/critical like any other source.
- **`GET /hospitals/{ccn}` returned 500 for every hospital from ~2026-07-23 to 2026-08-13** — the hospital deep-dive code deployed on 07-23, but its database migrations were not applied to production, so every detail request failed on a missing table. Search, history, and other endpoints were unaffected. Fixed by applying migrations 045–059 and loading the six new data sources (HRRP, CMI/wage index, service lines, CBSA population, HCRIS deep worksheets, ClinicalTrials.gov) to production. Two guards now prevent recurrence: migrations run automatically before each deploy and abort it on failure, and a synthetic probe checks the public endpoints every 5 minutes and alarms on consecutive failures.
- **Hospice facility count corrected: 8,391, not 14,067** — the 2020-08 and 2020-11 CMS hospice files quote CCNs and ZIPs in Excel's text-forcing format (`="011500"`). The loader stored the wrapper literally, which created 5,676 duplicate facility rows under phantom IDs and ~823K quality/patient-experience rows that never joined to the real facility. The duplicate rows are removed, the historical measure rows are re-keyed to the correct CCNs (no history lost), and the loader now strips the wrapper so reloads cannot reintroduce it. If you had paged through `/hospice`, roughly 40% of rows were duplicates of other rows; per-CCN lookups for the affected facilities previously required the wrapped literal and now work with the plain CCN.
- **HCRIS `net_income` was silently wrong for every hospital** — the worksheet coordinate pointed at line 02500 ("Other Income"), not the actual Net Income line (02900). For a hospital with material non-operating income, this understated net income by orders of magnitude. Also fixed: `total_discharges` was reading a Medicare-days count instead of an actual discharge count, which meant `cost_per_discharge` was calculated against the wrong denominator for every loaded report. Both are corrected in the loader; existing rows are corrected on the next reload.
- **HCRIS "latest report" queries could nondeterministically return a stale, superseded report** — `/cost-reports/{ccn}` and related endpoints picked the most recent report by fiscal year end with no tiebreaker, so a hospital with two report submissions for the same fiscal year (e.g. an amended resubmission) could have either one served depending on unspecified row order. Now breaks ties by preferring the higher `rpt_rec_num` (more recently processed). Affects roughly 13% of loaded report-years.
- **HCRIS balance sheet quick ratio now uses the standard definition** (current assets minus inventory and prepaid expenses) rather than summing individual line items, which understated it.
- **HCRIS square footage bounded against known data-entry anomalies** — a small number of source submissions carry physically-impossible square footage values (one report showed 22 billion sq ft; a worksheet subtotal line was being read as if it were a single cost center, showing up to 200M sq ft). Both are now excluded/nulled rather than surfaced.

### Changed
- **Dashboard redesigned on the Verzi corporate brand** (verzihealth.com): deep green-black theme (`#00110F`), turquoise accent, pill CTAs, JetBrains Mono as the data voice. The homepage hero now shows a **live pipeline ledger** — real per-source refresh dates and freshness straight from the public `/sources` registry — and a full **data atlas** table of all 45 sources with license, cadence, and last-load date. Every headline stat carries a provenance chip linking to the registry. PDF/print export keeps the light palette.
- **Dashboard homepage hero** rewritten verify-first: "Provider data you can actually verify," with direct links to `/sources` and `/accuracy` and a "Check Our Accuracy" call-to-action. Site metadata updated to match.

### Removed
- **Named-system case study retired from `/resources`** — the catalog now carries methodology + product docs only. The accuracy benchmark at `/accuracy` continues to publish per-system variance; systems under private evaluation are excluded from public surfaces.

---

## [2026-07-08]

### Added
- **Per-source freshness monitor** — `/status` and `/sources` now show whether each registered source is refreshing within its declared cadence. New shared module `api/providers/_shared/freshness.py` classifies each `source_registry` row as `fresh` / `stale` / `critical` / `never` against per-cadence thresholds (daily = 2/8d, weekly = 10/21d, monthly = 45/100d, quarterly = 100/200d, annual = 400/550d). Customer-facing consequence: if we say a source is monthly and it's actually 4 months stale, `/sources` shows a red "stale · 121d" badge with an explanatory note instead of hiding the drift. The `/status` pipeline check switches from "look at the single most recent successful run" (which was silently masking slow-cadence outages) to a per-source rollup that fails when any source is critical. Three CloudWatch alarms in `infra/template.yaml` (`PipelineNoRunAlarm` 25h, `PipelineWeeklyAlarm` 8d, `PipelineFailureAlarm` any) implement the same principle server-side. Alarm on the absence of success, not just the presence of failure.
- **NPPES Authorized Official schema** — migration 052 adds 8 AO columns (last/first/middle name, title, credential, name prefix/suffix, phone) to `nppes_providers` plus a partial index on the AO name pair for Type 2 orgs. Loader updated to capture them from the NPPES bulk file. Fields will populate on the next NPPES full-refresh run (monthly cadence). Unblocks the future `authorized_official` attribution signal migration.
- **Tenet Healthcare decomposition** — 6 new `system_hierarchy` rows: TENET parent + TENET_USPI, TENET_HEALTHSYSTEM, TENET_CALIFORNIA, TENET_FLORIDA, TENET_HOSPITALS. Sourced from CMS Hospital All Owners disclosures (60 hospitals under TENET HEALTHCARE CORPORATION + 24 USPI + 18 regional). EIN 952557091 linked. `/systems/TENET` now returns a real rollup.
- **UHS decomposition** — 7 new rows: UHS parent + UHS_ACUTE + UHS_BEHAVIORAL + UHS_DELAWARE + UHS_HOLDING + UHS_GEORGIA + UHS_PENNSYLVANIA. Only public behavioral-health rollup at scale (Provo Canyon, Cambridge, Cygnet). 145 hospitals under UNIVERSAL HEALTH SERVICES INC. EIN 232077891 linked.
- **Surgery Partners decomposition** — 3 new rows: SGRY parent + SGRY_ASC + SGRY_STONEBRIDGE. ASC roll-up; 17 direct facilities. EIN 473620923 linked. Unique in the market — no other public API exposes ASC ownership at NPI level.
- **Amazon One Medical decomposition** — 1 new row: ONE_MEDICAL as standalone primary-care group. Identified via 4 PECOS org_pac_id matches (ONE MEDICAL GROUP INC, ONE MEDICAL OF NY PC, ONE MEDICAL SENIORS GROUP INC, ONE MEDICAL P.C.). Amazon acquired for $3.9B Feb 2023.
- **IRS 990 XML index for 2020 + 2021** — 989K more rows loaded after fixing the `sub_date VARCHAR(4)` schema constraint (migration 049 widened to TEXT to accept the legacy `1/2/2020 3:03:27 PM` format). Combined with 2022-2024, healthcare-EIN discovery graph now spans 5 tax years (~2.7M index rows).

### Fixed
- **6 remaining sec_10k EINs** — Acadia, American Well, LifeStance, Privia Health, Select Medical, Pennant Group all previously had CIK-in-EIN or SEC placeholder `000000000`. Backfilled real 9-digit EINs by parsing each company's most recent 10-K cover page for the `XX-XXXXXXX` pattern. All 30 `org_eins.sec_10k` rows now have valid EINs.

### Removed
- **QPP MIPS TIN loader** was killed as a plan item after investigation confirmed CMS redacts TIN across all public files (Doctors and Clinicians, PECOS Individual Enrollment, QPP annual). No public path exists; moved to DATA_GAPS Tier 2.1 with external-constraint status.

---

## [2026-07-07]

### Added
- **DOL Form 5500 healthcare backfill** — loaded 107K healthcare-NAICS filings across tax years 2020-2024 (peak years 2021-2024 each cover 24-27K filings representing ~26M W-2 employees per year). Data lives in `dol_5500_filings` (raw) and `financial_filings` (aggregated per EIN × tax period). Powers the `/systems/{id}/financials/workforce` endpoint for any org whose EIN is known. Confirmed correct at the top of the distribution — Trinity Health at 158K W-2 employees (2023), Kaiser 149K, matching each system's publicly-stated head-counts.
- **DOL 5500 employment attribution signals** — 222,138 new signals in `employment_attribution_signals` with `source='dol_5500'`, `signal_type='participant'`, `confidence=0.55`. Cross-cites the DOL W-2 employer floor for NPIs whose PECOS reassignment org matches a linked EIN. Top employers surfaced: NYU (5,724 NPIs), Mayo Clinic (4,899), Montefiore (2,797), Henry Ford (2,538). Ready to grow substantially once the org_eins fuzzy-match review lands.
- **IRS 990 XML index for 2022-2023** — 1.36M new `irs_990_index` rows spanning both years. Combined with the pre-existing 2024 index, the healthcare EIN discovery graph now covers three tax years. 2020-2021 blocked on a `tax_period VARCHAR(4)` constraint issue (older years use different format); tracked in DATA_GAPS.

### Fixed
- **`org_eins` CIK-in-EIN data-model bug** — 30 rows sourced from SEC 10-K filings were storing the SEC EDGAR CIK in the `ein` column (variable-width, not a real EIN). Migration 047 added a proper `cik` column and moved the misplaced values. `scripts/providers/sec_filings/backfill_eins.py` then backfilled 25 real 9-digit EINs via SEC EDGAR submissions API (e.g. Tenet now correctly resolves to EIN 952557091 instead of CIK 70318). Remaining 5 issuers (American Well, LifeStance, Privia Health, Select Medical, Pennant Group) return SEC's placeholder `000000000` and are documented as a follow-up gap.
- **`org_eins.org_pac_id` first population** — 51 rows auto-linked to PECOS `org_pac_id` via trigram fuzzy match (similarity ≥ 0.85) using `scripts/providers/sec_filings/link_org_eins_to_pac.py`. 198 ambiguous candidates written to `data/ein_pac_id_review.csv` for human triage.

---

## [2026-07-02]

### Added
- **`/my-usage` HTML dashboard** — the authenticated usage endpoint now serves a styled dashboard to browsers with a tier badge, color-coded usage progress bar (green under 50%, amber under 90%, red at 90%+), tile grid, and a contextual upgrade CTA. JSON shape unchanged for API clients; a new `billing_tier` key is additive.
- **JSON-LD structured data** on the six main marketing pages (`/`, `/pricing`, `/quickstart`, `/sources`, `/accuracy`, `/legal/data-disclosure`). Enables Google Rich Results eligibility — pricing tiles as native price cards, quickstart as a HowTo card, `/sources` as a Dataset result.
- **`/changelog`** — public user-visible change log (this file). Content-negotiated: browsers get styled HTML, `curl` gets raw markdown.
- **`/status`** — public traffic-light health page. Shows real-time state of the four dependencies (PostgreSQL, S3, SES, pipeline freshness).
- **`/health/deep`** — deep health check endpoint. Runs live dependency probes. `/health` remains shallow + fast for CI polling.
- **`/examples`** and **`/examples/{lang}`** — copy-paste ready code samples in Python, Node.js, curl, Go, and PHP. Browsers get a styled page; `curl` gets the raw source file with the correct MIME type.
- **OpenAPI/Swagger enrichment** — 12 endpoints across `hospitals`, `nursing-homes`, `exclusions`, `provider` (npi_profile), `licenses`, and `mips` routers now carry `summary`, `description`, path/query examples, and response payload examples. The Swagger UI at `/docs` "Try it" form now auto-fills with real CCNs, NPIs, and state codes.
- **Sitemap now includes `/resources/{slug}` and `/examples/{lang}` URLs** with `lastmod` pulled from each file's mtime — helps search engines prioritize recently-updated content.

### Changed
- `/pricing` "Start with Free" CTAs pre-wired to `/billing/checkout?tier=starter` and `?tier=pro`. Currently return `503` until Stripe env vars are set; will flip on live automatically the moment the keys land.
- `/quickstart` "What's next" section now leads with `/examples` as the primary CTA (was "Full API Reference").
- `/health` split into shallow (`/health`, unchanged, fast) vs deep (`/health/deep`, runs real dependency probes).

---

## [2026-06-28]

### Added
- **`/quickstart`** — 5-minute developer integration guide with tabbed curl / Python / JavaScript samples, endpoint gallery, rate-limit callouts.
- **Open Graph social preview cards** — 1200×630 branded PNGs on every marketing page. LinkedIn / Twitter / Slack previews now render with a proper card instead of a bare URL.
- **`/sources/{source_id}`** now returns a styled HTML detail page in browsers (was JSON-only). Completes the `/sources/*` HTML family (list, detail, history).
- **`/resources` catalog + `/resources/{slug}`** rendered documents — the methodology docs (API Style Guide, Accuracy Benchmark, License Intelligence, Financial Intelligence, Employment Reconciliation) are now browsable as styled HTML with the site's brand chrome. `curl` still gets raw markdown.
- **`robots.txt`** and **`sitemap.xml`** for search-engine discovery. Allows public marketing surfaces; blocks admin / billing internals.

### Fixed
- `/billing/plans` legacy URL 301s to the canonical `/pricing`. Old page said "17 data sources" and referenced the wrong tier limits.

---

## [2026-06-27]

### Added
- **`/sources/{source_id}/history`** HTML — per-source refresh audit timeline with status-colored markers, four stat tiles (session count, success rate, last run, last status), and a smart empty state.
- **`/accuracy`** HTML — the marquee proof page. Four summary tiles (median variance, mean variance, systems within ±10%, within ±25%), methodology callout, sorted results table with color-coded variance (green/amber/red), and a ground-truth submission invitation.
- **`/platform/stats`** HTML — live dashboard with pulse-animated "refreshed Xs ago" badge, four stat tiles (data rows, sources, facilities, clinicians), last-pipeline-run card with status pill.
- SEO + Open Graph meta tags across seven public pages (description, `og:title`, `og:description`, `og:type`, `og:site_name`, `og:url`, `twitter:card`).
- **`/pricing`** page with four tiers (Free / $49 Starter / $199 Pro / Enterprise), FAQ, and cross-sell to the executive dashboard.
- **Shared content-negotiation helper** (`api/providers/_shared/content_negotiation.py:prefers_html`) — same URL serves HTML to browsers and JSON to API clients based on `Accept` header + explicit `?format=` override.

### Changed
- **`BILLING_TIERS`** aligned with the new 4-tier pricing (Free 10K/day, Starter 50K/day, Pro 500K/day, Enterprise 10M/day). Was 3 tiers with stale limits.
- **`/billing/checkout`** accepts a `?tier=` query param (starter or pro). Tier flows through Stripe checkout metadata so the webhook upgrades to the right tier.

### Fixed
- `test_admin::test_get_organization` was failing because it referenced a `starter` tier that didn't exist in `BILLING_TIERS`. Now passes.

---

## [2026-06-26]

### Added
- **Live source catalog at `/sources`** — every dataset we ingest with license, refresh cadence, accuracy class, and last-loaded state. 38 sources across CMS (13), other federal (5 — OIG, HRSA, CDC, AHRQ, Census/OMB), academic (1 — Dartmouth), state open-data (1 — CA Medical Board), commercial (2 — Google Maps, FMP), and Verzi-original layers (13). Same URL serves JSON to API clients.
- **Per-source refresh history at `/sources/{source_id}/history`** — every load attempt, success or failure, with timing and row-count deltas.
- **`/platform/stats`** endpoint — live row counts, source counts, facility / clinician counts, last pipeline run status. Backs the marketing numbers without hardcoding them.
- **Data Use & Disclosure Statement** at `/legal/data-disclosure` — canonical, version-controlled statement of which datasets we ingest, under what license, and our HIPAA classification. Maintained outside the auto-generated Terms/Privacy so Termly regenerations can't wipe substantive disclosure.
- **LoadSession instrumentation** — every pipeline loader run now records a session row (`source_id`, `started_at`, `completed_at`, `status`, `rows_loaded`, `error_message`) to the `load_sessions` table.

### Changed
- Homepage stats now fetch from `/platform/stats` via Next.js ISR (1-hour revalidation) instead of hardcoded strings. Row counts stop drifting.
- Data source attribution rewritten across README, `PROJECT_OVERVIEW`, `terms.html`, `privacy.html`, and the homepage to reflect the full 30+ source catalog (CMS, HHS OIG, HRSA, CDC, AHRQ, IRS, DOL, SEC EDGAR, Census, Dartmouth, plus commercial APIs) instead of claiming CMS-only. Every `312M+ rows` / `27 sources` reference updated to live counts or durable phrasing.
- Signup form bot-prevention hardening: honeypot field, disposable email denylist, per-IP rate limit, Cloudflare Turnstile scaffolded and env-flag-gated.

### Fixed
- Migration tooling: `run_migrations.py --stamp` bootstrap flag, cleaner prod auth flow via AWS Secrets Manager (`cms-dev/postgres-credentials`), fixed URL-encoding issue with prod password.

---

## [2026-06-17]

### Added
- **Financial Intelligence** product layer — IRS Form 990 deep XML pipeline (Schedule R + Schedule J), DOL Form 5500 loader, SEC EDGAR 10-K via FMP for 30 curated public healthcare tickers.
- **`financial_filings`** table — cross-source EIN × filing_type × tax_period_end unified store. 15K+ rows.
- **`executive_compensation`** table — Schedule J + 10-K NEO records, 267 entries with matched NPIs for clinician-executives.
- **Financial Intelligence API endpoints**: `/systems/{id}/financials`, `/systems/{id}/financials/executives`, `/systems/{id}/financials/workforce`, `/companies/{ticker}`, `/companies/search`, `/ma-events/recent`, `/provider/{npi}/financial-affiliation`.

---

## [2026-06-16]

### Changed
- **API consolidation**: `/quality` nested under `/provider`, `/quality` router renamed to `/mips`, fixed `/systems` route collision.

---

## [2026-06-11]

### Added
- **License + Quality Intelligence v1**
  - `provider_licenses` (5M+ NPI-keyed licenses derived from NPPES taxonomy across all 50 states)
  - `license_disciplinary_actions` (8.5K OIG LEIE joined to NPI)
  - License Intelligence API: `/licenses/state/{state}`, `/licenses/{npi}`, `/licenses/{npi}/disciplinary`, `/licenses/alerts/recent`
  - Quality Intelligence API: `/quality/{npi}`, `/quality/leaderboards/{specialty}`, `/quality/systems/{system_id}`
  - Extended `/provider/{npi}` dossier with new `license_intelligence`, `quality_intelligence`, `employment_intelligence` blocks

---

## [2026-06-10]

### Added
- **Physician Employment Intelligence v2** — multi-source attribution + system decomposition
  - `org_eins`, `employment_attribution_signals`, `system_hierarchy` tables
  - Large national systems decomposed into operating divisions
  - Accuracy benchmark at `/accuracy` — per-system variance vs publicly-stated counts
- **Physician Employment Intelligence v1** — initial employer typology product
  - `physician_employer_systems`, `employer_facts` materialized view
  - 12 API endpoints at `/provider/*`, `/employers/*`, `/systems/*`

---

## Older

Prior to 2026-06-10, changes were tracked via git log only. The last major additions before that date were VBC/ACO analytics, HCAHPS benchmarking materialized views, market intelligence (HSAF), community health (CDC PLACES, Medicare Geographic Variation, AHRQ SDOH), and the initial 9 CMS provider-type ingestion.

---

## How to read this

- **Added** — new endpoints, tables, or user-visible features
- **Changed** — modifications to existing behavior (usually backward-compatible)
- **Fixed** — bugs or inconsistencies resolved
- **Deprecated** — features scheduled for removal (none yet)
- **Removed** — features removed (none yet)
- **Security** — security-relevant changes (none breaking; hardening only)

For the machine-readable API changelog, hit `curl https://api.healthcaredata.io/changelog` — returns raw markdown.
