docs/reference/837p.md line 48: the CO Medicaid trading-partner line
was still saying 'SKCO0 (sender) <-> COHCPF (receiver)'. The HCPF
837P Companion Guide (June 2025 - Version 2.5) requires
NM1*PR NM109 = CO_TXIX; SKCO0 causes Gainwell to reject the SET
('2010BB NM109 must equal CO_TXIX or CO_BHA').
docs/reference/co-medicaid.md line 118 + the Trading Partner table at
the top: same fix, with a pointer to the SP33 spec for the
root-cause story.
No code changes. Pure doc fix to match what the source now emits
(PayerConfig.co_medicaid().payer_id = 'CO_TXIX').
9.5 KiB
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.
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
- Example:
- Inbound (HPE sends):
TP<TPID>-<OrigTx>_M<Tracking>-<ts>-1of1_<FileType>.x12- Example:
TP11525703-837P_M019048402-20260520231513488-1of1_999.x12 FileTypeis one of:999,TA1,270,271,276,277,277CA,278,820,834,835,ENCR- 277CA is distinguished by
ST*277CAcontent (filename uses277)
- Example:
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)
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
# 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 TPID11525703. The legacy trading-partner IDSKCO0is no longer accepted as the payer identifier.
835 (remittance)
BPR10 = "81-1725341"— TXIX (Medicaid) accountBPR10 = "84-0644739"— BHA (behavioral health) accountN1*PR N104 = "7912900843"— payer IDN1*PR N102is 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
# 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
# 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:
- Decrypt with old key, dump to SQL
- Re-encrypt with new key, import SQL
- 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:
curl 'http://localhost:8000/api/admin/audit-log?entity_type=claim&entity_id=C-123'
To verify the chain (run nightly or on demand):
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:
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 and the
cyclone.parsers.validator / cyclone.parsers.validator_835 modules.
Adding a new payer
The pattern is:
- Add a
PayerConfig(837P) andPayerConfig835(835) factory incyclone/parsers/payer.py— usually thin wrappers that override only the differing fields (allowed codes, payer IDs). - Register the factory in the
PAYER_FACTORIES/PAYER_FACTORIES_835dicts incyclone/api.pyandcyclone/cli.py. - 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. - Update this note + the 837p/835 notes with the new payer's specifics.