c62826daea
- providers table seeded with 3 NPIs (Montrose, Delta, Salida) - payers + payer_configs (per-tx config) join table - clearhouse singleton (dzinesco identity, SFTP block, filename block) - config/payers.yaml loader with Pydantic schema validation - cyclone/edi/filenames.py: HCPF X12 File Naming Standards helpers - cyclone/clearhouse/sftp.py: stub that copies to ./var/sftp/staging/... - cyclone/secrets.py: macOS Keychain wrapper via keyring - 11 new validation rules R200-R210 (CO MAP + HCPF naming) - 6 new API endpoints (/api/clearhouse/*, /api/config/*, /api/admin/reload-config) - migration 0007 - 72 new tests (646 total passing, was 574) See spec: docs/superpowers/specs/2026-06-20-cyclone-multi-payer-npi-sftp-design.md
168 lines
5.8 KiB
Markdown
168 lines
5.8 KiB
Markdown
# 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 <tyler@dzinesco.com> |
|
||
| 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
|
||
(<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/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.
|
||
|
||
## 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.
|