Files
cyclone/src/types/index.ts
T

990 lines
29 KiB
TypeScript

// ---------------------------------------------------------------------------
// UI-shaped types (used by the existing pages; backed by in-memory sample data
// when no API is configured). Keep these stable — the rest of the app reads
// from them.
// ---------------------------------------------------------------------------
export type ClaimStatus =
| "draft"
| "submitted"
| "accepted"
| "denied"
| "paid"
| "pending";
export type RemittanceStatus = "received" | "posted" | "reconciled";
export type ActivityKind =
| "claim_submitted"
| "claim_paid"
| "claim_denied"
| "claim_accepted"
| "remit_received"
| "provider_added"
// Emitted by `cyclone.store.record_manual_match` when an operator
// pairs a remit to a claim by hand. Surface as a neutral event on
// the Activity Log; do NOT treat it as a status change.
| "manual_match";
export interface Claim {
id: string;
patientName: string;
providerNpi: string;
payerName: string;
cptCode: string;
billedAmount: number;
receivedAmount: number;
status: ClaimStatus;
submissionDate: string;
denialReason?: string;
}
export interface Provider {
npi: string;
name: string;
taxId: string;
address: string;
city: string;
state: string;
zip: string;
phone: string;
claimCount: number;
outstandingAr: number;
/**
* SP21 Task 1.6: populated by the extended `GET /api/config/providers/{npi}`
* endpoint. Top 10 claims for this provider by `submissionDate` desc.
* Optional so legacy callers (in-memory sample data) keep working
* without an API round-trip.
*/
recent_claims?: ClaimSummary[];
/**
* SP21 Task 1.6: populated by the extended `GET /api/config/providers/{npi}`
* endpoint. Top 10 activity events (by `ts` desc) joined to claims
* owned by this provider via `claim_id`.
*/
recent_activity?: ActivityEvent[];
}
/**
* SP21 Task 1.6: slim claim projection returned by
* `GET /api/config/providers/{npi}.recent_claims`. Mirrors the wire
* shape of `store.iter_claims(provider_npi=...)` 1:1 (camelCase,
* superset of the legacy `Claim` interface).
*/
export interface ClaimSummary {
id: string;
state: string;
billedAmount: number;
patientName: string;
providerNpi: string;
payerName: string;
cptCode: string;
submissionDate: string;
parsedAt: string;
status: string;
matchedRemittanceId?: string | null;
batchId: string;
receivedAmount?: number;
denialReason?: string | null;
}
/**
* SP21 Task 1.6: one row in `recent_activity`. Mirrors the Python
* ORM `ActivityEvent` (snake_case fields rewritten to camelCase for
* the wire). `payload` carries the same dict the recorder wrote at
* the time of the event (message, npi, amount, etc.).
*/
export interface ActivityEvent {
id: number;
ts: string; // ISO Z
kind: string;
batchId: string | null;
claimId: string | null;
remittanceId: string | null;
payload: Record<string, unknown>;
}
export interface Remittance {
id: string;
claimId: string;
payerName: string;
paidAmount: number;
adjustmentAmount: number;
receivedDate: string;
checkNumber: string;
status: RemittanceStatus;
denialReason?: string | null;
validationWarnings?: string[];
/**
* Persisted CAS adjustment rows, labeled by the backend via the bundled
* CARC dictionary. Optional — the list endpoint omits it to keep the
* payload small; the detail endpoint (`/api/remittances/{id}`) populates it.
*/
adjustments?: CasAdjustment[];
/**
* SP7: per-line 835 SVC composites on this remittance. Ordered by
* ``lineNumber``. Each SLP carries the per-line CAS adjustments under
* ``serviceLinePayments[i].adjustments`` via the line-reconciliation
* endpoint, or via a follow-up CAS query in the remit drawer.
*/
serviceLinePayments?: ServiceLinePayment[];
/**
* SP7: CLP-level (claim-level) CAS bucket — adjustments that were
* not attached to any SVC composite. Powers the ClaimLevelAdjustments
* panel in the remit drawer.
*/
claimLevelAdjustments?: RemittanceClaimLevelAdjustment[];
}
/**
* One Claim Adjustment Segment (CAS) row, labeled.
* Source: backend `cyclone.parsers.cas_codes.reason_label(group, reason)`.
*/
export interface CasAdjustment {
group: string; // "CO", "PR", "OA", "PI", "CR"
reason: string; // "45", "1", etc.
label: string; // "Charge exceeds fee schedule"
amount: number;
quantity: number | null;
}
export interface Activity {
id: string;
kind: ActivityKind;
message: string;
timestamp: string;
npi?: string;
amount?: number;
/**
* SP21 Task 2.5: read from the backend `ActivityEvent.claim_id` so
* the Dashboard "Recent activity" card can route a click on a
* `claim_*` event to the matching claim drawer (via
* `src/lib/event-routing.ts`). Populated only for events that
* correspond to a specific claim; `null` for orphan events such as
* `remit_received` or `provider_added`.
*/
claimId?: string | null;
/**
* SP21 Task 2.5: read from the backend `ActivityEvent.remittance_id`.
* Populated only for events that reference a specific remittance
* (e.g. `remit_received`); `null` otherwise. The Dashboard toast
* covers the Phase 2 case where this routes to a not-yet-built
* `RemitDrawer` (Phase 4).
*/
remittanceId?: string | null;
}
// ---------------------------------------------------------------------------
// Backend-aligned types (snake_case to match FastAPI JSON output 1:1).
// These mirror the Pydantic models in
// backend/src/cyclone/parsers/models.py (837P)
// backend/src/cyclone/parsers/models_835.py (835 ERA)
// Numeric fields are strings (Decimals serialized); dates are ISO strings.
// ---------------------------------------------------------------------------
// --- 837P ------------------------------------------------------------------
export interface Address {
line1: string;
line2?: string | null;
city: string;
state: string;
zip: string;
}
export interface BillingProvider {
name: string;
npi: string;
tax_id?: string | null;
address?: Address | null;
}
export interface Subscriber {
first_name: string;
last_name: string;
member_id: string;
dob?: string | null;
gender?: "M" | "F" | "U" | null;
address?: Address | null;
}
export interface Payer {
name: string;
id: string;
}
export interface ClaimHeader {
claim_id: string;
total_charge: string; // Decimal serialized
place_of_service?: string | null;
facility_code_qualifier?: string | null;
frequency_code?: string | null;
provider_signature?: string | null;
assignment?: string | null;
release_of_info?: string | null;
prior_auth?: string | null;
}
export interface Diagnosis {
code: string;
qualifier?: string | null;
}
export interface Procedure {
qualifier: string;
code: string;
modifiers: string[];
}
export interface ServiceLine {
line_number: number;
procedure: Procedure;
charge: string; // Decimal serialized
unit_type?: string | null;
units?: string | null; // Decimal serialized
place_of_service?: string | null;
service_date?: string | null;
provider_reference?: string | null;
}
export interface ValidationIssue {
rule: string;
severity: "error" | "warning";
message: string;
segment_index?: number | null;
}
export interface ValidationReport {
passed: boolean;
errors: ValidationIssue[];
warnings: ValidationIssue[];
}
export interface Envelope {
sender_id: string;
receiver_id: string;
control_number: string;
transaction_date: string; // ISO date
transaction_time?: string | null;
implementation_guide?: string | null;
}
export interface BatchSummary {
input_file: string;
control_number?: string | null;
transaction_date?: string | null;
total_claims: number;
passed: number;
failed: number;
failed_claim_ids: string[];
issues_by_rule: Record<string, number>;
output_dir?: string | null;
}
export interface ClaimOutput {
claim_id: string;
control_number: string;
transaction_date: string; // ISO date
billing_provider: BillingProvider;
subscriber: Subscriber;
payer: Payer;
claim: ClaimHeader;
diagnoses: Diagnosis[];
service_lines: ServiceLine[];
validation: ValidationReport;
raw_segments: string[][];
}
export interface ParseResult837 {
envelope: Envelope | null;
claims: ClaimOutput[];
summary: BatchSummary;
/**
* Server-side batch id (UUID). Present on the JSON response from
* POST /api/parse-837 — null/missing on the NDJSON-streaming path
* (use the final `summary.batch_id` from the NDJSON stream instead).
* Required by /api/batches/{batch_id}/export-837.
*/
batch_id?: string | null;
}
// --- 835 ERA ---------------------------------------------------------------
export interface FinancialInfo {
handling_code: string;
paid_amount: string; // Decimal serialized
credit_debit_flag: string;
payment_method?: string | null;
payer_tax_id?: string | null;
payment_date?: string | null; // ISO date
}
export interface ReassociationTrace {
trace_type_code: string;
trace_number: string;
originating_company_id: string;
payer_id?: string | null;
}
export interface Payer835 {
name: string;
id?: string | null;
address?: Address | null;
contact_url?: string | null;
}
export interface Payee835 {
name: string;
npi: string;
address?: Address | null;
}
export interface ClaimAdjustment {
group_code: string;
reason_code: string;
amount: string; // Decimal serialized
quantity?: string | null;
}
export interface ServicePayment {
line_number: number;
procedure_qualifier: string;
procedure_code: string;
modifiers: string[];
charge: string;
payment: string;
units?: string | null;
unit_type?: string | null;
service_date?: string | null;
adjustments: ClaimAdjustment[];
ref_benefit_plan?: string | null;
}
export interface ClaimPayment {
payer_claim_control_number: string;
status_code: string;
status_label?: string | null;
total_charge: string;
total_paid: string;
patient_responsibility?: string | null;
claim_filing_indicator?: string | null;
original_claim_id?: string | null;
facility_type?: string | null;
frequency_code?: string | null;
per_diem_covered_days?: string | null;
ref_benefit_plan?: string | null;
service_payments: ServicePayment[];
raw_segments: string[][];
}
export interface ParseResult835 {
envelope: Envelope;
financial_info: FinancialInfo;
trace: ReassociationTrace;
payer: Payer835;
payee: Payee835;
claims: ClaimPayment[];
summary: BatchSummary;
validation?: ValidationReport | null;
/** See ParseResult837.batch_id. */
batch_id?: string | null;
}
// --- NDJSON streaming ------------------------------------------------------
// The backend emits one JSON object per line during an NDJSON stream:
// { "type": "<event>", "data": <object> }
// See backend/src/cyclone/api.py:_ndjson_stream* for the source of truth.
export type NdjsonEventType =
| "envelope"
| "claim"
| "claim_payment"
| "financial_info"
| "trace"
| "payer"
| "payee"
| "summary";
export interface NdjsonEvent<T = unknown> {
type: NdjsonEventType;
data: T;
}
// --- Parsed-batch slice (used by the Upload page + store) ------------------
export type ParsedBatchKind = "837p" | "835";
export interface ParsedBatch {
id: string;
kind: ParsedBatchKind;
inputFilename: string;
parsedAt: string; // ISO timestamp
claimCount: number;
passed: number;
failed: number;
claimIds: string[];
summary: BatchSummary;
}
// ---------------------------------------------------------------------------
// Reconciliation (7-state claim model + match audit trail)
// Mirrors cyclone.db.ClaimState (Python enum) 1:1. The reconciliation page
// uses these to drive the unmatched bucket and the manual-match UI.
// ---------------------------------------------------------------------------
export type ClaimState =
| "submitted"
| "received"
| "paid"
| "partial"
| "denied"
| "reconciled"
| "reversed";
export const CLAIM_STATES: ClaimState[] = [
"submitted",
"received",
"paid",
"partial",
"denied",
"reconciled",
"reversed",
];
export interface Match {
id: number;
claim_id: string;
remittance_id: string;
strategy: "pcn-exact" | "score-auto" | "manual";
matched_at: string;
prior_claim_state?: ClaimState | null;
is_reversal: boolean;
}
// Mirrors `cyclone.store.to_ui_claim_from_orm` (backend/src/cyclone/store.py).
// Fields the backend does not currently emit are optional so the type
// matches the actual wire shape (was previously assumed but never true).
export interface UnmatchedClaim {
id: string;
patientName: string;
billedAmount: number;
providerNpi: string | null;
payerName?: string;
payerId?: string | null;
serviceDate?: string | null;
cptCode?: string;
submissionDate?: string;
state: ClaimState;
}
// Mirrors `cyclone.store.list_unmatched` (backend/src/cyclone/store.py) for
// the remittances payload. `status` is the 3-state remittance lifecycle
// ("received" | "posted" | "reconciled"); the previous `statusCode` field
// was a confusion with the 835 claim-status code and never existed on the
// wire. Fields the backend omits are optional.
export interface UnmatchedRemittance {
id: string;
payerClaimControlNumber: string;
payerName?: string;
status: string;
paidAmount: number;
adjustmentAmount: number;
batchId: string;
receivedDate?: string;
parsedAt?: string;
claimId?: string;
denialReason?: string | null;
validationWarnings?: string[];
isReversal?: boolean;
totalCharge?: number;
serviceDate?: string | null;
}
export interface UnmatchedResponse {
claims: UnmatchedClaim[];
remittances: UnmatchedRemittance[];
}
export interface MatchResponse {
claim: UnmatchedClaim;
match: { id: number; strategy: "pcn-exact" | "score-auto" | "manual" };
}
// ---------------------------------------------------------------------------
// 999 ACKs (SP3 P3 T16)
// Mirrors the `acks` table (cyclone.db.Ack) and the `/api/acks` response.
// ---------------------------------------------------------------------------
/**
* One persisted 999 ACK row, camelCased for the UI. The backend
* (snake_case) is re-shaped in `src/hooks/useAcks.ts` via the
* `mapAck` helper, so this interface matches what the page actually
* consumes.
*/
export interface Ack {
id: number;
sourceBatchId: string;
acceptedCount: number;
rejectedCount: number;
receivedCount: number;
ackCode: "A" | "E" | "R" | "P";
parsedAt: string;
/**
* AK2 set_control_number from the inbound 999 — the
* patient_control_number of the original claim batch this 999
* acks. Surfaced in the list endpoint so the operator can
* correlate a 999 to a claim batch in the UI without a second
* round-trip to the detail endpoint.
*/
patientControlNumber?: string | null;
/**
* SP28: distinct claim ids this 999 ack is linked to. Populated by
* the backend list endpoint so the Acks page can show a "Claims"
* badge column without a per-row detail round-trip. Empty array
* means no auto-link resolved (orphan).
*/
linkedClaimIds?: string[];
}
// ---------------------------------------------------------------------------
// TA1 (Interchange Acknowledgment) — mirrors the `ta1_acks` table
// and the `/api/ta1-acks` response. A TA1 is the lowest-level X12
// envelope ack: one per inbound ISA/IEA interchange, separate from
// the per-batch 999. Colorado Medicaid currently doesn't ship TA1s
// in the FromHPE path (the inbound MFT only carries 999s) but
// Gainwell has shipped them historically, so the UI shows them
// whenever one is on file.
// ---------------------------------------------------------------------------
/**
* One persisted TA1 ACK row, camelCased for the UI. Re-shaped in
* `src/lib/api.ts` (`mapTa1Ack`) from the snake_case backend payload.
*
* ``ackCode`` is the interchange ack code: ``A`` = accepted,
* ``E`` = accepted with envelope errors, ``R`` = rejected.
* ``noteCode`` is an optional 3-digit X12 note code (e.g. ``000`` = no
* error, ``001`` = unsupported interchange version, etc.).
*/
export interface Ta1Ack {
id: number;
controlNumber: string;
ackCode: "A" | "E" | "R";
noteCode: string | null;
interchangeDate: string | null;
interchangeTime: string | null;
senderId: string | null;
receiverId: string | null;
sourceBatchId: string;
parsedAt: string | null;
/**
* SP28: originating `Batch` ids this TA1 envelope is linked to
* (per D4, TA1 is envelope-level so it links to `Batch` rather
* than `Claim`). Empty array means no auto-link resolved.
*/
linkedClaimIds?: string[];
}
// ---------------------------------------------------------------------------
// SP28: claim↔ack link rows.
// Mirrors the `claim_acks` table (cyclone.db.ClaimAck) and the wire shape
// of `GET /api/claims/{id}/acks` + `GET /api/acks/{kind}/{id}/claims`.
// Per-AK2 granularity (D1): a single 999 with two AK2 set-responses
// produces two ClaimAck rows. TA1 batch-level rows have `claimId === null`
// — the `ClaimDrawer` panel filters those out; the `AckDrawer` panel
// renders them with the originating Batch instead of a claim.
// ---------------------------------------------------------------------------
/**
* Discriminator for the ack flavor that produced a `ClaimAck` row.
*
* - `"999"` — per-AK2 set-response (one row per AK2 in the inbound 999).
* - `"277ca"` — per-`ClaimStatus` (one row per `STC` in the inbound 277CA).
* - `"ta1"` — envelope-level (one row per TA1, no per-claim granularity).
*/
export type ClaimAckKind = "999" | "277ca" | "ta1";
/**
* One persisted claim↔ack link row, camelCased for the UI. The
* `claimId` is `null` for TA1 batch-level rows (D4) — the
* `ClaimDrawer` panel filters those out so the operator only sees
* per-claim acks; the `AckDrawer` panel renders them with the
* originating Batch instead.
*
* `claimState` is a derived join (the 999/277CA row carries the
* current `Claim.state` so the drawer can render the
* `ClaimStateBadge` inline without a second round-trip). For
* TA1 batch-level rows with `claimId === null`, this is `"n/a"`.
*
* `ak2Index` is populated only for `"999"` rows — 277CA and TA1
* don't have AK2 segments. `setControlNumber` is populated for
* 999/277CA rows (the value the upstream ack actually carried,
* regardless of which join path resolved the link — spec D10).
*/
export interface ClaimAck {
id: number;
claimId: string | null;
batchId: string | null;
ackId: number;
ackKind: ClaimAckKind;
ak2Index: number | null;
setControlNumber: string | null;
setAcceptRejectCode: string | null;
linkedAt: string;
linkedBy: "auto" | "manual";
claimState?: string;
}
// ---------------------------------------------------------------------------
// SP4 claim detail drawer types (Task 4).
// Mirrors the JSON shape of `GET /api/claims/{claim_id}` 1:1 — camelCase
// keys, numeric money fields (coerced from Decimal on the backend), ISO
// timestamps with a trailing `Z`. Distinct from the parser-aligned
// snake_case types above (e.g. ``BillingProvider``, ``ServiceLine``) which
// describe the EDI parse tree; these describe the UI-ready detail payload.
// The ``ClaimDetail*`` prefix keeps the two flavors side-by-side without
// a TypeScript duplicate-identifier collision.
// ---------------------------------------------------------------------------
export interface ClaimDetailDiagnosis {
code: string;
qualifier: string | null;
}
export interface ClaimDetailServiceLine {
lineNumber: number;
procedureQualifier: string;
procedureCode: string;
modifiers: string[];
charge: number;
units: number | null;
unitType: string | null;
serviceDate: string | null; // ISO date YYYY-MM-DD or null
}
/**
* The backend can return ``{}`` for a missing address (see
* ``store._address_to_ui``); callers that want a populated address must
* branch on ``Object.keys(addr).length === 0`` before reading fields.
*/
export interface ClaimDetailAddress {
line1: string;
line2: string | null;
city: string;
state: string;
zip: string;
}
export interface ClaimDetailBillingProvider {
name: string;
npi: string;
taxId: string;
address: ClaimDetailAddress | Record<string, never>;
}
export interface ClaimDetailSubscriber {
firstName: string;
lastName: string;
memberId: string;
dob: string | null;
gender: string | null;
}
export interface ClaimDetailPayer {
name: string;
id: string;
}
export interface ClaimDetailParties {
billingProvider: ClaimDetailBillingProvider;
subscriber: ClaimDetailSubscriber;
payer: ClaimDetailPayer;
}
export interface ClaimDetailValidationIssue {
rule: string;
severity: "error" | "warning";
message: string;
}
export interface ClaimDetailValidation {
passed: boolean;
errors: ClaimDetailValidationIssue[];
warnings: ClaimDetailValidationIssue[];
}
export interface ClaimDetailMatchedRemittance {
id: string;
totalPaid: number;
/** Mirrors the same `"received" | "reconciled"` mapping as the list endpoint. */
status: string;
receivedAt: string; // ISO Z
/**
* SP7: per-line counts for the badge. Optional for backward
* compatibility — older claim-detail responses (pre-SP7) won't have
* these fields. The card omits the badge when undefined.
*/
matchedLines?: number;
totalLines?: number;
}
export interface ClaimDetailStateHistoryEvent {
kind: string;
ts: string; // ISO Z (most-recent-first, capped at 50 by the backend)
batchId: string | null;
remittanceId: string | null;
}
/**
* One claim with the full drawer context: header, state, parties,
* validation, service lines, diagnoses, raw segments, recent
* ``stateHistory`` (most-recent-first, capped at 50), and a populated
* ``matchedRemittance`` block when the claim has been paired with an
* ERA. Mirrors ``CycloneStore.get_claim_detail`` (backend/src/cyclone/store.py)
* and the ``GET /api/claims/{claim_id}`` endpoint contract.
*/
export interface ClaimDetail {
id: string;
batchId: string;
state: string;
stateLabel: string;
billedAmount: number;
patientName: string;
providerNpi: string;
providerName: string;
payerName: string;
payerId: string;
submissionDate: string; // ISO Z
serviceDateFrom: string | null;
serviceDateTo: string | null;
parsedAt: string; // ISO Z
diagnoses: ClaimDetailDiagnosis[];
serviceLines: ClaimDetailServiceLine[];
parties: ClaimDetailParties;
validation: ClaimDetailValidation;
rawSegments: string[][];
matchedRemittance: ClaimDetailMatchedRemittance | null;
stateHistory: ClaimDetailStateHistoryEvent[];
/**
* SP7: per-line reconciliation slim projection. Parallel to
* ``serviceLines`` and indexed by ``lineNumber``. Populated by the
* claim-detail endpoint so the ServiceLinesTable can render Paid +
* Adjustments columns without a second fetch. ``paid`` /
* ``adjustmentsSum`` are null when the line has no matched 835 SVC
* composite.
*/
lineReconciliation?: ClaimDetailLineReconciliation[];
/**
* SP28: compact form of the claim's `claim_acks` rows. Each entry
* has `ack_id`, `ack_kind`, `set_accept_reject_code`, `parsed_at`
* (no `claim_state` join — the drawer can use this for the
* initial panel render and rely on the live-tail for the full
* per-row payload via `useClaimAcks`). Empty array when the claim
* has no acks.
*/
ackLinks?: ClaimAck[];
}
// ---------------------------------------------------------------------------
// Side-by-side batch diff (SP3 P4 / T18)
// Mirrors `GET /api/batch-diff?a=<batch_id>&b=<batch_id>` (see
// `backend/src/cyclone/batch_diff.py` and `backend/src/cyclone/api.py`).
// The contract is deliberately small: no raw segments, no service-line
// details — just the summary fields the operator needs to spot the
// deltas between two parsed files at a glance.
// ---------------------------------------------------------------------------
export type BatchKind = "837p" | "835";
export interface BatchDiffSideMeta {
id: string;
kind: BatchKind;
parsedAt: string; // ISO Z
inputFilename: string;
claimCount: number;
}
/**
* One claim (or remittance) row projected for the diff view. Carries
* just enough context to render side-by-side without the EDI parse
* tree. ``status`` carries the validation-derived label for 837P
* (``submitted`` / ``pending`` / ``draft`` / ``denied``) and the CLP02
* code/label for 835.
*/
export interface BatchClaimDiffSummary {
claim_id: string;
patient_name: string;
total_charge: number;
service_date: string | null;
status: string;
cpt_codes: string[];
}
/**
* One changed field between the two sides of a claim. ``field``
* identifies which column changed (``status`` / ``total_charge`` /
* ``service_date`` / ``cpt_codes``); ``a`` and ``b`` are the values
* from each side, rendered verbatim.
*/
export interface BatchDiffFieldDiff {
field: "status" | "total_charge" | "service_date" | "cpt_codes";
a: string | number | string[] | null;
b: string | number | string[] | null;
}
export interface BatchDiffChangedRow {
a: BatchClaimDiffSummary;
b: BatchClaimDiffSummary;
diff: BatchDiffFieldDiff[];
}
export interface BatchDiffSummary {
addedCount: number;
removedCount: number;
changedCount: number;
unchangedCount: number;
}
/**
* Full response from `GET /api/batch-diff`. The three bucket arrays
* (``added`` / ``removed`` / ``changed``) are always present (never
* absent) so the UI can index unconditionally; ``summary`` is
* precomputed by the backend so the four count cards don't have to
* derive them on the client.
*/
export interface BatchDiff {
a: BatchDiffSideMeta;
b: BatchDiffSideMeta;
added: BatchClaimDiffSummary[];
removed: BatchClaimDiffSummary[];
changed: BatchDiffChangedRow[];
summary: BatchDiffSummary;
}
// ---------------------------------------------------------------------------
// SP7 — Per-Service-Line Adjustment Audit types.
// See docs/superpowers/specs/2026-06-20-cyclone-line-reconciliation-design.md
// ---------------------------------------------------------------------------
/** One 835 SVC composite (Loop 2110) projected to wire shape. */
export interface ServiceLinePayment {
id: number;
lineNumber: number;
procedureQualifier: string;
procedureCode: string;
modifiers: string[];
charge: string;
payment: string;
units: string | null;
unitType: string | null;
serviceDate: string | null;
}
/** Status of one line on either side of a reconciliation. */
export type LineReconciliationStatus =
| "matched"
| "unmatched_835_only"
| "unmatched_837_only"
| "superseded";
/** Slim per-line projection embedded in `GET /api/claims/{id}`. */
export interface ClaimDetailLineReconciliation {
lineNumber: number;
status: LineReconciliationStatus;
paid: string | null;
adjustmentsSum: string | null;
}
/** Summary block at the top of the line-reconciliation response. */
export interface LineReconciliationSummary {
billedTotal: string;
paidTotal: string;
adjustmentTotal: string;
matchedLines: number;
totalLines: number;
}
/** One line in the line-reconciliation response. */
export interface LineReconciliationRow {
claimServiceLine: {
lineNumber: number;
procedureQualifier: string;
procedureCode: string;
modifiers: string[];
charge: string;
units: string | null;
unitType: string | null;
serviceDate: string | null;
} | null;
serviceLinePayment: ServiceLinePayment | null;
status: LineReconciliationStatus;
adjustments: Array<{
groupCode: string;
reasonCode: string;
amount: string;
}>;
}
/** Top-level shape of `GET /api/claims/{id}/line-reconciliation`. */
export interface LineReconciliationResponse {
claimId: string;
summary: LineReconciliationSummary;
lines: LineReconciliationRow[];
}
/** One CLP-level (claim-level) CAS adjustment on a remittance. */
export interface RemittanceClaimLevelAdjustment {
id: number;
groupCode: string;
reasonCode: string;
amount: string;
quantity: string | null;
}
/** Counts embedded in inbox-lane rows for the `MatchedRemitCard` badge. */
export interface MatchedRemittanceLineCounts {
matchedLines: number;
totalLines: number;
}
/** Inbox-lane claim row carrying the matched-remittance block. */
export interface InboxClaimRow {
id: string;
kind: "claim" | "remit";
patientControlNumber: string;
chargeAmount: number | null;
payerId: string | null;
providerNpi: string | null;
state: string;
rejectionReason: string | null;
rejectedAt: string | null;
serviceDateFrom: string | null;
score: number | null;
scoreTier: string | null;
scoreBreakdown: {
patient: number;
date: number;
amount: number;
provider: number;
} | null;
matchedRemittance: {
id: string;
matchedLines: number;
totalLines: number;
} | null;
}
// ---------------------------------------------------------------------------
// Auth (admin/user/viewer). Mirrors `users.to_public()` on the backend
// (backend/src/cyclone/auth/users.py). Role is the triple ("admin" |
// "user" | "viewer"); `disabledAt` is non-null when the account has been
// disabled by an admin. `createdAt` mirrors the original DB column.
// ---------------------------------------------------------------------------
export interface User {
id: number;
username: string;
role: "admin" | "user" | "viewer";
createdAt: string | null;
disabledAt?: string | null;
}