From 0c81968d0ccf3d397a0385cab36a4d8bfb387272 Mon Sep 17 00:00:00 2001 From: Nora Date: Mon, 22 Jun 2026 17:54:39 -0600 Subject: [PATCH] fix(permissions+seed): add /api/reconciliation routes and shape billing_provider.address as dict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs surfaced as 403/500 storms in the live UI: 1. PERMISSIONS matrix only had '/api/reconcile' as a prefix but the client hits '/api/reconciliation/*' (dashboard reconciliation tab, manual match endpoint). Every call returned 403 even for admin. Add ('GET', '/api/reconciliation') and ('POST', '/api/reconciliation') alongside the existing /api/reconcile entries. 2. The seed CLI built billing_provider.address as a flat string but to_ui_claim_detail._address_to_ui expects {line1,line2,city,state,zip}, so opening any seeded claim in the detail drawer threw 'str object has no attribute get' → 500. Build the dict from the provider fields instead. --- backend/src/cyclone/auth/permissions.py | 2 ++ backend/src/cyclone/seed_cli.py | 15 +++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/src/cyclone/auth/permissions.py b/backend/src/cyclone/auth/permissions.py index 0fec752..310160a 100644 --- a/backend/src/cyclone/auth/permissions.py +++ b/backend/src/cyclone/auth/permissions.py @@ -43,6 +43,7 @@ PERMISSIONS: dict[tuple[str, str], set[Role]] = { ("GET", "/api/inbox/lanes"): ALL_ROLES, ("GET", "/api/inbox/export.csv"): ALL_ROLES, ("GET", "/api/reconcile"): ALL_ROLES, + ("GET", "/api/reconciliation"): ALL_ROLES, ("GET", "/api/audit-log"): ADMIN_ONLY, # Write endpoints (admin + user, no viewer). @@ -53,6 +54,7 @@ PERMISSIONS: dict[tuple[str, str], set[Role]] = { ("POST", "/api/inbox/rejected"): WRITE_ROLES, ("POST", "/api/inbox/payer-rejected"): WRITE_ROLES, ("POST", "/api/reconcile"): WRITE_ROLES, + ("POST", "/api/reconciliation"): WRITE_ROLES, ("POST", "/api/resubmit"): WRITE_ROLES, ("POST", "/api/acks"): WRITE_ROLES, diff --git a/backend/src/cyclone/seed_cli.py b/backend/src/cyclone/seed_cli.py index 7275a5d..99cc187 100644 --- a/backend/src/cyclone/seed_cli.py +++ b/backend/src/cyclone/seed_cli.py @@ -243,10 +243,17 @@ def _build_seed_rows( "npi": provider["npi"], "name": provider["name"], "tax_id": provider["tax_id"], - "address": provider["address"], - "city": provider["city"], - "state": provider["state"], - "zip": provider["zip"], + # 837 parser produces ``address`` as a structured dict so + # the claim-detail drawer can render line1/line2/city/state/zip. + # A flat string here crashes ``_address_to_ui`` with + # ``AttributeError: 'str' object has no attribute 'get'``. + "address": { + "line1": provider["address"], + "line2": None, + "city": provider["city"], + "state": provider["state"], + "zip": provider["zip"], + }, "phone": provider["phone"], }, "payer": {"name": payer},