# Cardiology Practice Tracker — guide for AI agents (Claude) > A dashboard of independent cardiology practices in California, Texas and Florida: > address, roster, echocardiogram volume, website, email, EHR and ownership, plus a > national tafamidis (ATTR-CM) prescriber list. This file tells an LLM agent how to > READ and WRITE the tracker. If you are Claude helping David update it, use the API > below — never edit the database or the seed by hand. The live, machine-readable contract is **`GET /api/schema`** on this same origin — fetch it first. It always reflects the deployed field list, enums, rules, and examples. This file is the human-and-agent-readable summary. **If anything here disagrees with `/api/schema`, the schema is right and this file is stale — follow the schema.** ## What the data is Every practice is a real, independent, physician-owned cardiology practice, built from the CMS NPPES registry and verified against the practice's own website. Measurements (roster size, echo volume, tafamidis claims, the Medicare billing group) come from public CMS files. Researched fields (website, email, ownership) each carry a confidence marker. **Everything is real — never fabricate a practice, an email, a website, or a number.** Leave a field blank rather than guess. ## API (same origin, JSON) - `GET /api/practices?state=&city=&zip=&ownership=&min_cardiologists=&min_echo=&min_tafamidis=&has=&q=&sort=&dir=&limit=&offset=&full=1` → `{ total, limit, offset, practices }` - `POST /api/practices` → create; body is a practice object, `id` and `practice_name` are required. - `GET /api/practices/:id` → `{ practice }` (`:id` is the natural key, URL-encoded; the numeric `pid` also works) - `PATCH /api/practices/:id` → update writable fields; every change is audited → `{ practice, changed, ignored }` - `DELETE /api/practices/:id` → `{ deleted }` - `GET /api/bootstrap` → everything the UI needs in one call: practices with rosters, excluded list, meta, national tafamidis prescriber list. - `GET /api/physicians?q=&state=&practice_id=&subspecialty=` → the roster across every practice. - `GET /api/excluded?state=&q=` → sites removed from the tracker, each with the reason. - `GET /api/meta?recount=1` → build provenance and counts. `PATCH /api/meta` sets a meta key. - `GET /api/audit?entity=&entity_id=&actor=&limit=` → the change log, newest first. - `GET /api/schema` → the full machine-readable contract (fields, enums, rules, examples). ## Access — how to get in (humans and agents) The API is gated on this deployment. `GET /api/schema` and `POST /api/login` are the only open endpoints; everything else returns `401` without valid auth. Both ways in attribute every change to you. **Humans — the UI.** Open the live app and sign in with your own account (seeded users plus anyone in `TRACKER_USERS`/`CRM_USERS`). A new account starts on the shared first-time password and is forced to set its own on first sign-in. Signed-in edits are attributed to your username. **Agents (Claude) — two headers on every request:** - `x-tracker-token: ` — the `TRACKER_WRITE_TOKEN` secret (the tracker also accepts `CRM_WRITE_TOKEN`, shared with the amyloid CRM). It is **not** in the repo: get it from David or read it from the deployment's env. Never commit it, paste it into a page, or put it in a URL. If the API starts returning `401`, the token was rotated — get the new value from David. - `x-tracker-editor: via Claude` — e.g. `David via Claude`. Use your own identity so the log shows who made each change (agent writes record `via=claude`). Every create/update/delete appends one `audit_log` row per changed field, with a UTC timestamp and the old→new value (readable at `GET /api/audit`), and is versioned into the repo by `npm run pull`. ## What you may write, and what you may not `GET /api/schema` lists both sets, generated from the same constant the write path enforces, so this document cannot drift from the code. In short: - **Writable** — what a person can learn and correct: `practice_name`, address, `phone`, `fax`, `website` (+ `website_confidence`, `website_proof`), `email` (+ `email_confidence`), `ownership` (+ `owner_name`, `ownership_reason`, `ownership_confidence`), `mso`, `status`, `research_confidence`, `notes`, `ehr_source`. - **Read-only** — measurements: `cardiologists`, every `echo_*` figure, `tafamidis_*`, the CMS group fields, and the roster. The method page states how each was derived, so an edit here would make the page lie. Change these by re-running the pipeline, not the API. - **Custom fields** — anything the schema does not have goes in the `fields` object on `PATCH /api/practices/:id` (a key of 1–40 letters, digits or underscore). This is how the tracker carries an owner, a call outcome, or a next step with no migration and no deploy. Set a custom field to `null` to delete it. ## Enums and the confidence ladder - `website_confidence` / `email_confidence` / `ownership_confidence`: `verified`, `detected`, `reported`, `inferred`, `unknown`. A published fact and a guessed one look the same without one. - `ownership`: `independent`, `system`, `government`, `safety-net`. `status`: `active`, `closed`, `unclear`. - `verified` = the practice's own site states it; `detected` = fingerprinted on the practice's own site; `reported` = a secondary source; `inferred` = derived, not confirmed; `unknown` = not established. ## Rules (do not break these) 1. **Never invent a value.** Leave a field empty rather than guess. An invented email or website reaches a real practice. 2. A `website` must be an absolute `http(s)` URL and must carry a `website_confidence`. The write is refused without one. 3. An `email` must be one the practice publishes. Never build one from a name pattern. 4. **Say where a fact came from.** Put the source in `sources` or `ownership_reason`, and set the matching confidence marker. 5. **Read before you write.** GET the practice (or `GET /api/practices?q=`) and confirm it is the right one. A PATCH that repeats the current value is a no-op and is not audited, so an unexpected diff means you were working from stale data. ## Playbook for updating from research 1. **Find the practice.** `GET /api/practices?q=`; check the address before you act. If it is genuinely new, `POST /api/practices` (`id` and `practice_name` required); otherwise `PATCH` it. 2. **Verify, don't guess.** `website_confidence: "verified"` / `email_confidence: "verified"` only when the value came straight from the practice's own site; otherwise `reported` or `inferred`. Never invent. 3. **Record provenance.** Put the URL or source in `sources` or `ownership_reason`. 4. **Ownership moves a practice in or out.** If research shows a hospital or health system owns it, set `ownership: "system"`, `ownership_reason`, `ownership_confidence`, and `status` — the tracker is for independent practices only. 5. **Free-form facts go in `fields`** — an `owner`, a `next_step`, a call outcome — no migration needed. ## Example — correct a website and record a next step ``` PATCH /api/practices/18450%20HIGHWAY%2059%20N%7C%7CHUMBLE%7C77338 x-tracker-token: x-tracker-editor: David via Claude content-type: application/json { "website": "https://vitalheartandvein.com", "website_confidence": "verified", "email": "info@vitalheartandvein.com", "email_confidence": "verified", "sources": [{"url": "https://vitalheartandvein.com/contact", "note": "site footer"}], "fields": { "owner": "David", "next_step": "demo booked 2026-10-02" } } ``` ## Example — add a practice found in research ``` POST /api/practices x-tracker-token: x-tracker-editor: David via Claude content-type: application/json { "id": "123 MAIN ST||ORLANDO|32801", "practice_name": "Example Cardiology", "address_line1": "123 Main St", "city": "Orlando", "state": "FL", "zip": "32801", "phone": "4075551234", "ownership": "independent", "ownership_confidence": "reported", "website": "https://examplecardiology.com", "website_confidence": "verified" } ```