# Cyclone A self-hosted EDI claims management suite for a single billing office. Parses 837P professional claims and 835 ERA remittances (X12 005010X222A1 and 005010X221A1), with a local-only FastAPI backend and a React UI for browsing, filtering, and inspecting the parsed data. Local-only on purpose: binds to `127.0.0.1`, no auth, no internet exposure. Built for one operator, one machine, one trading partner (Colorado Medicaid, currently). ## Install ```bash # Backend (Python 3.11+) cd backend python -m venv .venv .venv/bin/pip install -e '.[dev]' # Frontend (Node 20+) cd .. npm install ``` ## Dev Two terminals: ```bash # Terminal 1 — backend cd backend .venv/bin/python -m cyclone serve # (defaults to 127.0.0.1:8000; override with CYCLONE_PORT=...; reload with CYCLONE_RELOAD=1) # Terminal 2 — frontend npm run dev # (Vite on http://localhost:5173) ``` Then open `http://localhost:5173`. Drop an `.txt` 837P or 835 file on the Upload page; navigate to Claims, Remittances, Providers, or Activity to see the parsed data. The frontend reads its backend URL from `VITE_API_BASE_URL` (default empty). Create a `.env.local` at the repo root with: ``` VITE_API_BASE_URL=http://127.0.0.1:8000 ``` Without that, the UI falls back to its in-memory sample store via the existing `data` adapter (parses are disabled). ## Test ```bash # Backend cd backend && .venv/bin/pytest # Frontend type-check + build npm run typecheck npm run build # Frontend unit tests npm test ``` ## Persistence Parsed batches, claims, remittances, matches, and activity events are stored in a SQLite file at `~/.local/share/cyclone/cyclone.db` by default. The directory is auto-created on first run. To use a different location, set `CYCLONE_DB_URL`: ```bash export CYCLONE_DB_URL=sqlite:///path/to/cyclone.db # or export CYCLONE_DB_URL=postgresql://user:pass@host:5432/cyclone ``` ### Backup ```bash sqlite3 ~/.local/share/cyclone/cyclone.db ".backup /path/to/backup.db" ``` This is safe to run while the backend is running (uses SQLite's online backup API). ## Project layout ``` . ├── backend/ │ ├── src/cyclone/ │ │ ├── api.py # FastAPI app, GET + parse routes │ │ ├── store.py # InMemoryStore, mappers │ │ ├── __main__.py # `python -m cyclone serve` │ │ ├── cli.py # click CLI │ │ └── parsers/ # X12 tokenizer, models, validator, writers │ └── tests/ │ ├── fixtures/ # co_medicaid_*.txt, minimal_*.txt │ ├── test_api.py # parse-837/835 round-trip │ ├── test_api_gets.py # 6 GET endpoints │ ├── test_store.py # store + mappers + iterators │ ├── test_api_streaming.py │ └── test_api_parse_persists.py ├── src/ # React + Vite + TypeScript UI │ ├── components/ │ │ └── ui/ # Skeleton, EmptyState, ErrorState, FilterChips, Pagination, … │ ├── pages/ # Claims, Remittances, Providers, Activity, Upload │ ├── hooks/ # useBatches, useClaims, useRemittances, useProviders, useActivity, useParse │ ├── lib/ # api.ts (6 GET + parse837/parse835/health), format.ts, utils.ts │ ├── store/ # zustand sample-data + parsed-batches store │ └── types/ # shared TS types ├── docs/ │ ├── reference/ # condensed 837P/835/X12/CO Medicaid notes │ └── superpowers/plans/ # implementation plan ├── tailwind.config.js # shimmer, scan, row-flash keyframes └── package.json ``` ## Roadmap Sub-projects 2 and 3 are **shipped**. Next up: - **Sub-project 3 (shipped) — More 837P/835 features.** - **837P validation rules:** R034 enforces `REF*G1` on frequency-code 7/8 claims; R035 enforces `BHT06` transaction-type-code is in the allowed set per payer config. - **835 CAS deep-parsing:** every CAS adjustment now surfaces with its CARC reason code + label (e.g. `CO-29: The time limit for filing has expired`), surfaced via `GET /api/remittances/{id}` and rendered as an expansion row on the Remittances page. - **999 ACK transaction set:** full inbound parser + outbound serializer. Auto-generated on 837 ingest when `?ack=true` is passed to `POST /api/parse-837`. Persisted to the `acks` table, browsable on a new `/acks` page, and downloadable as the regenerated raw 999 text. - **270/271 eligibility (API-only):** `POST /api/eligibility/request` builds a 270 from a JSON payload (subscriber, provider, payer, service type); `POST /api/eligibility/parse-271` ingests the response and returns structured `coverage_benefits`. - **Sub-project 4 — Frontend features.** Per-claim detail drawer, batch diff view, real-time streaming of NDJSON GET responses, advanced filters (date range, multi-status, saved filter sets), keyboard-driven navigation. ### SP3 endpoints - `POST /api/parse-837?ack=true` — existing 837 parse, plus optional auto-generated 999 ACK persisted alongside the batch. - `POST /api/parse-999` — parse an inbound 999 ACK and persist it. - `GET /api/acks` — list ACKs. - `GET /api/acks/{id}` — ACK detail, including the regenerated `raw_999_text`. - `POST /api/eligibility/request` — build a 270 from JSON. - `POST /api/eligibility/parse-271` — ingest a 271 and return parsed coverage benefits. The UI gains a new **Acks** page (sidebar entry) that lists persisted ACKs and lets you download the regenerated 999 text. ## License No license file yet; this is internal-use software. Add a `LICENSE` file when one is decided.