# Verzi Provider Intelligence API — Style Guide

This is the contract for adding new API endpoints. Read it before you add or rename a route. The goal is a discoverable, consistent surface that customers can reason about without docs deep-dives.

## The three-axis model

Every endpoint should fit one of these three top-level axes:

| Axis | Key | Top-level prefix | What lives here |
|---|---|---|---|
| **Providers** | NPI | `/providers/{npi}` (currently `/provider/{npi}`, to be renamed) | Everything keyed to a single clinician: dossier, employment, licenses, quality, prescribing, utilization, compliance |
| **Facilities** | CCN | `/hospitals/{ccn}`, `/nursing-homes/{ccn}`, `/dialysis/{ccn}`, etc. | Everything keyed to a single facility: identity, measures, patient experience, financial health, market position |
| **Markets** | Geo (state, county FIPS, ZIP, CBSA) | `/market-intelligence/*`, `/community-health/*` | Geographic analytics, beneficiary counts, market share, community health |

Cross-cutting analytics that span multiple of these axes live under topic-specific prefixes (`/benchmarks/*` for cross-facility HCAHPS analytics, `/mips/*` for cross-NPI MIPS analytics, `/vbc/*` for value-based-care program data, etc.).

Raw-source passthroughs live under dataset names (`/pecos/*`, `/dac/*`, `/nppes/*`) and are documented as "primary source data" tier.

## Naming conventions

**Plural collection nouns.** `/hospitals`, `/clinicians`, `/employers`, `/systems`, `/sources`, `/portfolios`, `/prescribers`. Never singular. Existing singular routes (`/provider`, `/portfolio`, `/prescriber`) are tech debt; new routes must be plural.

**kebab-case path segments.** `/cost-reports`, `/home-health`, `/market-intelligence`, `/community-health`, `/shortage-areas`, `/open-payments`, `/physician-utilization`, `/patient-experience`. Never camelCase or snake_case in URL segments.

**snake_case path and query params.** `org_pac_id`, `system_id`, `hcpcs_code`, `zip_code`, `fips_code`, `severity_floor`, `min_stars`. Consistent with Python and PostgreSQL conventions; matches the existing surface.

**One canonical key per concept.** A "system" is identified by `system_id`. A "facility" is identified by `ccn`. A "provider" is identified by `npi`. An "employer" is identified by `org_pac_id`. Search endpoints accept names as query params (`?q=`) but path keys are always the canonical id.

**Sub-resource nesting under the parent.** When a thing belongs to a provider, nest it: `/providers/{npi}/quality`, `/providers/{npi}/employment`, `/providers/{npi}/licenses`. When a thing belongs to a system: `/systems/{system_id}/divisions`, `/systems/{system_id}/physicians`, `/systems/{system_id}/hcahps`. When a thing belongs to a facility: `/hospitals/{ccn}/measures`, `/hospitals/{ccn}/patient-experience`.

**Cross-NPI / cross-facility analytics live at the top level.** `/mips/leaderboards/{specialty}`, `/mips/systems/{system_id}`, `/licenses/state/{state}`, `/licenses/alerts/recent`, `/benchmarks/hcahps/national`. The pattern: when the resource is "all NPIs of X" or "all facilities of Y", it's a cross-cutting analytic, not a sub-resource.

## When to add a new endpoint vs extend an existing one

**Extend if the new data answers the same customer question** as an existing endpoint. Example: adding "EIN of the billing entity" to the clinician's quality response is an extension of `/providers/{npi}/quality`, not a new endpoint.

**Add a new endpoint if any of these are true:**
- New customer question (different use case)
- Different response shape (>50% of the fields are different)
- Different cardinality (one row vs many)
- Different update cadence (real-time vs batch)
- Different authorization model

**Always check before you add.** Grep `api/routers/` for every endpoint that takes the same key (NPI / CCN / org_pac_id) and confirm none of them already answer the question. If one comes close, extend it instead.

## Tag taxonomy

OpenAPI tags should follow `{Axis} — {Subdomain}` for first-class product surfaces, or `{Topic}` for cross-cutting analytics. Examples:

| Router | Tag |
|---|---|
| npi_profile.py | `Providers — Profile` |
| employment / physicians | `Providers — Employment` |
| licenses.py | `Providers — Licenses` |
| quality (per-NPI, now under /providers) | `Providers — Quality (MIPS)` |
| open_payments per-NPI | `Providers — Open Payments` |
| hospitals.py | `Facilities — Hospitals` |
| nursing_homes.py | `Facilities — Nursing Homes` |
| benchmarks.py | `Benchmarks — HCAHPS` |
| mips.py | `MIPS — Cross-NPI Analytics` |
| sources.py | `Data Transparency` |
| accuracy.py | `Data Transparency` |
| portfolio.py | `Portfolios & Watchlists` |
| sec_filings, irs_990 | `Financial Intelligence` (Loop 2) |
| networks (Loop 2) | `Network Intelligence` |

This makes the OpenAPI / `/docs` page group endpoints visually so customers can scan by buyer persona.

## Public-facing vs auth-required endpoints

**Public (no auth):** Data transparency endpoints — `/sources`, `/sources/{id}`, `/accuracy`, `/health`. These are the proof-of-trustworthiness surface; they should never require auth.

**Auth-required (everything else):** All data-bearing endpoints require `X-API-Key`. The auth check returns 422 with FastAPI's default validation error format. Do not paper over auth as 404 — customers should get a clear "you need a key" message.

## Deprecation policy

Renaming or removing an endpoint is a breaking change. Process:

1. **Add the new endpoint** with the correct name + tags.
2. **Mark the old endpoint** `deprecated=True, include_in_schema=False` in FastAPI, and convert its handler to a `RedirectResponse(target, status_code=308)`. The 308 preserves the HTTP method (vs 301 which can downgrade POST to GET).
3. **Update `CLAUDE.md`** with the deprecation notice + date.
4. **Keep the redirect for at least one full quarter** before considering removal.
5. **Announce on the `/sources` or future `/changelog` endpoint** so customers polling the API know about the move.

## How "quality" is disambiguated

"Quality" is a heavily overloaded word in healthcare data. In our API:

- **HCAHPS** = patient experience (CCN-keyed). Surfaces: `/hospitals/{ccn}/patient-experience`, `/benchmarks/hcahps/*`, `/systems/{system_id}/hcahps`.
- **MIPS** = Medicare clinician quality program (NPI-keyed). Surfaces: `/providers/{npi}/quality`, `/mips/leaderboards/{specialty}`, `/mips/systems/{system_id}`.
- **Clinical measures** = CMS process/outcome facility measures (CCN-keyed). Surfaces: `/hospitals/{ccn}/measures`, `/nursing-homes/{ccn}/measures`, etc.
- **HVBP / HAC** = Value-Based Purchasing + Hospital-Acquired Condition programs (CCN-keyed, financial penalty/bonus). Surfaces: `/benchmarks/hvbp/revenue-impact/{ccn}`, `/vbc/hac/{ccn}`.

When in doubt, use the program name (HCAHPS, MIPS, HVBP, HAC) rather than the generic word "quality."

## How "enrollment" is disambiguated

"Enrollment" means three different things in healthcare data:

- **Hospital PECOS enrollment (855A):** `/enrollments/*` — hospitals enrolling in Medicare.
- **Individual PECOS enrollment (855I):** `/pecos/*` — clinicians enrolling in Medicare.
- **Medicare beneficiary enrollment:** `/market-intelligence/beneficiaries/*` — count of Medicare beneficiaries in a geography (currently mislabeled as `/market-intelligence/enrollment/*`, will be renamed).

Always specify which one in endpoint names and docs.

## How "system" is disambiguated

"System" has a single canonical meaning: a parent rollup of facilities and/or employers in our `system_hierarchy` table, keyed by `system_id`. Examples: `KAISER`, `HCA`, `HCA_TRISTAR`, `SUTTER_PAMF`.

The `system_name` is a human-readable label. It is **not** a canonical identifier. Path keys are always `system_id`. Some legacy endpoints accept `system_name` for backward compatibility; this is tech debt to be removed.

## Audit cadence

Run the endpoint audit (per top of this file) before each loop release. Specifically check:

- Any endpoint with the same path-param-type (e.g., `{npi}`) as an existing endpoint must not return overlapping data.
- Any endpoint matching `/{some_word}/{any_id}` must not collide with a sibling pattern.
- Any new endpoint must follow the naming conventions in this guide.

The audit takes ~15 minutes with the right grep commands. Skip it and we pay back the time with a refactor loop later.

## Known tech debt (to be addressed)

These exist today and are tracked for a future refactor cycle. Do not add new endpoints that depend on them.

1. `/provider/*` singular prefix → should become `/providers/*`.
2. `/portfolio*` singular → should become `/portfolios*`.
3. `/prescriber/*` singular → should become `/prescribers/*`.
4. `/clinicians/{npi}` and `/clinicians/{npi}/measures` overlap with `/provider/{npi}` and `/providers/{npi}/quality`.
5. `/enrollments/systems/{system_name}` uses non-canonical key; should be `/systems/{system_id}/hospitals`.
6. `/market-intelligence/enrollment/*` is mislabeled; should be `/market-intelligence/beneficiaries/*`.
7. `/pecos/enrollment/org/{org_name}` uses free-text key; should be `org_pac_id`.
8. `/hospitals/{ccn}/hcahps` vs `/benchmarks/hcahps/facility/{ccn}` overlap; one should redirect to the other.

Each item gets its own redirect shim (308) when fixed. None should be addressed in a product-feature loop; they belong in a dedicated cleanup loop.
