Commit Graph

627 Commits

Author SHA1 Message Date
Tyler 162127b494 feat(frontend): CSV export utility + ExportCsvButton component 2026-06-20 17:12:44 -06:00
Tyler 365e64e25a feat(frontend): Claims page wires live tail + TailStatusPill 2026-06-20 17:11:56 -06:00
Tyler 8523772792 style(frontend): print styles for detail drawers (Cmd-P paper records) 2026-06-20 17:09:37 -06:00
Tyler f1407dbb1d feat(frontend): TailStatusPill with reconnect button 2026-06-20 17:07:01 -06:00
Tyler e56a1eb503 feat(frontend): useMergedTail merges base + tail with filter 2026-06-20 17:05:24 -06:00
Tyler 0e766ce654 feat(frontend): useTailStream lifecycle hook with backoff + stall detection 2026-06-20 17:03:36 -06:00
Tyler 4cd52c3084 merge: ui/activity-filter — kind + since filters on ActivityLog 2026-06-20 17:00:07 -06:00
Tyler e76f821f22 merge: ui/keyboard-acks-remits — j/k row nav + ? cheatsheet on Acks and Remittances 2026-06-20 16:59:48 -06:00
Tyler d376647f5f merge: ui/batches-browser — list + detail drawer for /api/batches 2026-06-20 16:59:44 -06:00
Tyler cff4883412 merge: ui/mobile-responsive — Dashboard/Providers/Upload/Reconciliation breakpoints 2026-06-20 16:59:31 -06:00
Tyler 2fedb26c6a merge: ui/a11y-polish — focus rings, ARIA labels, skip-link 2026-06-20 16:59:25 -06:00
Tyler f4a545f379 fix(frontend): UnmatchedClaim/UnmatchedRemittance types match store.to_ui_claim_from_orm wire shape
The Reconciliation page crashed with 'c.chargeAmount.toFixed is undefined' because
the TS types declared fields the backend never emitted (chargeAmount vs billedAmount,
patientControlNumber vs patientName, statusCode vs status, totalPaid vs paidAmount).

- UnmatchedClaim: chargeAmount -> billedAmount, patientControlNumber -> patientName,
  payerId/serviceDate now optional (backend omits them)
- UnmatchedRemittance: statusCode -> status, totalPaid -> paidAmount,
  totalCharge/serviceDate/isReversal now optional
- Reconciliation page + test fixtures updated to use the wire shape
- Tests: 124/124 pass
2026-06-20 16:59:22 -06:00
Tyler ac0d9c8fb7 merge: frontend tail-stream and tail-store libs (Phase 4) 2026-06-20 16:57:38 -06:00
Tyler da29188dd0 feat(api): GET /api/{claims,remittances,activity}/stream + unsubscribe
Adds three live-tail streaming endpoints that emit an NDJSON snapshot
then forward new event-bus events as they arrive, with a 15s idle
heartbeat (overridable via CYCLONE_TAIL_HEARTBEAT_S for tests).

Each endpoint:
  1. yields a snapshot of existing rows as {"type":"item","data":<row>}
  2. terminates the snapshot with {"type":"snapshot_end","data":{"count":N}}
  3. subscribes to its event kind and forwards each new event as an
     {"type":"item","data":<event>} line
  4. emits a {"type":"heartbeat","data":{"ts":<iso>}} line every
     CYCLONE_TAIL_HEARTBEAT_S seconds when idle
  5. checks request.is_disconnected() before each yield and unsubscribes
     from the bus on cleanup so a closed stream releases its queue

The shared tail loop lives in api._tail_events, which polls
bus.subscribe_raw()'s queue directly instead of using the bus's
async-iterator wrapper — wait_for on an async generator cancels the
inner future on timeout, which poisons subsequent __anext__ calls with
StopAsyncIteration. Queue.get() is idempotent under cancellation, so
heartbeats don't break the subscription.

EventBus gains an unsubscribe(queue, kinds) method (idempotent) so
the tail loop can release its queue in a try/finally. The disconnect
test asserts the subscriber list is empty after the body iterator is
closed, validating no queue leak per open stream.

Tests in test_api_stream_live.py: 8 tests covering snapshot shape,
post-snapshot publish, heartbeat timing, multi-item snapshots, and
client disconnect cleanup. Plus 2 tests in test_pubsub.py for the
new unsubscribe method.
2026-06-20 16:57:30 -06:00
Tyler 8f419cdeba feat(frontend): j/k row nav + ? cheatsheet on Acks and Remittances 2026-06-20 16:57:22 -06:00
Tyler ff4906d0c1 feat(frontend): kind + since filters on ActivityLog page 2026-06-20 16:53:19 -06:00
Tyler 354268330c feat(frontend): Batches browser page with list + detail drawer 2026-06-20 16:52:20 -06:00
Tyler b37fb5a26c a11y(frontend): focus rings, ARIA labels, skip-link across ui components 2026-06-20 16:51:36 -06:00
Tyler 1476c90570 style(frontend): mobile responsive polish for Dashboard/Providers/Upload/Reconciliation 2026-06-20 16:51:17 -06:00
Tyler 64fb11a9cf feat(frontend): tail-store Zustand with FIFO cap 10k per slice 2026-06-20 16:05:21 -06:00
Tyler 11a4eaa480 feat(frontend): NDJSON streamTail parser for /api/{resource}/stream 2026-06-20 16:02:25 -06:00
Tyler 3ab1de23d3 refactor(api): _ndjson_line helper for streaming responses 2026-06-20 15:52:05 -06:00
Tyler 8bcb23c78d feat(api): parse endpoints pass EventBus into store writes
- api.parse_837 / parse_835: pass request.app.state.event_bus into store.add()
- conftest: autouse fixture wires a fresh EventBus onto app.state for every
  test, since TestClient does not invoke the FastAPI lifespan handler
  unless used as a context manager
- test_pubsub: split get_event_bus coverage into a raises-when-missing test
  and a returns-attached-bus test, both save/restore app.state.event_bus
  around the assertion so the autouse fixture's bus is preserved

Phase 2 complete: db init moved to lifespan, EventBus is the process-wide
publish point, and the two ingest endpoints publish claim_written /
remittance_written / activity_recorded events on every store.add().
2026-06-20 15:51:18 -06:00
Tyler 8d02ed3204 test(store): 835 batch publishes remittance_written + activity_recorded 2026-06-20 15:47:35 -06:00
Tyler ac7f3283d6 feat(store): add publishes claim_written + activity_recorded events 2026-06-20 15:42:56 -06:00
Tyler 5a712e5afd refactor(main): rely on lifespan for db init (SP5) 2026-06-20 15:37:15 -06:00
Tyler f52eec9bad feat(api): lifespan handler initializes EventBus + db 2026-06-20 15:36:50 -06:00
Tyler 25a76a515d feat(pubsub): get_event_bus() late-import accessor 2026-06-20 15:35:02 -06:00
Tyler b2f5a16541 feat(pubsub): EventBus with drop-oldest overflow, per-kind fan-out 2026-06-20 15:28:13 -06:00
Tyler e8dc8c16d7 build(deps): pytest-asyncio for SP5 stream tests 2026-06-20 15:24:48 -06:00
Tyler b980e77cf2 docs(sp5): live-tail design spec — pub/sub + long-poll NDJSON for claims/remits/activity 2026-06-20 13:47:54 -06:00
Tyler 972cc99200 docs: mark SP4 partial shipped (claim drawer); tick all plan checkboxes 2026-06-20 12:37:31 -06:00
Tyler 8dc220826c smoke: end-to-end SP4 (claim drawer) flow passes 2026-06-20 12:33:27 -06:00
Tyler d03293ed1d feat(frontend): wire KeyboardCheatsheet into Claims page via ? toggle 2026-06-20 12:23:06 -06:00
Tyler d44aa1de3f feat(frontend): KeyboardCheatsheet overlay 2026-06-20 12:18:08 -06:00
Tyler 67d5c13939 feat(frontend): Claims page wires click-to-open drawer with URL sync 2026-06-20 12:09:42 -06:00
Tyler 436cbead11 feat(frontend): ClaimDrawer root orchestrator with skeleton/error/data states 2026-06-20 12:00:27 -06:00
Tyler 7117f75764 feat(frontend): ClaimDrawer barrel export 2026-06-20 11:50:44 -06:00
Tyler 3fec07ca4d style(frontend): tidy drawer review nits
- RawSegmentsPanel: summary now reads 'Show N raw segments' for
  non-empty (with singular pluralization). The previous identical
  ternary branches read as a bug.
- PartiesGrid: replace the verbose intersection cast on AddressBlock
  with a direct ClaimDetailAddress cast (the union's other arm is
  Record<string, never>, ruled out by isEmptyAddress above).
2026-06-20 11:49:18 -06:00
Tyler dc89244100 feat(frontend): StateHistoryTimeline with kind-colored dots 2026-06-20 11:43:49 -06:00
Tyler 1671e96a10 feat(frontend): MatchedRemitCard with view-remittance link 2026-06-20 11:41:30 -06:00
Tyler 7c7e724464 feat(frontend): RawSegmentsPanel collapsible debug aid 2026-06-20 11:39:59 -06:00
Tyler 94a39e9dd6 feat(frontend): PartiesGrid with billing provider/subscriber/payer cards 2026-06-20 11:38:51 -06:00
Tyler 042597217f feat(frontend): DiagnosesList with qualifier + code formatting 2026-06-20 11:34:00 -06:00
Tyler 7f389fe04b feat(frontend): ServiceLinesTable with procedure + charge columns 2026-06-20 11:29:10 -06:00
Tyler 19b2eb7eaa fix(frontend): announce blocking validation errors via role=alert
ClaimDrawerError.tsx already wraps its error surface with role=alert
(line 36). Apply the same pattern to the ValidationPanel errors
sub-section so assistive tech announces blocking validation failures
when the drawer opens, matching the established sibling pattern.

Warnings stay decorative (no role change) — they're advisories, not
blocks.
2026-06-20 11:25:28 -06:00
Tyler 88da603c9b feat(frontend): ValidationPanel with rule-grouped errors + warnings 2026-06-20 11:21:54 -06:00
Tyler 702104c26d test(frontend): cover all 6 states in ClaimDrawerHeader badge variant matrix
The parametrized variant test only exercised 4 of the 6 mapped states
(submitted/denied/paid/pending). Add accepted (-> default) and matched
(-> success) so the entire STATE_VARIANT map is verified, not just the
branches whose tokens happened to be easy to reach.
2026-06-20 11:18:50 -06:00
Tyler 8933596c77 feat(frontend): ClaimDrawerHeader with state badge + close button 2026-06-20 11:15:49 -06:00
Tyler 67653f0da8 feat(frontend): ClaimDrawer skeleton + error states 2026-06-20 11:09:41 -06:00