# Colorado Medicaid — payer specifics Cyclone's CO Medicaid (`PayerConfig.co_medicaid()` and `PayerConfig835.co_medicaid_835()`) factories encode the trading-partner and code-set rules that the Colorado Department of Health Care Policy & Financing (HCPF) requires. ## Trading Partner IDs (TPID) | Role | 837P | 835 | |---|---|---| | Sender | `SKCO0` | (varies; usually the clearinghouse) | | Receiver | `COHCPF` | (varies) | These appear in the `NM1*PR` (payer) and `NM1*40` (receiver) segments of the 837P file. ## dzinesco's TPID (clearinghouse identity) | Field | Value | |---|---| | Clearinghouse name | `dzinesco` | | dzinesco TPID | `11525703` | | Submitter name (`NM1*41`) | `Dzinesco` | | Submitter contact | Tyler Martinez | | SFTP host | `mft.gainwelltechnologies.com` | | SFTP username | `colorado-fts\coxix_prod_11525703` | | SFTP outbound dir | `/CO XIX/PROD/coxix_prod_11525703/FromHPE` | | SFTP inbound dir | `/CO XIX/PROD/coxix_prod_11525703/ToHPE` | ## dzinesco's 3 billing-provider NPIs All 3 NPIs are registered to the same Montrose corporate office. They share tax ID `721587149` and taxonomy `251E00000X` (Home Health). | NPI | Label | Legal name | Address | |---|---|---|---| | `1881068062` | Montrose | TOC, Inc. | 1100 East Main St, Suite A, Montrose, CO 814014063 | | `1851446637` | Delta | TOC, Inc. | 1100 East Main St, Suite A, Montrose, CO 814014063 | | `1467507269` | Salida | TOC, Inc. | 1100 East Main St, Suite A, Montrose, CO 814014063 | Production data (June 2026) confirms all 3 NPIs submit through the same Montrose address block. See SP9 spec `docs/superpowers/specs/2026-06-20-cyclone-multi-payer-npi-sftp-design.md`. ## SFTP submission to Gainwell dzinesco submits 837P files to Gainwell's MFT (Managed File Transfer) at `mft.gainwelltechnologies.com`. The full SFTP path layout is specified by the user (2026-06-20): - **Outbound** (we send): `/CO XIX/PROD/coxix_prod_11525703/FromHPE` - **Inbound** (HPE/Gainwell sends to us): `/CO XIX/PROD/coxix_prod_11525703/ToHPE` ### File naming Per the HCPF X12 File Naming Standards Quick Guide (): - **Outbound** (we send): `TPID-TransactionType-yyyymmddhhmmssSSS-1of1.X12` - Example: `11525703-837P-20260620132243505-1of1.x12` - 17-digit millisecond precision, Mountain Time (NOT UTC) - "1of1" is the only accepted sequence value - **Inbound** (HPE sends): `TP-_M--1of1_.x12` - Example: `TP11525703-837P_M019048402-20260520231513488-1of1_999.x12` - `FileType` is one of: `999`, `TA1`, `270`, `271`, `276`, `277`, `277CA`, `278`, `820`, `834`, `835`, `ENCR` - 277CA is distinguished by `ST*277CA` content (filename uses `277`) ### SP9 stub vs SP13 wire-up The SP9 SFTP client is a **stub** that writes generated 837 files to `./var/sftp/staging/{outbound_path}/{filename}` instead of opening a real SFTP connection. The structural interface matches the future `paramiko`-backed implementation, so SP13 is a one-file swap. `clearhouse.sftp_block.stub` is `true` by default. Set to `false` (and create the Keychain entry, see below) to enable real SFTP. ### Keychain setup (one-time, by operator) ```sh security add-generic-password -s cyclone -a sftp.gainwell.password -w '' security find-generic-password -s cyclone -a sftp.gainwell.password -w ``` The `cyclone/secrets.py` module fetches the secret by name. When the entry is missing or `keyring` is not installed, the SFTP stub falls back to `` so the local flow still works. ### Verification ```sh # List providers (3 NPIs) curl http://localhost:8000/api/config/providers # List payers (1 = CO_TXIX) curl http://localhost:8000/api/config/payers # List CO_TXIX configs curl http://localhost:8000/api/config/payers/CO_TXIX/configs # Clearhouse identity curl http://localhost:8000/api/clearhouse # Submit 2 claims to the SFTP stub curl -X POST http://localhost:8000/api/clearhouse/submit \ -H 'Content-Type: application/json' \ -d '{"claim_ids": ["CLM-1", "CLM-2"], "payer_id": "CO_TXIX"}' # Files appear at ./var/sftp/staging/CO XIX/PROD/coxix_prod_11525703/FromHPE/ ls -la "./var/sftp/staging/CO XIX/PROD/coxix_prod_11525703/FromHPE/" ``` ## Payer IDs ### 837P (claims) - `NM1*PR N104 = "SKCO0"` (COHCPF) ### 835 (remittance) - `BPR10 = "81-1725341"` — TXIX (Medicaid) account - `BPR10 = "84-0644739"` — BHA (behavioral health) account - `N1*PR N104 = "7912900843"` — payer ID - `N1*PR N102` is one of `"CO_TXIX"` or `"CO_BHA"` ## Allowed codes ### 837P `CLM05-3` (frequency code) | Code | Meaning | |---|---| | 1 | Original claim | | 7 | Replacement claim | | 8 | Void / cancel | Cyclone raises `R030_frequency_allowed` if any other value appears. ### 835 `CLP02` (claim status code) Allowed: `{1, 2, 3, 4, 19, 20, 21, 22, 23, 25}`. See the 835 reference note for the meaning of each. ### Place of service (POS) All CMS POS codes `01`–`99` are accepted. The canonical list lives in `cyclone/parsers/payer.py` as `CMS_PLACE_OF_SERVICE_CODES` and is the source of truth for validation and any UI dropdowns. ## Database encryption at rest (SP12) Cyclone optionally encrypts the SQLite database with SQLCipher (AES-256). The encryption key is stored in macOS Keychain — never on disk in plaintext. Without the key, the DB falls back to plain SQLite. ### One-time operator setup ```bash # 1. Install SQLCipher (C library) and the Python binding. brew install sqlcipher pip install -e backend[sqlcipher] # 2. Generate a random 32-byte key and store it in Keychain. python3 -c "import secrets; print(secrets.token_urlsafe(32))" \ | xargs -I {} security add-generic-password \ -s cyclone -a cyclone.db.key -w "{}" # 3. Restart Cyclone. The DB is now encrypted. ``` ### Verification ```bash # The DB file should be unreadable as plain SQLite. sqlite3 ~/.local/share/cyclone/cyclone.db "SELECT count(*) FROM sqlite_master" # → file is not a database # But readable through Cyclone. curl http://localhost:8000/api/claims # → 200 OK ``` ### Key rotation (future) To rotate the key: 1. Decrypt with old key, dump to SQL 2. Re-encrypt with new key, import SQL 3. Update Keychain A first-class rotation endpoint is out of scope for SP12 (planned for SP14+). ## Audit log (SP11) Cyclone persists every state-changing event to a tamper-evident hash-chained audit log. Each row carries a SHA-256 hash of `(id, event_type, entity_type, entity_id, actor, payload_json, created_at, prev_hash)`, where `prev_hash` is the previous row's hash. Modifying any row's payload invalidates every subsequent row's hash. To list events: ```bash curl 'http://localhost:8000/api/admin/audit-log?entity_type=claim&entity_id=C-123' ``` To verify the chain (run nightly or on demand): ```bash curl http://localhost:8000/api/admin/audit-log/verify # → {"ok": true, "checked": 1234} # → {"ok": false, "checked": 1180, "first_bad_id": 1181, "reason": "hash mismatch ..."} ``` Events written today: - `claim.rejected` (999 ACK AK5 R/E) - `claim.payer_rejected` (277CA STC A4/A6/A7) - `clearhouse.submitted` (SFTP submit) Compliance: HIPAA §164.316(b)(2) requires 6-year retention. The schema doesn't enforce retention — that's a separate vacuum job. ## 277CA Claim Acknowledgment (SP10) After Gainwell accepts our 837P file (999 AK5=A) and adjudicates the claims, they send back a 277CA per X12 005010X214. The 277CA carries one `STC` segment per claim with a category code: | STC code | Meaning | Cyclone lane | |---|---|---| | A1, A2, A3 | Acknowledged / accepted | (logged, no action) | | A4, A6, A7 | Rejected by payer | **Inbox Payer-Rejected** | | A8, A9 | Pended | (logged for follow-up) | | P1–P5 | Paid | (835 follow-up expected) | The Payer-Rejected lane is distinct from the 999 envelope "rejected" lane: a claim can be syntactically valid (999 A) but semantically denied (277CA STC A6). To upload a 277CA: ```bash curl -X POST http://localhost:8000/api/parse-277ca \ -F "file=@TP11525703-837P_M019048402-...-1of1_277.x12" ``` The response includes `matched_claim_ids` (which Cyclone claims were stamped payer-rejected) and `orphan_status_codes` (status entries we couldn't tie to a Cyclone claim — usually because the PCN in REF*1K doesn't match anything we sent). The Inbox at `/api/inbox/lanes` returns the new `payer_rejected` lane alongside the existing four. ## Validation rules Cyclone enforces See [837p.md](./837p.md#validation-rules-cyclone-enforces) and the `cyclone.parsers.validator` / `cyclone.parsers.validator_835` modules. ## Adding a new payer The pattern is: 1. Add a `PayerConfig` (837P) and `PayerConfig835` (835) factory in `cyclone/parsers/payer.py` — usually thin wrappers that override only the differing fields (allowed codes, payer IDs). 2. Register the factory in the `PAYER_FACTORIES` / `PAYER_FACTORIES_835` dicts in `cyclone/api.py` and `cyclone/cli.py`. 3. Add a smoke fixture under `backend/tests/fixtures/` (a real or synthetic file matching the new payer's rules) and a parser test that loads it. 4. Update this note + the 837p/835 notes with the new payer's specifics.