Files
cyclone/docs/reference/co-medicaid.md
T
Tyler 2c0afbe9c5 feat(sp10): 277CA parser + Payer-Rejected Inbox lane
- Add cyclone.parsers.models_277ca + parse_277ca (X12 005010X214)
  - Per-Patient HL ClaimStatus with REF*1K (PCN), REF*EJ (tax ID),
    STC category code, amount, service date
  - STC classifier: A1-A3 accepted, A4/A6/A7 rejected, A8/A9 pended,
    P1-P5 paid, anything else unknown
  - Multiple STCs per Patient HL: last wins (canonical pattern for
    'first pended, then paid' acknowledgments)
  - Subscriber-level STCs surface in unscoped_statuses
- Add apply_277ca_rejections — stamps Claim.payer_rejected_* fields on
  matching rows (lookup by patient_control_number, mirrors 999 path)
- New /api/parse-277ca, /api/277ca-acks, /api/277ca-acks/{id} endpoints
- New two77ca_acks table + Two77caAck ORM model
- New Claim columns: payer_rejected_at, _reason, _status_code, _by_277ca_id
- New payer_rejected lane in /api/inbox/lanes (distinct from 999
  envelope rejected lane)
- New PayerConfig277CA block in config/payers.yaml + Pydantic model
- Migration 0008 bumps user_version to 8

Tests: 654 -> 688 (parser 22 + apply 6 + API 8 + config 6 + adjustments).
All 688 backend tests pass; 1 pre-existing skipped test class unaffected.
2026-06-20 23:41:01 -06:00

6.9 KiB
Raw Blame History

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)

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/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 0199 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.

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)
P1P5 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:

  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.