# 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 (submitter, `NM1*41`) | `Dzinesco` (TPID `11525703`) | (varies; usually the clearinghouse) |
| Receiver (`NM1*40`) | `COMEDASSISTPROG` | (varies) |
| Payer (`NM1*PR`) | `CO_TXIX` (per HCPF 837P Companion Guide) | (varies) |

These appear in the `NM1*PR` (payer) and `NM1*40` (receiver) segments of
the 837P file. The legacy codes `SKCO0` / `COHCPF` are no longer
accepted by Gainwell (HCPF error: "2010BB NM109 must equal CO_TXIX or
CO_BHA"); see [SP33](../superpowers/specs/2026-07-02-cyclone-co-txix-payer-fix-design.md).

## dzinesco's TPID (clearinghouse identity)

| Field | Value |
|---|---|
| Clearinghouse name | `dzinesco` |
| dzinesco TPID | `11525703` |
| Submitter name (`NM1*41`) | `Dzinesco` |
| Submitter contact | Tyler Martinez <tyler@dzinesco.com> |
| SFTP host | `mft.gainwelltechnologies.com` |
| SFTP username | `colorado-fts\coxix_prod_11525703` |
| SFTP outbound dir | `/CO XIX/PROD/coxix_prod_11525703/ToHPE` |
| SFTP inbound dir | `/CO XIX/PROD/coxix_prod_11525703/FromHPE` |

## 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/ToHPE`
- **Inbound** (HPE/Gainwell sends to us): `/CO XIX/PROD/coxix_prod_11525703/FromHPE`

### File naming

Per the HCPF X12 File Naming Standards Quick Guide
(<https://hcpf.colorado.gov/tp-x12-filenaming>):

- **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<TPID>-<OrigTx>_M<Tracking>-<ts>-1of1_<FileType>.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 '<password>'
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 `<stub-secret>` 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/ToHPE/
ls -la "./var/sftp/staging/CO XIX/PROD/coxix_prod_11525703/ToHPE/"
```

## Payer IDs

### 837P (claims)

- `NM1*PR NM108 = "PI"`, `NM1*PR NM109 = "CO_TXIX"` (per HCPF 837P Companion Guide, June 2025 — Version 2.5). dzinesco submits against this code under dzinesco TPID `11525703`. The legacy trading-partner ID `SKCO0` is no longer accepted as the payer identifier.

### 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.
