## Inspiration

I was studying with a close friend when he mentioned that his parents — physicians practicing in the Bay Area — were dealing with prior authorization denials again. A patient needed a time-sensitive procedure. The insurance company denied it. Staff spent hours on hold, resubmitting the same documentation. The procedure was delayed by weeks.

In time-sensitive cases, weeks can mean permanent harm. That's not a process problem — that's a patient safety problem.

I started researching. What I found was worse than I expected.

$$39 \text{ PA requests} \times 52 \text{ weeks} = 2{,}028 \text{ requests per physician per year}$$

$$13 \text{ hrs/week} \times 52 \text{ weeks} = 676 \text{ hours of staff time annually — not seeing patients}$$

And the number that changed everything:

$$\boxed{81.7\% \text{ of appealed denials are overturned}}$$

Most are never appealed. Not because the cases aren't winnable — because nobody has time to fight them.

I had just learned about Jac and Object Spatial Programming at JacHacks. The moment I understood that computation could travel to data through a persistent graph — I knew exactly what to build.

**Persist.** The first autonomous prior authorization agent that doesn't just submit — it fights.

---

## What It Does

Persist closes five gaps that every other prior auth tool ignores:

**Gap 1 — Pre-submission intelligence.** Before any PA is submitted, Persist audits the request against payer-specific criteria and flags missing documentation. For UHC with InterQual step therapy requirements, missing treatment history triggers a critical gap with an estimated approval probability drop from 85% to 35%. Nobody else does this.

**Gap 2 — Automatic clinical note intake.** Upload a physician's clinical note — Persist extracts patient name, date of birth, member ID, ICD-10 codes, CPT codes, and treatment history automatically. A 24-minute manual data entry task becomes a 3-second file upload.

**Gap 3 — Autonomous denial fighting.** When a payer denies a claim, Persist automatically parses the denial letter, identifies the exact criterion that failed, scores appeal viability using AMA 2024 data, drafts a clinically accurate ERISA-compliant appeal letter, and submits it — all without human intervention.

**Gap 4 — CMS-0057-F compliance monitoring.** Persist monitors every active case against federal response deadlines:

$$\Delta t_{\text{expedited}} \leq 72 \text{ hours}, \quad \Delta t_{\text{standard}} \leq 7 \text{ calendar days}$$

When a payer misses a deadline, Persist auto-escalates with the specific CMS-0057-F citation.

**Gap 5 — Institutional memory.** Every processed denial writes a `DenialPattern` node to the Jac graph. Every won appeal updates the winning language. Over time:

$$\text{win\_rate}_{t+1} = \frac{\text{times\_won} + 1}{\text{times\_seen} + 1}$$

Persist gets smarter with every case. The graph memory is the moat.

---

## How I Built It

### The Jac OSP Architecture

Prior authorization has its own polyglot problem. Every payer speaks a different language — UHC uses InterQual, BCBS uses AIM Specialty Health, Aetna uses Clinical Policy Bulletins. Most tools patch this with Python orchestration layers and generic LLM prompts.

Persist solves it with **one language: Jac.**

Jac's Object Spatial Programming model is architecturally perfect for healthcare workflows. Clinical state is inherently relational and persistent. In traditional architectures, representing the full PA lifecycle requires complex JOIN queries across 6+ tables. In Jac, it's a single walker traversal through a typed graph. Computation travels to data — not the other way around.

This is the core principle of Jac that no other language implements — and it's the reason Persist's agent logic is fundamentally different from every Python-based PA tool on the market.

**Graph Schema:**

root └── PatientCase [via ++>] └── PARequest [via HasRequest] ├── MonitorStatus [via HasMonitor] ├── PreSubmissionAudit [via HasAudit] └── DenialRecord [via HasDenial] └── AppealRecord [via HasAppeal] root └── PayerProfile [via HasPayerProfile] └── DenialPattern [via HasDenialPattern]


**8 node types. 7 typed edges. 20+ walkers.** Every clinical relationship explicitly modeled. Every decision traceable.

### The Autonomous Orchestrator

`auto_process_case` is a single walker that executes the full appeal lifecycle in one graph traversal — locating the patient case, checking idempotency to prevent double-processing, calling Claude to analyze the denial and draft the appeal, applying deterministic viability scoring, and persisting the `AppealRecord` node to the graph. Zero human intervention. One walker call. Full lifecycle.

### Deterministic Viability Engine

Appeal viability scoring is **deterministic**, not LLM-dependent. Based on AMA 2024 survey data across 1,000 physicians:

$$V(d) = \begin{cases} 0.82 & \text{if } d = \texttt{medical\_necessity} \\ 0.78 & \text{if } d = \texttt{step\_therapy} \\ 0.95 & \text{if } d = \texttt{administrative} \\ 0.05 & \text{if } d = \texttt{non\_covered} \end{cases}$$

The scoring is baked into the engine — not the prompt. Auditable, consistent, and not subject to LLM hallucination on the most critical decision.

### Payer Intelligence Engine

`PayerProfile` nodes store payer-specific criteria engines, appeal language patterns, step therapy requirements, and deadline timelines:

| Payer | Criteria Engine | Avg Denial Rate | Appeal Overturn |
|-------|----------------|-----------------|-----------------|
| UnitedHealthcare | InterQual v2024.1 | 34% | 81% |
| Blue Cross Blue Shield | AIM Specialty Health | 28% | 79% |
| Aetna | Clinical Policy Bulletins | 25% | 77% |
| Cigna | Proprietary | 22% | 83% |

UHC appeals cite InterQual v2024.1 guideline versions. BCBS appeals cite AIM criteria numbers. Aetna appeals cite the specific CPB number from the denial letter. Payer-specific language — not generic output.

### Appeal Letter Generation

Appeal letters follow ERISA requirements, citing the specific denial reason code (CO-50 for medical necessity, CO-97 for bundling), the exact criterion that failed (e.g., CP.PHAR.118 for UHC step therapy), validated outcome scores (DAS28-CRP for rheumatoid arthritis, Oswestry Disability Index for lumbar spine), and specialty society guidelines (ACR, ADA, ACC) as secondary support. Structured to be submitted directly — no human editing required.

### Real-Time WebSocket Streaming

`agent_status_stream` is an async walker that streams 7 live agent steps to the frontend with real clinical data from the graph at each step — actual payer names, CPT codes, denial criteria, and viability percentages — not generic status text.

### Live OSP Graph Visualization

`get_graph_snapshot` returns the full graph as typed nodes and edges. The frontend renders it as a D3 force-directed graph — 28 nodes, 27 edges, 7 node types, fully interactive. Click any `PatientCase` node and you navigate directly to the case detail. The entire clinical decision graph, visible in real time.

### FHIR R4 Transmission

Appeal submissions are structured as FHIR R4 `Claim/$submit` requests — the standard mandated by CMS-0057-F for all payers by 2027. Every transmission is logged with the full request lifecycle, payer endpoint, HTTP response, and assigned claim reference number.

---

## Challenges I Ran Into

**The byLLM runtime bug** was the most significant. The `by llm()` operator in Jac 0.15 throws `NotImplementedError` when called from standalone module-level functions under jac-scale — it only works within walker `can` abilities. This took the first full day to diagnose. We went through three hypotheses (ability signature mismatch, incorrect cast syntax, wrong import structure) before identifying it as an undocumented runtime scoping constraint. The solution was replacing byLLM with direct Anthropic API calls using lazy client initialization inside each function.

**Rate limits on the free Anthropic tier** (5 requests/minute, 10K tokens/minute) required careful orchestration. The dashboard auto-fires multiple Claude calls on load — we implemented lazy initialization, request guards, and temporary Haiku fallback during development while maintaining Sonnet quality for the final demo.

**Payer-specific clinical accuracy** required deep domain research. UHC's InterQual v2024.1 criteria, BCBS's AIM Specialty Health requirements, Aetna's CPB numbering — these had to be correct, not approximate. A billing specialist reading the appeal letter knows immediately if the language is wrong.

**Windows port permissions** (WinError 10013) blocked ports 8000, 8080, and 8765 sequentially, requiring migration to port 8766 and updates across all Vite proxy and WebSocket configurations.

**UTF-16 encoding corruption.** Cursor and OneDrive repeatedly saved `.jac` files as UTF-16 instead of UTF-8, breaking the Jac parser with hundreds of "Unexpected character" errors on every server restart. Solution: explicit UTF-8 writes via `[System.IO.File]::WriteAllText()` on Windows.

---

## Accomplishments That I'm Proud Of

**The autonomous appeal workflow actually works.** Sarah Johnson's Humira denial — UHC, step therapy, CP.PHAR.118 — goes from denied to appeal_submitted in a single `auto_process_case` walker call. The appeal letter cites the exact criterion, documents the 6-month methotrexate trial, references DAS28-CRP scores above 5.1, and follows ACR guidelines. A real billing specialist would submit this letter.

**The competitive landscape:**

| Tool | Submits | Monitors | Fights Denials | Learns |
|------|---------|----------|----------------|--------|
| Cohere Health | ✓ | ✗ | ✗ | ✗ |
| Waystar | ✓ | Partial | ✗ | ✗ |
| Infinitus | ✓ | ✓ | ✗ | ✗ |
| Thoughtful AI | ✓ | ✓ | Routes to humans | ✗ |
| **Persist** | ✓ | ✓ | **✓ Autonomously** | **✓ Graph memory** |

Nobody autonomously fights denials. That gap is ours.

**The live OSP graph visualization.** Seeing the full clinical decision graph rendered in real time — 28 nodes, 27 edges, every clinical relationship visible — is the most honest demonstration of what Jac's OSP model enables.

**GitHub shows ~34% Jac by line count.** That 34% IS Persist — every walker, every node, every byLLM call, every piece of persistent graph memory. The React frontend is a display layer with zero business logic.

---

## What I Learned

**The moat isn't the LLM — it's the rules engine.** Claude handles two things: parsing unstructured denial letter text and drafting appeal letters. Everything else — viability scoring, payer criteria matching, CMS deadline tracking, gap detection — is deterministic. This is what makes Persist auditable and defensible in a clinical setting.

**Jac's OSP model is architecturally superior for healthcare workflows.** Clinical state is relational, persistent, and multi-hop by nature. The graph isn't just a storage layer — it's the computation model. This is a genuinely better architectural fit than REST APIs over relational databases for this domain.

**Healthcare AI requires clinical accuracy, not just technical correctness.** Generic LLM output fails the billing specialist test. Every denial reason code, every outcome score, every payer criteria reference has to be correct. The bar is: would a real billing specialist submit this letter?

**Validate before you build.** My previous startup failed because I didn't validate the core assumption with domain experts early enough. For Persist, physicians confirmed the problem was real before a single line of code was written.

---

## What's Next for Persist

- **Direct payer portal integration** via FHIR R4 `Claim/$submit` — mandated for all payers by CMS by 2027
- **Multi-practice graph federation** — aggregate denial patterns across practices for industry-wide payer intelligence
- **Automated P2P review scheduling** — book peer-to-peer calls directly from the denial record with pre-generated talking points
- **EHR integration** (Epic, Cerner) for automatic clinical note extraction without manual file upload
- **Real-time denial webhooks** via CMS FHIR notification API — zero human steps from denial to appeal

Built With

Share this project:

Updates