{
 "name": "InVision Amyloid CRM",
 "purpose": "Go-to-market CRM for InVision Precision Cardiac Amyloid (AI echo-based ATTR-CM detection). An LLM agent (e.g. Claude with a Gmail connector for andrew@invisionmedtech.com) can read email, then add or update contacts here via this API. Read this document first, then dedupe before adding.",
 "base_url": "same origin as this page (use relative /api/... paths)",
 "auth": {
  "required": "The whole API is gated (reads AND writes), except this /api/schema document and POST /api/login. Humans sign in through the web UI which sets an HttpOnly 'crm_session' cookie. Accounts live in the `users` table, each with its own password (stored only as a salted PBKDF2 hash). A new account still on the shared first-time default (CRM_PASSWORD, default 'gotomarket') must set a personal password on first sign-in (login returns must_change:true; POST /api/password completes it). Unauthenticated /api/* returns 401 {error:'unauthorized'}.",
  "agents": "An agent (Claude) authenticates on EVERY request with two headers: 'x-crm-token: <the shared password, or CRM_WRITE_TOKEN if set>' AND 'x-crm-editor: <who you act for> via Claude' (e.g. 'David via Claude Code'). The x-crm-editor value is recorded as the actor on every audited change, and such changes show as via='claude' in the log.",
  "audit": "Every login/logout and every create/update/delete is appended to audit_log with a UTC timestamp, the actor, via (ui|claude|api), the entity, and the field-level old->new diff. Read it at GET /api/audit. It is versioned into the repo by `npm run pull`, so the change history lives in git too."
 },
 "endpoints": [
  {
   "method": "GET",
   "path": "/api/contacts",
   "desc": "List contacts (warm-first). Filters: ?warm=HOT|WARM|COLD & ?status=<status> & ?q=<free text over name/institution/role/metro/specialty>.",
   "returns": "{ contacts: [contact] }"
  },
  {
   "method": "POST",
   "path": "/api/contacts",
   "desc": "Create a contact. Body = a contact object; only 'contact_name' is required. Unknown keys are ignored.",
   "returns": "{ contact } (201)"
  },
  {
   "method": "GET",
   "path": "/api/contacts/:id",
   "desc": "One contact by id.",
   "returns": "{ contact }"
  },
  {
   "method": "PATCH",
   "path": "/api/contacts/:id",
   "desc": "Update writable fields. Body = { field: value, ... }. Each change is written to the audit log. No-op fields are skipped.",
   "returns": "{ contact, changed }"
  },
  {
   "method": "DELETE",
   "path": "/api/contacts/:id",
   "desc": "Delete a contact.",
   "returns": "{ deleted }"
  },
  {
   "method": "GET",
   "path": "/api/systems",
   "desc": "Health systems ranked, with aggregate signal + key/known contacts.",
   "returns": "{ systems: [system] }"
  },
  {
   "method": "PATCH",
   "path": "/api/systems/:id",
   "desc": "Update a system's last_contact, owner, status, deal_stage, notes. Each change is audited.",
   "returns": "{ system, changed }"
  },
  {
   "method": "POST",
   "path": "/api/login",
   "desc": "Human sign-in {username, password} -> sets crm_session cookie. Agents use x-crm-token instead.",
   "returns": "{ ok, user, must_change }"
  },
  {
   "method": "POST",
   "path": "/api/password",
   "desc": "Change your own password {current_password, new_password} (>=8 chars). Requires a session.",
   "returns": "{ ok }"
  },
  {
   "method": "POST",
   "path": "/api/logout",
   "desc": "Clear the session cookie.",
   "returns": "{ ok }"
  },
  {
   "method": "GET",
   "path": "/api/me",
   "desc": "Current signed-in user + how (ui|claude|api).",
   "returns": "{ user, via }"
  },
  {
   "method": "GET",
   "path": "/api/audit",
   "desc": "Activity log, newest first. Filters: ?entity=contact|system|session & ?actor= & ?action= & ?via= & ?q=<text> & ?limit=.",
   "returns": "{ audit: [row] }"
  },
  {
   "method": "GET",
   "path": "/api/next_steps",
   "desc": "The manual priority-next-steps queue (open first, then done).",
   "returns": "{ next_steps: [item] }"
  },
  {
   "method": "POST",
   "path": "/api/next_steps",
   "desc": "Add a queue item. Body {label (required), institution?, note?, source?, kind: contact|system|adhoc, contact_id?}.",
   "returns": "{ next_step } (201)"
  },
  {
   "method": "PATCH",
   "path": "/api/next_steps/:id",
   "desc": "Check off / reopen (body {done:1|0}, stamps done_by/done_at) or edit label/note/source. Audited.",
   "returns": "{ next_step, changed }"
  },
  {
   "method": "DELETE",
   "path": "/api/next_steps/:id",
   "desc": "Remove a queue item. Audited.",
   "returns": "{ ok, id }"
  },
  {
   "method": "GET",
   "path": "/api/schema",
   "desc": "This document (public).",
   "returns": "schema JSON"
  }
 ],
 "required_to_create": [
  "contact_name"
 ],
 "writable_fields": {
  "contact_name": "Full name. REQUIRED to create.",
  "institution": "Health system / hospital (or company, for partner/channel/pharma contacts).",
  "metro": "'CITY, ST' of the person's main campus, e.g. 'BOSTON, MA'.",
  "role_title": "Their role / title in prose.",
  "specialty": "Clinical specialty, e.g. 'Echocardiography', 'Advanced Heart Failure'.",
  "seniority": "e.g. 'Professor', 'Director', 'MD'. Leave blank rather than guessing.",
  "priority_rank": "Integer metro priority rank (lower = higher priority). Blank for non-hospital (partner) rows.",
  "priority_score": "Numeric composite priority score (optional).",
  "amyloid_program_director": "'Y' if they lead a cardiac amyloidosis program, else 'N'.",
  "echo_lab_director": "'Y' if they direct the echo lab / cardiac imaging, else 'N'.",
  "service_line_lead": "'Y' if a CV division chief / service-line / operations / value leader, else 'N'.",
  "cardiology_informatics_lead": "'Y' if the cardiology informatics / clinical-AI / digital-health decision-maker, else 'N'.",
  "amyloid_publications": "Rough level: 'Low' | 'Moderate' | 'High' (optional).",
  "echo_publications": "Rough level: 'Low' | 'Moderate' | 'High' (optional).",
  "tafamidis_claims": "Numeric string — CMS tafamidis claim volume (usually only on prescriber rows).",
  "tafamidis_benes": "Numeric string — CMS tafamidis beneficiaries.",
  "public_email": "Best email from a public/verifiable source or a work-email thread. NEVER fabricate.",
  "email_source": "Provenance sentence, e.g. 'InVision work email (Penn Medicine <> InVision - Echo AI Follow-up)'.",
  "verification": "'verified' (from a signature / institutional page / thread) | 'reported' (secondary / inferred) | 'pattern-inferred' (constructed from a domain convention) | 'not found'.",
  "prior_contact_with_david": "'Y' if David/InVision has already engaged them, else 'N'.",
  "personal_contact_gmail": "Provenance marker shown as a chip. Use '◆ Work email' for anything sourced from the InVision work inbox; '★ Gmail' = a confirmed personal contact; '◇ Candidate' = a researched gap-fill; '◈ crm.ivmtc.com' = imported from the external ivmtc CRM; '✦ Drew' = sourced from Drew's (Andrew's) own contacts/inbox; '? Gmail (confirm)' = a possible personal match; '' otherwise.",
  "known_email_from_gmail": "A known private/alternate email (from David's inbox).",
  "warm_cold": "'HOT' | 'WARM' | 'COLD'. Warmth of the relationship / deal; drives the top-of-list sort.",
  "suggested_outreach": "One-line outreach angle (optional).",
  "notes_cautions": "Research notes / cautions.",
  "status": "Coarse CRM status: 'New' | 'Contacted' | 'Meeting' | 'Pilot' | 'Won' | 'Lost' | 'Do not contact'.",
  "deal_stage": "InVision Playbook sales stage: '01 - Disco & Qual' | '02 - Demo & Meetings' | '03 - Proposal & Value Analysis' | '04 - Scoping & Purchase' | '05 - Contracting' | 'Closed Won' | 'Closed Lost' | 'Hold' | 'Do not contact'. Blank for cold prospects not yet in an active motion. Drives the Pipeline board.",
  "icp_tier": "ICP tier from the account-tiering model: 'Tier 1' (High / Hunt) | 'Tier 2' (Nurture / Opportunistic) | 'Tier 3' | 'Channel/Pharma' (partners). Seeded from the team's authoritative account tiering.",
  "buyer_persona": "InVision Playbook buyer persona: 'Amyloid Treating Cardiologist' | 'Echo Lab Medical Director' | 'CMIO / AI Governance' | 'Chair / VP CVSL (economic buyer)' | 'IT Sourcing / Procurement' | 'Channel / OEM / Pharma partner'.",
  "champion": "'Y' if this person is an engaged internal champion for the deal, else 'N'.",
  "economic_buyer": "Who signs / owns the budget (name or role); part of MEDDPICC. Blank if unknown.",
  "competitor": "Competing solution in this account, e.g. 'Us2.ai'. Blank if none known.",
  "owner": "Deal owner (e.g. 'Andrew Reeve').",
  "last_contacted": "Free-text or date of last contact.",
  "next_step": "The next action.",
  "crm_notes": "Free-form working notes."
 },
 "enums": {
  "warm_cold": [
   "HOT",
   "WARM",
   "COLD"
  ],
  "status": [
   "New",
   "Contacted",
   "Meeting",
   "Pilot",
   "Won",
   "Lost",
   "Do not contact"
  ],
  "deal_stage": [
   "01 - Disco & Qual",
   "02 - Demo & Meetings",
   "03 - Proposal & Value Analysis",
   "04 - Scoping & Purchase",
   "05 - Contracting",
   "Closed Won",
   "Closed Lost",
   "Hold",
   "Do not contact",
   ""
  ],
  "icp_tier": [
   "Tier 1",
   "Tier 2",
   "Tier 3",
   "Channel/Pharma",
   ""
  ],
  "buyer_persona": [
   "Amyloid Treating Cardiologist",
   "Echo Lab Medical Director",
   "CMIO / AI Governance",
   "Chair / VP CVSL (economic buyer)",
   "IT Sourcing / Procurement",
   "Channel / OEM / Pharma partner",
   ""
  ],
  "verification": [
   "verified",
   "reported",
   "pattern-inferred",
   "not found"
  ],
  "yes_no_flags": [
   "Y",
   "N"
  ],
  "provenance_markers": [
   "★ Gmail",
   "◆ Work email",
   "◇ Candidate",
   "◈ crm.ivmtc.com",
   "✦ Drew",
   "? Gmail (confirm)",
   ""
  ]
 },
 "deal_stage_ladder": [
  "01 - Disco & Qual (GATE: scheduled meeting with a decision-maker/influencer; qualify echo volume, tafamidis, amyloid COE, IT maturity, competitors)",
  "02 - Demo & Meetings (GATE: meeting held, D&Q + MEDDPICC captured, committed next step)",
  "03 - Proposal & Value Analysis (GATE: MEDDPICC complete; proposal / value analysis requested or RFI/RFP initiated)",
  "04 - Scoping & Purchase (GATE: proposal accepted, named VOC, purchase process initiated)",
  "05 - Contracting (GATE: fully executed agreement (MSA + Order Form + BAA); internal hand-off scheduled)",
  "Closed Won / Closed Lost / Hold (win-loss captured; onboarding initiated on a win)"
 ],
 "meddpicc": {
  "M": "Metrics — how the customer measures success",
  "E": "Economic Buyer — owns the budget",
  "D": "Decision Process — demos/RFI/RFP/committee/AI governance",
  "D2": "Decision Criteria — how they score alternatives",
  "P": "Paper/Purchase Process — what it takes to get a signed contract",
  "I": "Identified Pain",
  "C": "Champion — has authority/influence and wants the deal",
  "C2": "Competition — rival vendor or 'do nothing'"
 },
 "competitors": {
  "Ultromics": "HIGH / primary. EchoGo Amyloidosis (IFU ≥65 AND heart failure). Sites incl. UPMC, Northwestern, UChicago, Mayo, University Hospitals.",
  "Us2.ai": "MEDIUM / channel. OEM embeds (Fujifilm/Merge/Canon/GE); won Sutter. No amyloid reimbursement code.",
  "MyCardium": "MEDIUM / watch. Measurements & reporting only, no detection IFU; UTSW head-to-head.",
  "do nothing": "The real competitor in most rooms — 'our readers would catch it'."
 },
 "stage_heuristics": {
  "Contacted": "Cold intro sent, no substantive reply yet.",
  "Meeting": "Met or in active back-and-forth; also formal RFI / pricing / ROI / contract scoping.",
  "Pilot": "A pilot or evaluation is actually underway.",
  "Won": "Signed / deployed.",
  "Lost": "They declined or chose a competitor (note who in notes_cautions)."
 },
 "agent_playbook": [
  "1. DEDUPE FIRST: GET /api/contacts?q=<last name> (and check institution) before creating — never add a duplicate person. If they exist, PATCH the existing id instead.",
  "2. When the information comes from the InVision work inbox, set personal_contact_gmail='◆ Work email', email_source='InVision work email (<thread subject>)', and prior_contact_with_david='Y'.",
  "3. Set verification='verified' only when the email/title came straight from a signature or institutional page; otherwise 'reported'. NEVER invent an email, name, or title.",
  "4. Map deal stage to status + warm_cold using stage_heuristics. A system that declined -> status='Lost' or 'Do not contact', warm_cold='COLD', and say why in notes_cautions.",
  "5. Set the director flags (amyloid/echo/service-line/informatics) from the person's actual role. These drive the Priority-gaps view.",
  "6. Authenticate EVERY request: headers 'x-crm-token: <shared password / CRM_WRITE_TOKEN>' AND 'x-crm-editor: <who you act for> via Claude'. The editor identity is logged as the actor on every change (via='claude'); without valid auth the API returns 401.",
  "7. Prefer PATCH for status/next_step/owner/last_contacted updates as a deal advances; every change is audited.",
  "8. Keep deal_stage current on the InVision Playbook ladder (see deal_stage_ladder) as accounts move — a meeting scheduled -> '01 - Disco & Qual'; meeting held with MEDDPICC -> '02 - Demo & Meetings'; proposal/VA/RFI -> '03 - Proposal & Value Analysis'; purchase process started -> '04 - Scoping & Purchase'; contract out (MSA/OF/BAA) -> '05 - Contracting'. Fill MEDDPICC-lite when the email reveals it: champion='Y' for an engaged advocate, economic_buyer (who owns budget), competitor (Ultromics / Us2.ai / MyCardium / 'do nothing'), buyer_persona, icp_tier."
 ],
 "examples": {
  "add_contact": {
   "method": "POST",
   "path": "/api/contacts",
   "headers": {
    "content-type": "application/json",
    "x-crm-editor": "andrew@invisionmedtech.com via Claude"
   },
   "body": {
    "contact_name": "Jane Cardiologist",
    "institution": "Example Health",
    "metro": "AUSTIN, TX",
    "role_title": "Director, Echocardiography Lab",
    "specialty": "Echocardiography",
    "echo_lab_director": "Y",
    "public_email": "jane.cardiologist@examplehealth.org",
    "email_source": "InVision work email (Example Health <> InVision - Echo AI Chat)",
    "verification": "verified",
    "prior_contact_with_david": "Y",
    "personal_contact_gmail": "◆ Work email",
    "warm_cold": "WARM",
    "status": "Meeting",
    "owner": "Andrew Reeve",
    "next_step": "Send pricing + schedule technical eval",
    "crm_notes": "Met at ASE; interested in Syngo integration."
   }
  },
  "update_stage": {
   "method": "PATCH",
   "path": "/api/contacts/123",
   "headers": {
    "content-type": "application/json",
    "x-crm-editor": "andrew@invisionmedtech.com via Claude"
   },
   "body": {
    "status": "Pilot",
    "warm_cold": "HOT",
    "last_contacted": "2026-09-01",
    "next_step": "Kick off 30-day evaluation"
   }
  }
 }
}