{
  "name": "Cardiology Practice Tracker",
  "purpose": "Independent cardiology practices in California, Texas and Florida: address, roster, echocardiogram volume, website, email, EHR and ownership. Built from the CMS NPPES registry, each practice's own website, and the CMS Medicare claims summary. Read this document first, then read before you write.",
  "base_url": "same origin as this document; use relative /api/... paths",
  "auth": {
    "reads": "Gated on this deployment. Sign in at POST /api/login, or send x-tracker-token.",
    "writes": "Every POST, PATCH and DELETE needs a credential. An agent sends two headers on every request: 'x-tracker-token: <TRACKER_WRITE_TOKEN>' and 'x-tracker-editor: <who you act for> via Claude'. The token is a deployment secret; it is not in the repo, never goes in a URL, and must never be committed. A person signs in instead and gets an HttpOnly 'tracker_session' cookie.",
    "audit": "Every create, update and delete appends one audit_log row per changed field, with the actor, via (ui|claude|api) and the old and new value. Read it at GET /api/audit."
  },
  "endpoints": [
    {
      "method": "GET",
      "path": "/api/bootstrap",
      "desc": "Everything the single-page UI needs: every practice with its roster and CPT split, the excluded list, meta, and the national tafamidis prescriber list.",
      "returns": "{ practices, excluded, meta, tafamidis_prescribers }"
    },
    {
      "method": "GET",
      "path": "/api/practices",
      "desc": "List practices. Filters: state, city, zip, ownership, mso, min_cardiologists, min_echo, min_tafamidis, q (free text over name, city, ZIP, website, email, physician name and NPI), has=website|email|echo|echo_lab|tafamidis|split_roster, cms_group (substring of the Medicare billing group). Sort: sort=cardiologists|echo|tafamidis|name|city|state|updated & dir=asc|desc. Paging: limit (max 1000), offset. Add full=1 to include rosters.",
      "returns": "{ total, limit, offset, practices }"
    },
    {
      "method": "POST",
      "path": "/api/practices",
      "desc": "Create a practice. id and practice_name are required; any other writable field may be set. Unknown keys are ignored.",
      "returns": "{ practice } (201)"
    },
    {
      "method": "GET",
      "path": "/api/practices/:id",
      "desc": "One practice with its roster, CPT split and custom fields. :id is the natural key and must be URL-encoded; the numeric pid also works.",
      "returns": "{ practice }"
    },
    {
      "method": "PATCH",
      "path": "/api/practices/:id",
      "desc": "Update writable fields. Body is { field: value, ... } plus an optional { fields: { any_key: value } } for attributes the schema does not have. Set a custom field to null to delete it. Each change is audited; no-ops are skipped.",
      "returns": "{ practice, changed, ignored }"
    },
    {
      "method": "DELETE",
      "path": "/api/practices/:id",
      "desc": "Delete a practice and its roster, CPT rows and custom fields.",
      "returns": "{ deleted }"
    },
    {
      "method": "GET",
      "path": "/api/physicians",
      "desc": "Roster across every practice. Filters: q (name or NPI), state, practice_id, subspecialty.",
      "returns": "{ total, physicians }"
    },
    {
      "method": "GET",
      "path": "/api/excluded",
      "desc": "Sites removed from the tracker, each with the reason. Filters: state, q.",
      "returns": "{ total, excluded }"
    },
    {
      "method": "GET",
      "path": "/api/meta",
      "desc": "Build provenance and counts. Add recount=1 to recompute the counts from the tables.",
      "returns": "{ meta, live_counts }"
    },
    {
      "method": "PATCH",
      "path": "/api/meta",
      "desc": "Set a meta key. Value is stored as JSON.",
      "returns": "{ changed }"
    },
    {
      "method": "GET",
      "path": "/api/audit",
      "desc": "Change log, newest first. Filters: entity, entity_id, actor, limit.",
      "returns": "{ entries }"
    },
    {
      "method": "POST",
      "path": "/api/login",
      "desc": "Human sign-in { username, password }. Sets the session cookie. Agents use x-tracker-token instead.",
      "returns": "{ ok, user, must_change }"
    },
    {
      "method": "POST",
      "path": "/api/password",
      "desc": "Change your own password { current_password, new_password }, minimum 8 characters.",
      "returns": "{ ok }"
    },
    {
      "method": "GET",
      "path": "/api/me",
      "desc": "Who the caller is.",
      "returns": "{ user, public_read }"
    }
  ],
  "writable_fields": [
    "address_line1",
    "address_line2",
    "city",
    "ehr_source",
    "email",
    "email_confidence",
    "fax",
    "mso",
    "name_from",
    "notes",
    "org_npi",
    "owner_name",
    "ownership",
    "ownership_confidence",
    "ownership_reason",
    "phone",
    "practice_name",
    "research_confidence",
    "state",
    "status",
    "website",
    "website_confidence",
    "website_proof",
    "zip"
  ],
  "custom_fields": "Anything not in writable_fields goes in the `fields` object on PATCH /api/practices/:id. A key is 1-40 characters of letters, digits or underscore. It is stored against the practice and returned under `fields`. This is how the tracker carries information it was never built to hold — an owner, a call outcome, a next step — with no migration and no deploy.",
  "read_only_fields": {
    "why": "These are measurements, not opinions. The method page states how each one was derived, so an edit here would make the page lie. Change them by re-running the pipeline.",
    "fields": [
      "cardiologists",
      "clinicians_in_building",
      "registry_updated",
      "nppes_name",
      "echo_ffs_studies",
      "echo_ffs_office",
      "echo_ffs_facility",
      "echo_ffs_beneficiaries",
      "echo_addon_services",
      "echo_allowed_usd",
      "echo_billing_npis",
      "echo_medicare_est",
      "echo_gross_up",
      "echo_ma_share",
      "echo_market_area",
      "tafamidis_claims",
      "tafamidis_prescribers",
      "tafamidis_by_npi",
      "cms_group",
      "cms_group_members",
      "cms_group_pac_id",
      "cms_enrolled",
      "cms_agreement",
      "physicians",
      "echo_by_code",
      "ehr",
      "pacs",
      "front_office",
      "portal_links",
      "endpoint_vendors",
      "endpoint_employers",
      "same_phone"
    ]
  },
  "enums": {
    "website_confidence": [
      "verified",
      "detected",
      "reported",
      "inferred",
      "unknown"
    ],
    "email_confidence": [
      "verified",
      "detected",
      "reported",
      "inferred",
      "unknown"
    ],
    "ownership_confidence": [
      "verified",
      "detected",
      "reported",
      "inferred",
      "unknown"
    ],
    "ownership": [
      "independent",
      "system",
      "government",
      "safety-net"
    ],
    "status": [
      "active",
      "closed",
      "unclear"
    ],
    "research_confidence": [
      "high",
      "medium",
      "low"
    ],
    "name_from": [
      "org_npi",
      "registry_org_name",
      "physician",
      "research"
    ]
  },
  "rules": [
    "Never invent a value. Leave a field empty rather than guess. An invented email or website reaches a real practice.",
    "A website must be an absolute http(s) URL and must carry a website_confidence. The write is refused without one, because a published address and a guessed one look the same.",
    "An email must be published by the practice. Never build one from a name pattern.",
    "Say where a fact came from. Put the source in `sources` or in ownership_reason, and set the matching confidence marker.",
    "Read the practice before you write it. A PATCH that repeats the current value is a no-op and is not audited, so a diff you did not intend means you were working from stale data."
  ],
  "medicare_group": {
    "source": "CMS Doctors and Clinicians National Downloadable File",
    "what": "cms_group is the Medicare group the cardiologists at this address reassign their billing to, cms_group_members is how many clinicians bill under it, cms_enrolled is how many of this roster are enrolled at all, and cms_agreement is the share of them on the lead group. CMS keeps it from live enrolment records, so it names an employer years before the self-reported registry does.",
    "why_it_matters": "A practice whose cardiologists bill under a health system's physician network is employed by that system, whatever the door says. That signal removed 183 practices the registry showed as independent. An agreement below about two thirds means the roster is split, which usually means an acquisition part way through — filter has=split_roster.",
    "caveat": "A cardiologist who reads studies for a third party reassigns billing to that reading service too. Those groups are skipped when picking the lead, because they name a side job and not an employer."
  },
  "echo_volume": {
    "source": "CMS Medicare Physician & Other Practitioners, by Provider and Service, data year 2024",
    "counted": "Eleven CPT codes count as one study each: 93303, 93304, 93306, 93307, 93308 (transthoracic); 93312, 93314, 93315, 93317, 93318 (transoesophageal); 93350, 93351 (stress). Doppler (93320, 93321), colour flow (93325), contrast (93352), strain (93356) and probe placement alone (93313, 93316) are add-on codes that ride on a base study. They are counted in echo_addon_services and never as studies.",
    "office_vs_facility": "echo_ffs_office is billed from the practice's own office, so the practice owns the machine. echo_ffs_facility is a hospital study the practice read.",
    "not_counted": "Medicare Advantage, commercial insurance, Medicaid, self-pay and the Veterans Administration. CMS also suppresses any provider-and-code pair under 11 beneficiaries. Every count is a floor.",
    "estimate": "echo_medicare_est is echo_ffs_studies times echo_gross_up, the practice county's ratio of all Medicare beneficiaries to Original Medicare ones. It assumes an Advantage patient has an echocardiogram at this practice at the fee-for-service rate, which a capitated Advantage contract breaks. It is inferred, not measured."
  },
  "tafamidis": {
    "source": "CMS Medicare Part D Prescribers — by Provider and Drug, data year 2024",
    "what": "tafamidis_claims is the number of tafamidis (Vyndaqel/Vyndamax) claims billed to Medicare Part D in 2024, summed over the cardiologists on this roster. tafamidis_prescribers is how many of them prescribed it, and tafamidis_by_npi lists each one with their NPI and claim count. Tafamidis is the oral transthyretin stabiliser for ATTR cardiac amyloidosis, so a prescribing practice runs an amyloid service.",
    "joined_by": "NPI — exact, never a name match. The same public file the amyloid CRM uses.",
    "not_counted": "Medicare Part D only. It already covers standalone and Medicare Advantage drug plans, so it is all Medicare — but not commercial insurance, cash or the Veterans Administration. CMS suppresses any prescriber and drug under 11 claims, so a low-volume prescriber reads as none. Every count is a floor.",
    "national_list": "GET /api/bootstrap also returns tafamidis_prescribers: every prescriber NPI in the Part D file, in any state and at any kind of practice, with the name, practice address and phone from NPPES. in_tracker (with practice_id) marks the ones that are independent CA/TX/FL practices in this tracker. This is the whole amyloid-treating market, not just the three tracker states."
  },
  "example": "curl -X PATCH https://<host>/api/practices/$(python3 -c \"import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))\" '18450 HIGHWAY 59 N||HUMBLE|77338') -H 'content-type: application/json' -H \"x-tracker-token: $TRACKER_WRITE_TOKEN\" -H 'x-tracker-editor: David via Claude Code' -d '{\"email\":\"info@vitalheartandvein.com\",\"email_confidence\":\"verified\",\"fields\":{\"owner\":\"David\",\"next_step\":\"demo booked 2026-10-02\"}}'"
}