# 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
- **The KB has its own address: `kb.verzi.health`.** The root opens the KB. The site menu and `app.verzi.health` now point there. Links to `api.verzi.health/kb/...` keep working.
- **KB toolbar and side rail.** Each LCD, Article and code page has one toolbar under the header. It holds the date of service and State. In the paid view it also holds Find in policy, Copy + cite (copies selected CMS text with its version citation) and the appeal exhibit. On a wide screen it stays at the top while you scroll, with the not-in-force banner. A right rail holds "Requirements at a glance": this version's approved labels, grouped by kind, each a button to its highlighted passage. The related documents CMS lists moved to the rail. A one-line legend explains the teal highlight.
- **Two more lists in provider standing.** `GET /provider/{npi}/standing` returns `sam_exclusion` (listed on the SAM.gov exclusion list on the date of service) and `medicare_opt_out` (opted out of Medicare on the date of service), each from the archived list in force on that date and each with its own receipt that names the list (`source_list`). They never feed the OIG `exclusion` block. A date no held version covers answers `unknown` with a reason. SAM history starts at our first capture in 2026; the opt-out list starts at the April 2023 release, in force from its release day (about three weeks after the month).

### Changed
- KB Copy + cite copies CMS's words only. Words Verzi writes inside the policy text (the section headings Verzi names, table column names, N/A) are left out, with a "[...]" line where they were; the citation names each section the quote spans. Paragraphs and list items end with a line break and table cells with a tab. Copy + cite refuses a selection in several pieces.
- **NCCI PTP answers for dates before 2022 cover HCPCS Level II pairs only.** For a date of service before 2022-01-01, `GET /rules/ncci/ptp`, the edits block of `GET /rules/warrant` and `GET /rules/warrant/appeal`, and `GET /rules/warrant/changes` answer a pair only when both codes are public HCPCS Level II (first character A to V, not D). A pair with any other code, such as a CPT code or a D code (CDT), answers `unknown` (`edited: null` on `/rules/ncci/ptp`) with a reason. On `/rules/ncci/ptp` and the edits block, `checked.rule` is `license_scope`. No answer changes today: Verzi holds no PTP file read for a date before 2024-04-01 yet.
- **A KB page says when the version shown is not the one in force today.** A quiet banner sticks under the top bar: "You are viewing version 20 (2020-01-01 to 2021-08-07). Version 52 is in force today.", with the in-force version as a link when the KB holds its page. It shows for an older version, a version not yet in force, and a document with no version in force today; it does not print. CMS's quoted revision note at the top now shows only on the version in force today; CMS's full log stays in the revision history section.
- **KB receipts are built for an auditor.** The "Receipts for auditors" table reads Document, Version, In force, Archived on and Verify. Verify links CMS's own copy of that version and Verzi's archived file. The table carries no hash. Article versions are their own rows, once each, after the LCD rows; the repeated "no version in force" lines are gone. Every small receipt toggle on the page is now a "source" toggle with the same two links. A new page, `/kb/file/{sha256}`, shows one archived file: its document, capture time and URL, size, sha256 fingerprint and how to check it, the structured rendering's sha256 and the archive file id. A signed-in KB account gets a one-hour download link for the original file there; an anonymous reader gets a sign-in link, never the download. Files from sources whose license carries CPT descriptors (the AMA license) offer no download. The appeal exhibit keeps the full receipt under each passage.
- **KB policy and code pages end with the main template's bottom sections.** Under the text, each page shows its version history (one box per version, "v20" over its window, with a button that shows the versions superseded the same day they took effect), then CMS's revision history as its own open section, then one "Receipts for auditors" box. A receipt line under the title names the version shown and its dates. On a code page, the free list of in-force documents no longer repeats the LCD the page shows, a signed-in customer gets those documents' text without a second list, and CMS's code-to-policy list moves into the receipts. A document version with no stored text rendering now says so, instead of a hash failure line.
- **A KB code page now holds its policy, in the main template.** `/kb/code/E0601` shows the code large, the LCD's title, CMS's descriptor, and then the LCD itself: the DME MAC jurisdiction row, CMS's code list, the revision note, the date of service picker, the version chips, the sections, the customer line and the receipts. The picker and the chips reload the code page, so one date drives every block. When CMS lists the code in more than one LCD with a KB page, chips switch between them (`?doc=`). A signed-in customer gets the LCD's verbatim text with its highlights, and below it the approved passages and CMS text of the code's other documents, such as its Article. A version id from another document shows the newest version and says so. A code with no LCD page keeps the old page. The policy page `/kb/lcd/{num}` is unchanged.
- **The applicability warrant answers faster on a cache hit, and fails closed on rendering drift like `w3` already does.** It now reads a document's text rendering through `w3`'s existing cache, instead of fetching it from the store, unchecked, on every request. A stored rendering that no longer matches its recorded hash now degrades that basis to `proxy_only` instead of quoting a span from drifted text. No field or shape changed otherwise.
- **The `w3` and `applicability` warrant blocks answer faster.** Both resolve a code's governing documents (an LCD and its Article, commonly two) in one batched as-of lookup instead of one query per document. Response bodies are byte-identical; no field or shape changed.
- **The warrant endpoint answers faster.** `/rules/warrant`'s seven to eight blocks (`w0`, `w1`, `w2`, `w3`, `exclusions`, `applicability`, `prior_authorization`, and `w0_ordering` when `ordering_npi` is set) run concurrently instead of one after another. They are independent reads; no field or shape changed. Measured on the five demo calls: 44 to 60% faster (for example 12.8s to 5.1s).
- **W2 PTP answers from CMS's PTP file in force on the date of service.** `/rules/ncci/ptp`, the warrant's `w2.ptp` block and the change timeline read the complete quarter file for the date's quarter, from the change-only store, and name it in `checked` (rule `ptp_edition_in_force`) and in `as_of.version_in_force`. Before, the newest held file answered for every date and the citation always named it; a pair CMS dropped from a later file answered `not_edited` for dates on which the edit was in force, and a date before the first held file answered from a 2026 file. A date whose quarter has no complete file now answers `edited: null` with a `reason` that names the quarter and the nearest files read; no other quarter's file answers for it (on 2026-10-09 the files read are 2024Q2 to 2025Q3 and 2026Q3 to 2026Q4, both settings). A `not_edited` answer cites the pair's newest row, with `present: false` and `recorded_in` when CMS's file no longer lists it. The fact's `citation` names the archived part file whose bytes carry the row (the file that recorded it, never a newer one).
- **Codes are checked and upper-cased on every rules and warrant route, and on the MCP warrant tools.** A code is five characters, digits or capital letters. `hcpcs=e0601`, `code_a=e0601` and `modifier=rr` now read as `E0601` and `RR`; before, the fee route answered 404 for a lowercase key. A value that is not a code (`1312`), or a code paired with itself (`code_a=13121&code_b=13121`, `companion` equal to `hcpcs`), is a 422; before, PTP answered `edited: false` for them.
- **The fee route states what 0.0 means.** `/rules/dmepos/fee` and the warrant's `w1` serve the file's own ceiling and floor; CMS's DMEPOS README says the fields are filled with zeros for codes adjusted using competitive bidding information, so `0.0` is CMS's value (E0601 RR), and `null` means the file leaves the cell blank or the area is not subject to them.
- **`/kb/metrics` serves its cached numbers at once and refreshes them in the background.** The first request after a deploy no longer waits about 20 s: the cache fills at app start, and an expired value serves while one refresh runs (`meta.stale`, `meta.refresh_error`).
- **W3 serves the in-force LCD or Article verbatim, marked unlabeled, when no approved label is in force.** A missing label no longer makes `warrants.w3` answer `not_yet_available`. Each in-force document now carries `label_status` (`labeled` or `unlabeled`). An unlabeled document carries its version, its title, and a `receipt` (archived file, sha256, rendering sha256, fetch time, publisher URL). `warrants.w3` never carries the document text. Entitled KB readers and keys get `document_text`, CMS's full text for that version, from `/kb/code/{hcpcs}/passages`, served only when it matches its stored hash (else `text_available: false`). `coverage.completeness` is `unlabeled` when no approved label serves, and `coverage.documents_unlabeled` counts these documents. The confidence rollup grades an unlabeled W3 as `partial`. The KB code page lists the in-force documents with their receipts; signed-in customers get the full CMS text.
- **The sample integration page needs a signed-in KB session.** It moved under the KB path (the KB sign-in reaches it) and shows only to readers whose plan includes the warrant; anyone else gets 404. The old path is gone.
- **Private beta wording on the docs landing and signup.** Badge says "private beta" instead of "self-serve keys"; signup says "Free key on request" instead of "Self-serve key"; the quickstart notes that keys are approved by hand during the beta.
- **Footer updated across all public pages.** The copyright line is now "(c) 2026 Verzi"; the verzi.health pages carry "Rules data, not PHI" and the provider-data pages (/sources, /platform/stats, /accuracy, /status, /my-usage) carry "Public data, not PHI".
- **The free tier card on the docs landing now lists archive and catalog items** instead of "Provider intelligence lookups and aggregates", matching the signup page.
- **CPT rationale blocklist comparison is now case-insensitive and whitespace-tolerant.**
- **The KB's receipts table shows the companion Article under each LCD version.** For every Article CMS relates to the LCD, a row under each LCD version names the Article version in force on that LCD version's stated start date (the as-of rule the warrant uses), with its window, a link to that version's page and the sha256 of the archived bytes. An LCD version with no stated start gets no row.
- **The KB opens on a code search.** Type a HCPCS code or the start of one at `/kb`: the page lists the codes that an LCD lists in CMS's own code-to-document table, with CMS's descriptor, and each code links to its page with the policy documents. The document table stays below it.

### Added
- **The revised January 2021 DMEPOS fee schedule file answers 2021Q1 dates.** CMS posted the January 2021 file twice: `dme21.zip` and, on 2021-03-01, `dme-21ar.zip` ("Revised January 2021 DMEPOS Fee Schedule ... Revised 3/1/21"), filed under the same quarter. The archive now follows that page and versions the revised file as the quarter's second version, with CMS's own marker as its version. On the same start a stated version now ranks above none (migration 164). So `/rules/dmepos/fee` and the warrant's `w1` answer a 2021Q1 date from the revised file once it is captured and versioned, and the first file stays visible as superseded. Before, the revised file was not held and the first file answered.
- **A W1 fee answer says when the file was continued by the publisher.** CMS released no July 2021 DMEPOS fee schedule file; its July 2021 transmittal (R10865) states there are no updates for July 1, 2021, so the April 2021 file stayed in force. `/rules/dmepos/fee` and the warrant's `w1` now answer a 2021Q3 date of service from the April 2021 file. Their `as_of` carries rule `file_quarter_continued` and a `continuation` block: the through date, the archived transmittal's doc id, version id and sha256, and CMS's sentence. Before, those dates answered `not_found` with the gap reason. Nothing changes for a date inside a file's own quarter.
- **KB policy pages carry the main template's version boxes, receipts table and jurisdiction row.** Each version chip now shows its window by the as-of rule ("2020-01-01 to 2021-08-07", "2024-01-01 to today"). Versions superseded the same day they took effect sit behind a toggle, closed by default; it opens when one of them is the version shown. The receipts toggle lists every archived version of the document with its window, capture date and archived bytes sha256 (linked to the file's provenance), then the live CMS pages with the same-version rule and the window rule. A DME MAC jurisdiction row (JA to JD with their states and contract numbers, from CMS's pages) sits under the title; two letters in the State box open the governing jurisdiction.
- **Past NCD versions are captured by number.** The CMS Coverage API lists every version of a National Coverage Determination (`/v1/data/ncd/other-versions/?ncdid=N`) and serves each one with its own effective and end dates. The archive now fetches every past version the list names that it does not hold (206 versions across 112 NCDs on 2026-10-08), so `GET /archive/docs/ncd:{id}/as-of` answers dates before the current version's start once the capture runs.
- **A W2 PTP answer can carry a correction.** A later CMS file can delete a pair on or before a date that the edition in force still lists open. `/rules/ncci/ptp` and the warrant's `w2.ptp` block keep answering from the edition in force. The fact now also carries a `corrections` entry: the later edition, its deletion date, and a note. The entry stays only while every edition after it still agrees. A still-later file that lists the pair open again, or moves the deletion date past the date of service, removes the entry.

### Fixed
- **A KB code page no longer prints an empty rendering hash.** The receipt of an in-force document with no approved label said "Text rendering sha256 ." when the archived file has no stored text rendering (a Coverage API version before its render pass, such as LCD L33718 version 52). The receipt now says "No text rendering is stored for this file yet." The signed-in receipt says it in similar words.
- **W0 keeps a reinstatement that OIG posted after the LEIE snapshot.** The archive dates the monthly reinstatement files by their month since 2026-10-08. The exclusion reader took that month start as the version's posting date. A LEIE snapshot taken inside the month then outranked the file, and a provider reinstated after the snapshot date stayed "excluded". We found this on 2026-10-09 with the 2026-09 file: the reader still excluded a provider on 2026-09-24 whom OIG reinstated on 2026-09-23. The reader now takes the later of the version's stated date and the first day of the following month as its posting date. So the reinstatement file wins over any snapshot taken in its own month. A snapshot from a later month still wins. Versions that carry a Last-Modified date from before the rule keep that date.
- **Coverage API documents carry their titles.** `archive_doc.title` was never set for LCDs, Articles and NCDs captured from the CMS Coverage API, so the KB pages and the coverage warrant showed ids only (for example `article:52467`). A one-time reader records the title each document's own stored JSON states (`data[0].title`), the publisher's string trimmed at the ends and nothing else, from the highest held version. A document first captured later stays untitled until the reader runs again.
- **W2 PTP answered not_edited for pairs CMS edits, when CMS lists the pair twice with the same effective date.** CMS's cumulative PTP files carry, for some pairs, a line deleted the day it starts (modifier indicator 9) and a live line. The stored table kept one row per (pair, effective date), so the live line was dropped at load: 3,948 live edits across the 2026Q4 files, 3,847 of them hospital. The row identity is now (pair, effective date, deletion date), both lines are stored, and the reader picks the one in force. Parts loaded before this fix answer `unknown` ("parsed before the duplicate-key fix; re-parse pending") until they are re-parsed; they never answer `not_edited` from the old rows.
- **Coverage API Article versions answer by the dates CMS states in them.** Article versions captured from the CMS Coverage API were stored without their effective dates, so a date of service could find no version in force (for example article:52467, the CPAP article, on 2026-09-15). Once the stated-dates reader runs after this release, the archive reads each held version's own stored JSON for the window CMS states (article_eff_date, article_rev_end_date) and uses it where the stored row has no date. Stored rows are not changed. Article versions whose JSON states no effective date stay undated.
- **Coverage API Articles carry their effective dates, and missing DME LCD and Article versions are captured by number.** Articles from the CMS Coverage API were stored without their effective dates, because the Article fields were not read. A DME LCD or Article version that CMS's own revision history names, and that the archive did not hold, is now fetched by its version number (131 of the 134 such versions were served on 2026-10-08). A document that still holds an undated version is held back until that version is dated, because a dated past version beside an undated current one would answer for today. Until the capture runs, some dates answer an older version or no version (for example A52457 on 2025-06-15 answers none; version 63 was in force).
- **Excel members of archived NCCI and DMEPOS files now parse.** The parser stores each archived file under a name with no extension, and the Excel reader refused those names, so every Excel-only member was skipped. For NCCI PTP that meant practitioner parts f1 and f2 of 2026Q3 and 2026Q4, which W2 answers as `unknown`. The reader now opens the file directly. The affected quarters answer once they are re-parsed.
- **W0 exclusion answers past windows for reinstated providers.** A provider excluded and later reinstated is no longer in the current LEIE file, so a date of service inside the old exclusion window used to answer "not excluded". The OIG monthly reinstatement files (every month from 2015-01 is retrievable, most from the Wayback Machine) answer it once they are captured and parsed. For the same exclusion, the newer of the LEIE file and the reinstatement file wins. When the archive cannot prove the answer (a provider who left the LEIE file with no reinstatement file held, or a date before the first live snapshot with a reinstatement month not yet parsed), `excluded_on_dos` is null with `status: "unknown"` and a reason. Each exclusion record names its archived file in a new `source` field. A W0 block whose exclusion answer is `unknown` rolls the warrant's `confidence.overall` up to `unknown`.
- **The sample integration page no longer shows CPT-licensed rationale text.** The blocklist filters "Code Descriptor / CPT Instruction" from the MUE cell display.

### Security
- The server access log no longer records query strings. A warrant request can carry a date of service and a patient ZIP, which together can identify a patient. The log keeps the path only.
- The MCP tools `warrant`, `warrant_changes` and `warrant_appeal` now return signed answers, the same Ed25519 attestation the REST warrant routes carry. Verify them offline with the key at `/v1/rules/warrant/signing-key`.

### Changed
- app.verzi.health now opens the KB sign-in. The provider-data dashboard stays at www.healthcaredata.io.
- Responses on api.healthcaredata.io no longer carry Deprecation or successor-version Link headers. healthcaredata.io stays the home of the provider-data product.
- **Pricing is by contact during the private beta.** `/pricing` shows one line: contact adrian@verzi.io for access and pricing. The page is out of the sitemap and marked noindex. `/my-usage` shows the same contact line to Free and Starter accounts instead of prices and a checkout button, and the quickstart's rate-limit note points to it.

### Fixed
- **Receipts no longer show an observed window that ends before it starts.** A version captured from the Wayback Machine (or loaded by a backfill), and any version that states its own dates, now shows no `observed_to`; only undated live captures carry an observed window. A Wayback or backfill version's `observed_from` is the time we stored it, and a dated version can be captured out of stated order, so an end date computed from a later live capture could fall before its start. Answers do not change: the observed window decides an answer only for live captures.
- **Manual chapter dates come only from the same manual's transmittal.** The archive dates a CMS manual chapter revision from the transmittal its "Rev." number names. Before the 2020 unified sequence, each manual numbered its own transmittals, so one number can name a Benefit Policy, a Claims Processing and a State Operations Manual transmittal. Two Benefit Policy chapter 15 revisions (Rev. 235 and Rev. 241) carried State Operations Manual dates; they now show no date until their own transmittals are held. Pre-2019 transmittals with upper-case file names (R3444CP.pdf) are now read as the same documents as the live ones.
- **Edits fail closed when a CMS file is missing.** The warrant's edits block (`w2`), `/rules/ncci/ptp` and `/rules/ncci/mue` answer "not edited" or "no MUE" only from files that are held and parsed. PTP answers from the latest quarter held when it is complete; otherwise from the newest held file that carries the pair; otherwise `unknown` (PTP `edited: null`, MUE `mue_value: null`) with a `reason` naming the missing parts. Every answer, negative included, now names the files it relied on in `checked` (rule, quarter, doc_ids, version_ids). Before this fix, a pair in an unparsed part (for example practitioner part 1 in 2026) answered "not edited" with no receipt. **Client change:** the `/rules/ncci/mue` 404 `detail` is now an object (`message` plus `checked`), not a string. An unknown edits part now makes the warrant's `confidence.overall` read `unknown` (the `w2` entry of `confidence.blocks` adds `mue_status` and `ptp_status`), and `/rules/warrant/changes` reports `changed: null` (and `any_change: null` when nothing else moved) when either date is unknown, instead of `false`.
- **The printed appeal exhibit carries no site chrome.** Printing a KB page no longer prints the top bar (brand, theme button, "Get a key") or the find-in-policy box and its yellow marks.
- **The updates feed shows one entry per document per capture run.** When one run captures several new versions of a document, only the newest is news; the others move to Newly archived. RSS, the weekly digest, alerts, and webhooks follow the feed. This removes about 1,750 identical Noridian rows and the skipped versions of MCD Articles from the 30-day feed.
- **Updates feed headlines name files and pages in words.** Quarterly files read "OPPS Addendum Q, 2026Q4" and "DMEPOS fee schedule, 2026Q4"; downloads carry the publisher's file name ("Medicare Coverage Database download current_lcd.zip", "FDA NDC Directory file ndctext.zip"); CMS pages read "CMS page: code sets". Word slugs show their hyphens and underscores as spaces. Citations and host names keep their characters.
- **One feed entry for an LCD change, not two.** CMS's revision-history file for an LCD or Article, captured in the same run as the document, no longer shows as a separate news entry; the document's entry carries CMS's note. It reads "LCD L33824 revision history" under Newly archived.
- **Source archive page in plain words.** The search box asks for a "document id", and an effective date CMS did not state reads "not stated" instead of a blank cell.
- **Better free samples on KB code pages.** The one free sample passage per code is now chosen by a stated rule: an LCD before an Article, a passage that stands on its own before a fragment (never one that ends pointing at text below it), and coverage criteria before order and documentation rules. E0601 now shows the re-evaluation rule (days 31 to 91); K0606 shows its first coverage criterion.
- **KB pages after hostile pass 3.** A code page whose approved passages are not in force today names the dates that show the sample, as links ("See the sample at: 2020-04-03, version 35"). The sample integration page's policy-passage cell uses the same sentence instead of "Not held yet" when approved passages exist on other dates. The sample caption states CMS's window: "in force since 2020-01-01" for an open window, "in force 2020-04-03 to 2021-08-07" for a closed one. CMS's revision notes keep their line breaks. The KB index range ends with "today" when a version of the document is in force today, so a current version with no CMS date no longer reads as retired.
- **The sample integration page loads fast after the first visit.** Its three code tables cache for five minutes per date of service, so a warm load skips about 9 seconds of database reads. A render where any cell could not be read is never cached.

### Added
- **Find in policy on KB pages.** Once the full text loads for a customer, a search box above it highlights every match inside CMS's verbatim text, shows "N of M matches" with next and previous, and clears with Escape. It runs entirely in the browser (no network call), skips receipts and page labels, and leaves the approved-passage highlights untouched.
- **The paid KB view.** Signed in to the KB (or calling with an API key), a code page loads every approved passage for the code on the chosen date (the same passages the warrant API serves), each with its label, CMS's words, the version, and a receipt behind a toggle; the free view still shows one sample. On a policy page, the loaded verbatim text highlights each approved passage where its words match exactly; a passage that does not match exactly is listed under the text instead, never forced. Pages never ask for a pasted key. An "Appeal exhibit (print)" button prints the page as an exhibit with every receipt open.

### Changed
- **KB customer log in.** KB pages no longer ask you to paste an API key. "Customer? Log in" opens `/kb/login`: enter your work email, then the 6-digit code we email you. The session lasts 14 days. Signed in, the page shows your organization and a Log out link, and loads the verbatim text when your plan includes it. Access is by organization: when the organization's plan includes the KB paid view, every member reads the verbatim text. API clients keep the `X-API-Key` header on `/kb/{kind}/{num}/v/{ver}/sections`; a key with the paid view still works when the browser session's plan does not include it. The login email is matched without regard to case, and Log out is a button (a POST), not a link.
- **Dashboard login signs in approved accounts only.** The dashboard login no longer creates an account for a new email. It sends a code only to an existing, approved account, and it gives the same answer for every email. Get an account at `/signup`. Dashboard sessions last 14 days; earlier sessions end and need one new sign-in.
- **One account per email, in any case.** Signup and the dashboard login store emails lowercase and find an existing account without regard to case: `Pat@Example.com` and `pat@example.com` are the same account.
- **Sample integration page: CMS's words for the rate types.** The rate-type legend now quotes CMS's own Transparency in Coverage schema definitions, linked to the pinned schema file. Receipts name their documents in words ("CMS DMEPOS fee schedule file, quarter 2026Q4") beside the file hash.
- **No empty sample box.** A KB code page with no approved label for the code shows no "Sample passage" box at all; the box appears with the first approval. Where approved passages exist but none is in force on the chosen date, the page still says so. On the one-code-every-payer sample page, the empty policy-passage cell reads "Not held yet" like every other empty cell.
- **Sample integration page in plain words.** A fee cell where CMS's file prints 0.00 reads "CMS's file lists 0.00 for this cell" instead of $0.00. The commercial payer's rate ranges say how many rows its own file publishes at 0.00 ("The payer's file lists 0.00 for 404 rows"). The edit cell names the MUE adjudication indicator (MAI) in CMS's own term. Rate types keep their published names, with a one-line legend. The Medicare check on commercial providers reads as three short sentences. Raw CMS column names and internal version ids move out of the cells; receipts keep the column names behind the "receipt" toggle.
- **KB pages say whose code list it is.** LCD pages list the codes CMS's own LCD code table names for the current version ("Codes CMS lists in this LCD"); Article pages, for which CMS publishes no DME code table, label their list as Verzi's coverage-label mapping. Receipts under quotes and lists now sit behind a "receipt" toggle. A governing LCD outside the Verzi KB links CMS's own page and says so. On a date-of-service view, the version-in-force line sits directly above the note it explains.

### Added
- **Why a passage did not serve.** When policy passages resolve, the `w3` block of the signed warrant response (`GET /v1/warrant`) carries a new top-level `withheld` field: the count of approved passages for the version in force that were not served, by reason. `verification_failed` means the words did not verify against the archived bytes. `not_on_serving_registration` means the passage quotes an earlier rendering of that version. Additive; existing fields are unchanged. KB code pages state the same reason in one sentence.

### Fixed
- **A retired document no longer answers "in force today".** A version with a stated effective window is now in force only inside that window. Before, a fresh capture of a retired LCD, Article, or quarterly file (stated end in the past) resolved for dates after its end. The as-of answer (`GET /v1/archive/docs/{doc_id}/as-of`, warrants, KB pages) for such a date is now `none`, with a new `reason` field: `before_first_stated_start`, `after_last_stated_end`, `gap_between_stated_windows`, `not_observed_on_date`, or `no_versions`. Versions with no stated date still resolve by their observed window.
- **A real effective date on the capture day still counts.** Only sources whose archive manifest dates versions by our own fetch treat that date as no date. Any other source's effective date is the publisher's, so a quarterly file effective October 1 and first captured October 1 shows as news.
- **The latest pipeline run names its source** on the status page.
- **`GET /v1/archive/changes` rejects an unknown `source_id`** with HTTP 422 instead of returning an empty list.
### Added
- **ICD-10-CM code lists on KB Article pages.** The current version of a policy Article now shows CMS's ICD-10-CM lists ("Codes that Support Medical Necessity" and "Codes that DO NOT Support Medical Necessity"): each group's paragraph, quoted, and its code count, free. The full code table serves customers with an API key. Read verbatim from CMS's Medicare Coverage Database download, with the file's sha256 as the receipt, and refreshed with each weekly capture. A group paragraph that cites CPT codes is withheld because it cites CPT codes. No list is ever attached to a HCPCS code page by inference.

### Changed
- **Code pages list governing policies as CMS lists them.** `/kb/code/{hcpcs}` now names every LCD whose current version lists the code in CMS's own LCD code table (from the Medicare Coverage Database download), with CMS's description of the code quoted, the group and version it sits in, the Articles CMS relates to each LCD, and the file's sha256 as the receipt. HCPCS Level II only. Nothing is inferred, and the list refreshes with each weekly capture. Code pages also answer faster (cached lookups).
- **Clearer code-page and receipt wording.** The prior-authorization line labels CMS's Effective Date cell as a quote. A code with no approved label keeps the "unlocks when the first label is approved" line on every date. Receipts drop the renderer tool string and state plainly when CMS gave a version no dates.

### Fixed
- **The updates feed shows changes, not first captures.** A version is news when the document was held before and this version is newer than every version held before it, in the publisher's order (effective date, then version number, then observed time). A retroactive revision is still the newest version, so it is news. A document's first capture is news only when its effective date falls in the window or later. First captures and older versions filled in by a later crawl sit behind a new "Newly archived" chip, out of "All", and never ride RSS, the digest, watch-a-code alerts, or webhooks. `GET /v1/archive/changes` takes `kind=news|newly_archived` and `category=`, both filtered before the row cap, and every row carries `news` and `newly_archived`. RSS guids are unique per version.
- **Readable names for the rest of the feed.** Transmittals for the Medicare Secondary Payer, Financial Management, General Information, State Operations, Communications, Managed Care, and Demonstrations manuals carry their manual name; any other transmittal reads "Transmittal {number}". Competitive Bidding pages read the site's own category path ("Competitive Bidding: Educational Information / News and Bulletins"), and pages known only by a document key read "Competitive Bidding document {key}".
- **Status ledger in plain words.** Each run shows its source name, the time in UTC, and one fixed sentence ("Checked. Nothing changed.", "The latest run failed."). Raw errors stay in `GET /v1/archive/runs`, which adds `source_name` and `summary`.
- **Faster feed and status.** The change feed, the upcoming list, and the source catalog are cached for two minutes.
- **Archive page and as-of JSON.** The archive page names documents ("LCD L33718"), drops internal ids, shows plain dates, and links the KB page. The as-of JSON's `kb_url` keeps the date (`?dos=`). Feed receipts name the source host, and history entries say "version", not "new version".

### Fixed
- **Revision notes for current policy versions.** LCD and Article versions captured from the Coverage API now carry CMS's own revision-history entry for that version (for example L33718 version 52, R11, 2024-01-01), read verbatim from CMS's revision-history endpoint. Before, the newest version of a policy showed no note while older versions did. 126 current versions gain a note. The quote carries its own receipt: the sha256 of the archived revision-history file it comes from, on the feed's receipt and as `note_sha256` on webhook events.

### Changed
- **Status page reasons in plain words.** A failing source shows one fixed sentence for its state ("The latest run failed.", "The latest run found no files.", and so on) and the run date, never the raw error text. Raw errors stay in `GET /v1/archive/runs`. Empty status tiles read "not reported" instead of a dash.
- **Watch-a-code alert subjects use the document's headline**: "E0601: Positive Airway Pressure (PAP) Devices changes 2026-11-01", the same headline rule as the feed.
- **KB policy pages lead with CMS's newest note.** The top block quotes the selected version's own revision note. On the newest version without its own note, it quotes CMS's newest note on file and names the version it belongs to. The "last changed" date comes from that same entry, so the two agree. Where no note is extracted, the page says exactly that instead of claiming CMS wrote none.
- **CMS's revision history and the related documents come before the key prompt.** The full revision history is its own section above the "Already a customer?" line, no longer inside the receipt. LCD pages list the Articles CMS relates to them, and Article pages list their LCDs, from CMS's own related-documents file. Each links to its KB page where one exists.
- **The "open on CMS's site" link works.** For versions captured from the Coverage API, the receipt links the public Medicare Coverage Database page; the API address (which needs a key) is shown as text.
- **Version links are exact.** KB version chips link each captured version by its archive id, so two captures of one CMS version, or a version CMS did not number, each open their own page. The verbatim-sections response names the version it actually served.
- **KB index:** documents without CMS dates say so, titles show CMS's characters ("/", not "&sol;"), and the count says which documents have archived history pages. Version chips read "version 48".

### Added
- **Sample integration page: one code, every payer.** An unlisted, noindex page for E0601, K0606, and A4239 in Utah, with Medicare, Utah Medicaid, and a commercial payer side by side. Columns: rate, prior authorization, edits, coverage passage. Each cell is a table fact with its receipt, a quoted passage, or "Not held yet". Commercial rates come from the payer's Transparency in Coverage files, with each rate-carrying NPI checked against CMS's 2024 Medicare DME supplier file. The date-of-service picker matches the KB pages.

### Added
- **Watch a code from its KB page.** Every `/kb/code/{code}` page carries a follow box: "One email when any policy cited for {code} changes or gains a future effective date. Nothing else, ever." It uses the same double opt-in subscription as the updates page.
- **Historical versions get their own chip.** Versions pulled from the past (backfill or Wayback copies) no longer show as news. The updates feed and RSS show what scheduled runs observed; the "Historical versions" chip shows the rest. `GET /v1/archive/changes` takes `origin=live|historical` and returns `truncated` when the window holds more rows than the limit, and the page says so.
- **RSS by chip.** `/v1/archive/changes.rss?category=claim_policy` (or `fee_schedule`, `code_set`, `regulatory`) carries one chip's sources. The page's RSS link follows the chip.

### Changed
- **The archive page opens on a worked example** (every version of LCD L33718) instead of an empty box.
- **Status page platform numbers read as labels**, not a raw JSON block.
- **Plain words on free pages.** "backfill" reads "pulled from the past" and "wayback" reads "Wayback Machine copy" on the feed and in the digest. Feed chips are keyboard buttons. Pages carry a favicon.

### Fixed
- **The honest-empty line says only what we know.** Where Verzi extracted the publisher's note and found none, an entry reads "CMS published no revision note for this change." Where no extraction runs for the source, it reads "No revision note is extracted for this source." Applies to the feed, calendar, digest, alerts, RSS, and the calendar export.
- **KB links that work.** Feed receipts and the as-of JSON link to `/kb/lcd/{n}` or `/kb/article/{n}`, and only when a KB page exists. The as-of notice names the KB page only when there is one.
- **No raw ids or internal numbers in headlines.** An entry with no title or label reads as its source's name plus the id's own tail. The gain line shows the publisher's version, or just "new version", never Verzi's internal version id.
- **Failing sources say so.** The status page reads each source's latest finished run. A failed, partial, or zero-capture run shows a "failing" pill with the run's own error line, as plainly as "fresh".
- **A complete source is not "failing".** Some sources skip files we already hold. When every file is held, the run finds nothing new and records that. The status page and the daily ledger no longer read that run as a moved page. CMS DME supplier utilization (44 of 44 files held) and Medicare provider enrollment (2 of 2) show their real state.

### Added
- **Date of service on KB pages.** KB document pages and KB code pages have a "Date of service" calendar picker. It sets `?dos=YYYY-MM-DD`. The page shows the policy version in force on that date. The warrant API's as-of rule chooses the version. One line names it: "Version in force on 2024-03-15: version 48, effective 2021-09-27 to present". A date of service wins over `?v=`. Some dates have no archived version in force. The page then says so and shows no policy content. A bad date gives a plain error line. On code pages, each mapped policy shows its own version in force. The sample passage follows the date.
- **CMS's Required Prior Authorization List for DMEPOS, as of the date of service.** `GET /v1/rules/warrant` adds a `prior_authorization` block. `required_prior_authorization` is `listed`, `not_listed`, `listed_in_stated_states`, or `not_determinable`. It states list membership on the date of service for the request's `state`, from the list version in force by the archive as-of rule. The block carries the list's own row: descriptor, Effective Date cell verbatim, phases with their printed dates and state scope. A state that no phase in effect covers is `not_listed`; the reason quotes the later phase that will cover it, as the list prints it. With no state and only state-limited phases in effect, the value is `listed_in_stated_states`, `scope_on_dos` quotes the scope, and the rollup grades it partial. When any part of the cell the answer needs is unreadable, the answer is `not_determinable`, never a confident `listed` or `not_listed`: `not_determinable_kind` names the cause (`date_not_readable`, `scope_not_readable`, `not_parsed`, `no_version_in_force`, `state_not_recognized`). A scope reads as Nationwide only when its words are exactly "Nationwide", with at most one trailing `:`, `;`, `,`, or `.` (the list prints "Nationwide:" once). A `state` that is not a state, DC, or territory code gives this block `not_determinable` and leaves the rest of the warrant answered. A CBA gives the block no state; `provider_context.prior_authorization_state` echoes the state used. When no list version is in force on the date, or the table is absent, the answer stays in its block and does not feed `confidence.overall`. Every other `not_determinable` stays in the rollup as unknown. Every answer carries a receipt: version, sha256, observed window, and the CMS URL. KB code pages show one line for the picked date of service or today: the date it answers for, the list's Effective Date cell quoted verbatim, and a receipt link. When only state-limited phases are in effect, the line says "for these states only, as the list prints them" with the scope verbatim.
- **Transmittal subject lines, quoted.** Every CMS transmittal entry on the updates feed, the calendar, the weekly digest, the watch-a-code alerts, RSS, and webhook events now carries the cover page's SUBJECT line verbatim, labeled "CMS's subject line, quoted". Extracted deterministically from the archived rendering, joined across its wrapped lines, nothing reworded. MLN Matters articles carry no SUBJECT field and keep the plain statement that none was published. New transmittals get their line in the same weekly load that archives them.
- **Sections changed, by the publisher's headings.** Feed entries and webhook events (`sections_changed`, `sections_changed_vs`) name the sections whose words changed versus the prior version of the same document, using the publisher's own section headings in document order: "Sections changed vs version 48: Coverage Indications, Limitations and/or Medical Necessity · Revision History". Words are compared whitespace-insensitively; no diff text is written. The slot stays empty where no comparable prior version exists.

### Changed
- **A colon, never a dash, in every headline and subject.** "Takes effect 2026-10-30: Claims Processing Manual transmittal R13954" on the feed, calendar, digest, alerts, and RSS; alert subjects read "{code}: {document} changes {date}". The Order and Referring file reads "Order and Referring", CMS's own dataset title.
- **Readable names for regulatory entries.** Price Transparency, No Surprises Act, Interoperability and Prior Authorization, and 42 CFR entries now carry a label from CMS's own program name and the publisher's URL slug. `pt:hospital-price-transparency-enforcement-updates` reads "Price Transparency: hospital price transparency enforcement updates", and `cfr:42-414` reads "42 CFR Part 414". The label comes from the id only, never from content, and an unmapped id keeps its raw form.
- **The free surfaces, to spec.** Every change surface now shares one entry anatomy: headline (a future effective date leads, bold), gain line (document reference, version, effective date, "cited by warrants for" the mapped codes), the publisher's revision note quoted and labeled or the plain statement that none was published, then a collapsed receipt with the KB and CMS source links. Applies to the updates feed, the calendar, the weekly digest, the watch-a-code alerts, and RSS.
- **Filter chips and the pipe rule.** The feed defaults to all sources with chips (All / Claim policy / Fee schedules / Code sets / Regulatory). Notification pipes (digest, watch-a-code, webhooks) carry claim-policy and fee-schedule sources only; regulatory overlays, code sets, and provider files never ride in an inbox, whatever the subscription says. Regulatory entries on the feed say so.
- **Calendar, date first.** `updates.verzi.health/calendar` leads with the effective date, claim-policy sources only, the quoted note under each item, and an "Add to my calendar" export (`GET /v1/archive/upcoming.ics`).
- **Status page freshness.** `status.verzi.health` adds a per-source freshness table: every archived source with its last capture, cadence, and a fresh or stale pill. Stale is shown as plainly as fresh.
- **The engineer door says so.** `GET /v1/archive/docs/{doc_id}/as-of` now carries a notice naming itself a machine surface, with the KB page link for the readable version. Payload otherwise unchanged.
- **KB doc pages lead with CMS's words** (the approved free-surfaces layout): title, one-line identity (versions, span, last-changed date), then the version's revision note quoted and expanded; the version strip and a one-sentence section summary follow; the API-key box is a one-line "Already a customer?" link after the free content; receipts collapse behind one toggle at the bottom. When CMS published no note, the page says so plainly. Code pages show the sample passage before anything else asks for a key.

### Changed
- **Readable names for transmittals and MLN Matters entries.** Feed, calendar, digest, and RSS entries derive a human label from CMS's own id structure (the transmittal suffix names the manual it amends): `transmittal:r13954cp` reads "Claims Processing Manual transmittal R13954". Metadata only, never content; anything unmapped keeps its raw id.
- **Policy documents first.** The updates feed defaults to entries with document titles or revision notes (the DME policy corpus), with counts for both views; "All sources" shows every observed version. The digest lists policy sources first.

### Changed
- **KB pages wear the shared brand.** `/kb` pages now use the one site component (light default with a persisted theme toggle, standard nav, brand type). Serif is reserved for verbatim document text: passages, the sample quote, and CMS's revision notes; interface text is sans. No words changed; the fidelity gate's guarantees are untouched.

### Changed
- **One brand across the public pages.** The status, archive, updates, calendar, and subscription pages now share one brand component: light theme by default, dark via the toggle in the nav (your choice persists per browser), Reddit Sans interface, turquoise accent, and a serif reserved for verbatim document text. Same nav row everywhere, now including the Coverage KB.

### Added
- Warrant W3 free tier: one sample item per code serves its verbatim passage text, marked `sample: true` (parity with the KB sample slot).

### Changed
- **Corpus metrics on the status page.** `status.verzi.health` now leads with the Medicare DME corpus numbers, read live from `GET /kb/metrics`: codes answering, policy documents and versions, the fidelity percentage with its section counts, archive depth, A/B MAC captures, and Medicaid states tracked.
- **Revision notes on the feeds.** Where the renderer extracted one, every change on `updates.verzi.health`, the calendar, the weekly digest, the watch-a-code alerts, and the webhook events now carries the publisher's own revision note verbatim (`revision_note`, with `rev_number` and `rev_date` where stated). Null when absent; never a paraphrase.
- **Readable updates feed.** Each entry on `updates.verzi.health` now leads with the document title, the effective date, and the HCPCS codes the coverage layer maps to the document, beside the version, origin, and sha256. `GET /v1/archive/changes` carries the codes as `affected_codes` on every change row (empty when no mapping exists). The entries stay machine-observed; the titles and dates are the publisher's own words from the archived bytes.

### Added
- **One-click unsubscribe headers.** Digest and watch-a-code emails now carry the RFC 8058 headers (`List-Unsubscribe`, `List-Unsubscribe-Post`), so a mailbox provider's own unsubscribe button revokes the subscription in one click. The in-body link keeps the scanner-safe two-step page.
- **Policy KB pages (`/kb`).** Every Medicare DME policy document (LCDs and Articles), rendered verbatim from the archived bytes, one page per document version. An independent fidelity gate proves every served section is word-identical to the source bytes; a section that fails the gate is held, never served. Free, no key: the document title, every version with its effective window, the section outline, counts, CMS's own revision-history notes, and the byte-level receipt (sha256, fetch date). Entitled keys additionally load the full verbatim section text. Thin per-code pages at `/kb/code/{hcpcs}` link each labeled code to its governing documents, and unlock ONE sample passage in full (the first approved item for the code, marked as a sample); unmapped codes say so plainly. `GET /kb/metrics` publishes the corpus numbers (codes, documents, versions, archive depth, fidelity percentage), every number computed live from the database.
- **Bounce and complaint suppression.** An address that bounces permanently or files a complaint never gets another subscription email, including the confirm email after a re-signup. Suppression is per address, written automatically from SES feedback, and checked before every send.
- **Upcoming-changes calendar.** `updates.verzi.health/calendar` lists every document version with a future effective date, soonest first, grouped by date: fee-schedule quarters, dated policy revisions, transmittal batches. `GET /v1/archive/upcoming` serves the rows (public, no key), each with `affected_codes` where a mapping exists. The dates are the publisher's own; a version with no stated effective date is never listed.
- **RSS feed.** `GET /v1/archive/changes.rss` renders the change feed as RSS 2.0; linked as the alternate representation on `updates.verzi.health`. Item text holds the publisher's facts and the sha256 receipt only.
- **Signed change webhooks.** A webhook subscription gets one signed POST per daily run with the matching changes: `X-Verzi-Signature` is `hex(hmac_sha256(secret, body))` with the per-subscription secret from registration. Every event carries its receipt (doc_id, version, sha256 of the archived bytes) and is auditable against `/v1/archive`. At-least-once delivery; dedupe on `version_id`. Documented at `/resources/webhooks`.
- **Watch a code.** Subscribe to one or more HCPCS codes on `updates.verzi.health`. A daily check emails you when a document that governs a watched code gets a new version, including a version with a future effective date ("takes effect <date>"). Same double opt-in, same one-click unsubscribe.
- **Weekly digest email.** Verified subscribers get one plain-text email a week with the window's new document versions: titles, versions, effective dates, and counts, the publisher's facts only, scope-filtered per subscription. Signup form on `updates.verzi.health` (double opt-in). A subscriber with no matching changes gets no email. Non-live versions say so (`backfill`, `wayback`).
- **Change subscriptions.** `POST /v1/subscriptions` subscribes a destination to the archive change feed: an email address (public, double opt-in; nothing sends until the confirm link is opened) or a webhook URL (API key required; deliveries are HMAC-signed with a per-subscription secret, returned once). Scope by HCPCS codes, by source ids, or neither for the full weekly digest. Unsubscribe is one click from every email.
- W3 label queue: `GET /warrant/w3/labels/pending` (verbatim quote + context per item) and `POST /warrant/w3/labels/{id}/decision` (same 409 contract as the requirement seam).
- Warrant W3: the slot now serves verbatim policy passages with Verzi labels (`policy_passages`) when the passage layer resolves for the code and DOS; the legacy requirement shape remains as fallback. Migration 128.
- Warrant W1: an exact fee-cell miss now names the modifier combinations that do exist for the code and area (`available_cells`); status stays `not_found`.
- **Updates feed.** `GET /v1/archive/changes?days=N` lists every new document version the archive observed in the window, with the publisher version, effective window, observation time, and sha256 (public, no key). `updates.verzi.health` serves it as a readable change feed: new LCD versions, fee-schedule quarters, NCCI edits, and code-set releases, grouped by day. Nobody writes these entries; the pipeline observes them.

### Added
- **Product selector on signup.** The API-key signup form now asks what you are here for: provider intelligence (the data API), warrants for RCM (paid), or both. The answer routes your access request to the right review; it does not change what a free key can do.

### Changed
- **Pending keys say so.** A key awaiting access review now gets a 403 that says it is pending review, instead of the generic "deactivated" message. (Signup has been approval-gated since the private-beta lock; access requests are now reviewed in the admin portal.)

### Added
- **Coding rules, parsed from the archive (`/rules/*`).** The first parsed layer over the source archive turns NCCI edits (warrant W2) and DMEPOS fee cells (warrant W1) into as-of lookups, each citing the exact archive file it came from. `GET /rules/ncci/ptp?setting=&code_a=&code_b=&dos=` answers whether a code pair is procedure-to-procedure edited for a setting on a date of service, with its modifier indicator; the row's own effective and deletion dates gate the answer (the deletion date is the first day out of force). `GET /rules/ncci/mue?setting=&hcpcs=&dos=` returns the Medically Unlikely Edit value and its adjudication indicator from the quarter in force on the date. `GET /rules/dmepos/fee?hcpcs=&state=&rural=&dos=` (or `cba=` for a former competitive-bidding area) returns the allowed amount, national ceiling, and floor. Every answer carries its receipt: the version id, the archive member file id, and that file's sha256. The tables hold codes and amounts only, no CPT descriptors. Spec: `docs/PARSING_SPEC.md`.
- **Source archive (`/archive/*`).** The raw, immutable layer under every parsed table: every byte a publisher served, stored exactly as served and hashed (sha256) before any parsing, with zip members as first-class files, versioned per document, and resolvable to the version in force on a date of service. Sources so far: HHS OIG LEIE (full file and monthly supplements), NCCI PTP and MUE tables (with the DME supplier MUE table and the MUE archive back to 2020), the DMEPOS fee schedule (every quarter CMS posts, 1998 to 2026), and HCPCS Level II quarterly files (2023 to 2026). Endpoints: `GET /archive/sources`, `GET /archive/{source_id}/files`, `GET /archive/{source_id}/docs`, `GET /archive/docs/{doc_id}/versions`, `GET /archive/docs/{doc_id}/as-of?date=`, `GET /archive/files/{sha256}` (metadata and a one-hour link to the original bytes), `GET /archive/files/{sha256}/text` (the stored text rendering with its renderer version), `GET /archive/runs`. The as-of answer names the rule that decided it (the publisher's stated window, or when the archive first saw the version) and never guesses. `/sources` gains `warrants`, `archive_file_count`, `last_fetch_at`, `last_change_at`, and `latest_publisher_version`. Text from files that carry CPT descriptors (AMA license) is not served. Spec: `docs/ARCHIVE_SPEC.md`.

### Changed
- **Applicability warrant: IN-category DMEPOS codes now resolve device-vs-supply from the HCPCS Level II descriptor** (parsed from the archived alpha-numeric HCPCS file). Those answers move from `proxy_only` to `cited`.
- **Source archive now holds A/B and HHH MAC coverage documents (all jurisdictions).** `mcd_ab_mac` adds 4,569 archived LCDs and Articles (29,237 dated versions, 2015 to 2025) alongside the DME MAC set, so `GET /archive/docs/{doc_id}/as-of` resolves the local coverage document in force on a date of service for A/B and HHH jurisdictions too. Proposed and draft versions are archived but carry no effective date (they were never in force). Older CMS transmittals back to 1999 were also backfilled, so more manual-chapter revisions resolve a stated effective date.

### Internal (not a public surface)
- **Cost Plus Wellness provider contracts (schema `wellness`, internal only).** A crawler captures every contract from costpluswellness.com (page labels, the PDF, its sha256, and the fetch date), keeps every version so a changed contract is a new version, and an extractor pulls the fixed contract terms (parties, plan sponsors, effective date, term and renewal, rate basis, fee schedule named, locality, facility or non-facility, percentage of Medicare, carve-outs, patient cost sharing, claim submission window, payment timing, the TPA or same-day system, and termination), each with the page and section it came from. Every field goes through human review before it counts as a fact. The site Terms of Use limit use to internal business use and forbid republishing, so this data is not on any public surface: the `/wellness/*` endpoints are gated behind the admin key and hidden from the OpenAPI schema, the source is kept off the public `/sources` catalog, and the index files are internal. The `/wellness/rates` endpoint turns a verified percentage into dollars per CPT and locality using the Medicare fee schedules below.

### Added
- **DailyMed labels, the official SPL record.** `GET /rx/labels` searches human prescription drug labels from DailyMed (the official Structured Product Labeling record, not openFDA's copy) by drug name, labeler, or a required section, returning set_id, version, effective date, title, labeler, and which of the four key sections are present. `GET /rx/labels/{set_id}` returns one label's boxed warning, indications, dosage and administration, and contraindications section text (parsed by LOINC code, nested subsections included), its RxNorm concepts, and its pharmacologic classes. Every `dailymed_url` is pinned to the version we hold (`lookup.cfm?setid=...&version=N`), so a citation never shows newer text than we stored. `GET /rx/drug-classes` serves the pharmacologic classes from DailyMed's official class mapping (1,539 classes) with the number of labels in each, filterable by name and FDA class type (EPC, MoA, PE, CS). Chosen over openFDA's label copy because DailyMed updates daily, keeps version history, and publishes the official SPL-to-RxNorm and pharmacologic class mapping files, while openFDA's copy is weekly, flattens tables, and attaches drug codes only on an exact match. New source `rx_dailymed` at `/sources`. Migration 097.
- **Drugs@FDA: the approved-drug application record.** `GET /rx/drugsfda` searches every FDA-approved drug application (29,336 NDA, ANDA, and BLA applications) by sponsor, drug name, active ingredient, application type, and first-approval-date window, returning the sponsor, products, ingredients, first and latest approval dates, and submission and product counts, ordered by first approval date. `GET /rx/drugsfda/{appl_no}` returns one application's products (form, strength, ingredient, therapeutic equivalence code, marketing status) and its complete submission history, the original plus every supplement with approval dates. The data comes from the FDA's own Drugs@FDA data files, not openFDA's copy. `first_approval_date` is the earliest approved submission; a submission status of TA is a tentative approval (approved on the merits but blocked by another product's patent or exclusivity). New source `rx_drugsfda` at `/sources`, refreshed monthly. Migration 096.
- **FDA drug recalls, matched to the NDC spine.** `GET /rx/recalls` serves the openFDA drug enforcement dataset (about 18,000 recall enforcement reports) filterable by NDC, ingredient, recalling firm, hazard class (Class I, II, III), status (Ongoing, Completed, Terminated), and report-date window. Each recall carries its classification, status, firm, reason, distribution pattern, and report, initiation, and termination dates. NDCs are extracted from three places, because the openFDA drug-code block is present on only about a fifth of records: the openFDA block, the free-text product description, and the code-info lot list. Each extracted NDC is normalized to 11 digits and matched against the current NDC directory; matched NDCs also resolve to their RxNorm ingredient. The match is fuzzy, so the response reports it honestly: `match_stats` and the `methodology_note` state that about 51% of recalls carry a parseable package NDC and about 19% match at least one NDC to the current directory (an older recall cites a discontinued NDC no longer listed). New source `rx_drug_recalls` at `/sources`, refreshed weekly. Migration 095.
- **FDA drug establishments are now queryable.** The FDA Drug Establishments Current Registration Site (DECRS) has been loaded since migration 089 and no endpoint could read it. `GET /rx/establishments` searches all 9,878 registered drug plants worldwide by operation, ISO-3 country, and firm or registrant name, returning FEI number, firm, country, and the registered operations. `operation=API MANUFACTURE` isolates the 2,400 plants that make an active ingredient, the supply-side view behind a drug shortage. `GET /rx/establishments/operations` lists the 15 operation types with counts. The `operations` array is now split into one element per operation (the source file separates them with semicolons, not commas), so a plant that registers "ANALYSIS; API MANUFACTURE; PACK" now matches an API-maker filter; the previous load stored the whole string as one element and missed those plants. The public file does not link a plant to the NDCs it makes; the `methodology_note` says so.
- **CMS Medicare fee schedules (public domain), loaded for internal rate math.** The Medicare Physician Fee Schedule (`medicare.mpfs_rvu`, `medicare.mpfs_gpci`, and the conversion factor) and the Ambulatory Surgical Center payment rates (`medicare.asc_rates`). With these, "a percentage of Medicare" becomes dollars per CPT code and locality. The non-facility allowed amount is `CF x [(work_rvu x work_gpci) + (pe_rvu x pe_gpci) + (mp_rvu x mp_gpci)]` for the named locality. Loaded for the recent years CMS publishes, refreshed quarterly. Today the only reader is the admin-only `/wellness/rates`, so these two sources are held off the public `/sources` catalog until a public fee-schedule endpoint ships (they will return to the catalog in the same change). Note: a CMS 2-digit locality code is not unique nationally, so resolve a locality by name where the code is shared.
- **FDA Orange Book and the generic launch radar.** The Orange Book has been loaded since migration 089 and no endpoint could read it. `GET /rx/orange-book` searches all 48,664 approved products by trade name, ingredient, applicant, application type (N brand, A generic), product type, and therapeutic equivalence code. `GET /rx/orange-book/launch-radar` serves `rx.v_generic_launch_radar`: the 2,457 brand prescription products with no approved generic of the same ingredient, dosage form, and strength, ordered by the date the last listed barrier falls, filterable by ingredient, applicant, and date window, with `unprotected=true` for the 866 products carrying no patent and no exclusivity at all. `loss_of_exclusivity` is an outer bound rather than a forecast: it is the latest listed patent or exclusivity date, so a late device or formulation patent can sit years past the compound patent, and most generics arrive earlier through challenges and settlements that this data does not carry. The `methodology_note` says so on every response.
- **Drug economics spine (schema `rx`).** The platform now speaks NDC. Seven new sources at `/sources`: the FDA NDC Directory (116K products, 218K packages, normalized to 11-digit NDCs), RxNorm prescribable content (NDC-to-RXCUI links and ingredient relationships, no UMLS license needed), NADAC weekly acquisition cost stored as change-only price history per NDC from 2013 forward, CMS Spending by Drug for Part D, Part B, and Medicaid (unpivoted to one row per drug, manufacturer, and year) plus the quarterly Part D file and Part D by geography and drug, Medicaid State Drug Utilization Data from 2019 forward, openFDA drug shortages, and the Mark Cuban Cost Plus Drug Company public price list captured as dated, change-only snapshots. Refreshed on the existing weekly, monthly, and quarterly pipeline cadences. API endpoints follow in a later release.
- **AHRQ MEPS Prescribed Medicines, 2018 to 2024.** Fill-level prescription events with 11-digit NDC, quantity, days supply, payer splits, and person weights, joined to the Full-Year Consolidated file for age and insurance coverage (`rx.meps_rx_fills`, about 200K fills per year). `rx.mv_meps_ingredient_mix` gives the weighted drug mix by year, payer segment, and active ingredient; `person_segment` `private_under_65` is the employer-covered population, which is the honest basis for commercial drug-mix questions that Part D cannot answer. `GET /rx/meps/mix` serves it with median generic NADAC beside the per-unit amount payers actually paid.
- **`/rx/*` Drug Economics API.** `GET /rx/summary` (counts and freshness), `GET /rx/ingredients` and `/rx/ingredients/{rxcui}` (RxNorm ingredients with NDC counts, median generic NADAC, CMS spending, Cost Plus matches, shortages), `GET /rx/ndcs/{ndc11}` and `/rx/ndcs/{ndc11}/nadac` (package profile and change-only price series), `GET /rx/nadac/movers` (largest price moves in a window), `GET /rx/spending` and `/rx/spending/top` (CMS Spending by Drug for Part D, Part B, Medicaid), `GET /rx/shortages`, `GET /rx/cost-plus` and `/rx/cost-plus/{slug}` (Cost Plus public price beside median NADAC, with the implied acquisition cost from the published formula and dated history), `GET /rx/employers` and `/rx/employers/{ein}` (DOL 5500 health plans with funding class, PBM, disclosed PBM fees, and drug-coverage carriers; default sort is active participants, largest first), and `GET /rx/pbms` (PBM rollup by plans, covered participants, and disclosed compensation). Every response carries a `methodology_note`.
- **DOL Form 5500 now covers every industry, with Schedule C and Schedule A.** `dol_5500_filings` previously held healthcare-NAICS sponsors only. It now loads all filings and records the welfare-benefit and funding flags, with a derived `health_funding_class` (self-funded only, self-funded with stop-loss, insured only) for health plans. New `dol_5500_service_providers` (Schedule C Part 1 Item 2: every service provider a plan paid, with direct and indirect compensation, DOL service codes, and a PBM flag with a normalized PBM family) and `dol_5500_insurance_contracts` (Schedule A: carrier, persons covered, health, drug, and stop-loss benefit flags, premiums earned, claims paid). The healthcare-only `financial_filings` aggregation is unchanged.
- **Which self-funded employers buy pharmacy and medical from one conglomerate.** Every DOL 5500 health plan now carries an `integration` class: `same_parent` when the PBM on Schedule C and the health carrier or medical administrator belong to one company (Caremark with Aetna, Express Scripts with Cigna, Optum Rx with UnitedHealthcare or UMR, CarelonRx with Anthem), `blues_affiliated` for Prime Therapeutics with a Blue Cross plan, `different`, or unclassified when a plan names no PBM or no carrier. Self-funded plans file no Schedule A for their medical administrator, so the administrator is read from Schedule C as well. For 2023 that is 295 same-parent self-funded plans covering 3.29M employees. `GET /rx/employers?integration=same_parent` and `GET /rx/employers/integration` (counts, covered employees, and disclosed PBM fees by class and by parent, plus the largest plans).
- **Medicare Part B ASP payment limits and the NDC-to-HCPCS crosswalk, 27 quarters since 2020.** What Medicare pays per billing unit for clinician-administered drugs (`rx.asp_pricing`) and the CMS crosswalk from every NDC to its HCPCS code with billing units per package (`rx.asp_ndc_hcpcs`), loaded quarterly with preliminary files flagged and replaced by the final. `GET /rx/asp` searches by description, HCPCS, or NDC. `GET /rx/biosimilars` puts each FDA Purple Book biosimilar beside its reference product's payment limit (Stelara J3358 against its seven biosimilars, Avastin, Remicade, Prolia and Xgeva) and the cheapest Cost Plus product of the same molecule.
- **Healthcare construction signals — new product surface.** Early construction and expansion signals from public building-permit portals, state Certificate of Need / Determination of Need programs, and statewide project registries across 19 states, refreshed daily by a new pipeline. Permits are keyword-screened and LLM-verified for healthcare relevance; CON/DoN filings are healthcare by definition. Each signal is conservatively resolved (state-scoped exact and fuzzy matching) to a facility, health system, and an organizational contact from public NPPES data where possible. Three endpoints: `GET /signals` (search by state, city, category, system, minimum value, recency), `GET /signals/summary` (counts and dollar volume by state and category), and `GET /signals/systems` (per-system rollup ranked by dollar volume). ~15.6K signals at launch spanning a 400-day window, including hospital towers, ASC build-outs, imaging centers, and senior-care projects with reported values from thousands of dollars to nearly $1B.
- **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 app.verzi.health) — 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
- Fixed: manual-chapter effective dates from transmittals now require publication agreement (migration 135); seven cross-publication false dates retracted to honest unknowns.
- **`/sources` understated our own freshness on 20 datasets we hold and serve.** The page reported "never loaded" for Part D Prescriber (102.9M rows), Physician Utilization (29.1M), Open Payments (27.7M), HCRIS, MA Star Ratings, HSAF, CDC PLACES, ClinicalTrials.gov, HRRP, IPPS Impact, the VBC/ACO layer, the CBSA crosswalk and population files, Dartmouth, AHRQ SDOH, Medicare Geographic Variation, CMS POS service lines, Medicaid SDUD, the SEC 8-K feed, and the employer typology crosswalk. Only the pipeline ever wrote `last_loaded_at`, and it writes it only for runs it wrapped. A loader run by hand never reached it, and neither did a yearly source that had already loaded its last published year: the Checker finds nothing new, so the loader never runs, so the page keeps saying never. Every registered loader now stamps the catalog itself when it succeeds, and a test fails the build when a new one does not.
- **`/sources` published per-run numbers as record counts.** The page claimed we hold 73 hospitals, 17 nursing homes, 6 hospices, 3 long-term care hospitals and 8 clinicians. Those were the counts of CSV files inside the last quarterly CMS ZIP. NPPES showed 73,243, the size of one weekly delta against a 9.7M-row registry. `last_record_count` now means what it says: the rows a source currently holds. The per-run number stays in the load history at `/sources/{source_id}/history`.
- **DAC stopped reporting its own refreshes.** The pipeline calls the source `dac`; the published catalog row is `dac_providers`. Every audit and freshness write went to a source_id no row has, and an `UPDATE` that matches nothing is not an error. So `/sources` froze DAC at its last manual load while the monthly pipeline kept reloading it, and `/sources/dac_providers/history` showed no runs at all. The two ids are mapped explicitly now, and a mismatch is a test failure rather than a silent no-op.
- **`GET /rx/demand` county and metro rows now cover rural counties and Connecticut.** A prescriber's county was taken from the ZIP-to-CBSA crosswalk, so prescribers in about 1,200 counties outside any metro or micro area had no county row, and Connecticut had no county or metro rows at all. The county is now the one that holds most of the practice ZIP's population, and the metro is that county's CBSA.
- **Paging through a list endpoint no longer repeats or skips rows.** Many list endpoints sorted on columns that tie, such as participant counts, dollar amounts, distance, or last name. Postgres returns tied rows in no fixed order, so `limit` and `offset` pages could return the same row twice and never return another. Every paged query now ends its sort on a unique key. This affects `/rx/employers`, `/rx/employers/metros`, `/rx/asp`, `/rx/biosimilars` and the other `/rx` lists, facility searches by radius, Part D and physician utilization, Open Payments, OIG exclusions, HPSA, DAC, PECOS, clinicians, HCRIS, community health, market opportunity, signals, and the VBC lists.
- **`/status` and `/sources` no longer flag healthy CMS sources as critical.** Eight CMS Archive sources declared a `weekly` update cadence. That is how often the pipeline checks them, not how often CMS publishes, and the freshness thresholds read that field. Every one of them turned critical between releases while working correctly, leaving `/status` red with 10 critical sources. Measured from the CMS archive API over the last 13 releases, the median gap is 91 days for hospitals, long-term care, inpatient rehab and hospice, 84 days for home health and dialysis, and about 30 days for nursing homes and doctors/clinicians. Cadences now match the publisher, so the two monthly sources stay on monthly thresholds rather than being lumped in as quarterly. `/status` is green with zero critical sources. How often the pipeline checks is unchanged.
- **The homepage no longer shows invented numbers.** It carried a hardcoded fallback row count, source count, facility count and clinician count, plus sample market-share and community-health panels that named real health systems beside fabricated figures. The fallbacks rendered whenever `/platform/stats` was unreachable, and the sample panels rendered always, so a visitor could not tell a real figure from a placeholder. The page now renders live figures, and omits a count entirely when the API does not answer.
- **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.verzi.health/changelog` — returns raw markdown.
