feat(sp36): absorb 277ca-acks endpoints into api_routers/acks.py
Moves GET /api/277ca-acks and GET /api/277ca-acks/{ack_id}
out of api.py (formerly lines 1278-1318) into the existing
api_routers/acks.py, which already handles 999 ACK list/detail/
stream. 277CA ACKs share the same shape-of-life contract (persisted
by a parse endpoint, listed newest-first, detail returns raw_json)
so the consolidation lands in the router that already owns the
adjacent surface — no new router file.
While here:
- collapsed the inline batch-fetch in list_277ca_acks_endpoint to
the existing _find_linked_claim_ids_for_acks(ack_ids, kind='277ca')
helper that the 999 list endpoint already uses (and ta1_acks.py
uses too). Eliminates the copy-pasted SQL; same N+1-avoidance
one-SELECT contract.
- pruned ClaimAck / to_ui_two77ca_ack imports from api.py.
Behaviour-preserving: pytest post-cut = 20 failed / 1253 passed /
10 skipped = baseline match. pytest -k '277ca or ack' = 235 passed,
1 skipped.
This commit is contained in:
@@ -38,7 +38,7 @@ from pydantic import ValidationError
|
||||
from cyclone import __version__, db
|
||||
from cyclone.auth.deps import matrix_gate
|
||||
from cyclone.clearhouse import InboundFile
|
||||
from cyclone.db import Batch, Claim, ClaimAck, ClaimState, Remittance
|
||||
from cyclone.db import Batch, Claim, ClaimState, Remittance
|
||||
from sqlalchemy import desc, or_
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from cyclone.inbox_state import apply_999_rejections
|
||||
@@ -96,7 +96,6 @@ from cyclone.store import (
|
||||
InvalidStateError,
|
||||
dashboard_kpis,
|
||||
store,
|
||||
to_ui_two77ca_ack,
|
||||
utcnow,
|
||||
)
|
||||
|
||||
@@ -1275,49 +1274,6 @@ async def parse_277ca_endpoint(
|
||||
})
|
||||
|
||||
|
||||
@app.get("/api/277ca-acks", dependencies=[Depends(matrix_gate)])
|
||||
def list_277ca_acks_endpoint(
|
||||
limit: int = Query(100, ge=1, le=5000),
|
||||
) -> Any:
|
||||
"""Return the list of persisted 277CA ACKs, newest first.
|
||||
|
||||
SP28: each item gains ``linked_claim_ids`` (batch-fetched in
|
||||
one query to avoid N+1) so the Acks page row can render the
|
||||
"🔗 N claims" badge inline.
|
||||
"""
|
||||
rows = store.list_277ca_acks()
|
||||
items = [to_ui_two77ca_ack(r) for r in rows[:limit]]
|
||||
ack_ids = [r.id for r in rows]
|
||||
linked_map: dict[int, list[str]] = {aid: [] for aid in ack_ids}
|
||||
if ack_ids:
|
||||
with db.SessionLocal()() as s:
|
||||
link_rows = (
|
||||
s.query(ClaimAck.ack_id, ClaimAck.claim_id)
|
||||
.filter(
|
||||
ClaimAck.ack_kind == "277ca",
|
||||
ClaimAck.ack_id.in_(ack_ids),
|
||||
ClaimAck.claim_id.isnot(None),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for ack_id, claim_id in link_rows:
|
||||
linked_map[ack_id].append(claim_id)
|
||||
for item, aid in zip(items, ack_ids[:limit]):
|
||||
item["linked_claim_ids"] = linked_map.get(aid, [])
|
||||
return {"total": len(rows), "items": items}
|
||||
|
||||
|
||||
@app.get("/api/277ca-acks/{ack_id}", dependencies=[Depends(matrix_gate)])
|
||||
def get_277ca_ack_endpoint(ack_id: int) -> dict:
|
||||
"""Return one persisted 277CA ACK row with its parsed detail."""
|
||||
row = store.get_277ca_ack(ack_id)
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail=f"277CA ACK {ack_id} not found")
|
||||
body = to_ui_two77ca_ack(row)
|
||||
body["raw_json"] = row.raw_json
|
||||
return body
|
||||
|
||||
|
||||
def _serialize_ta1(result) -> str:
|
||||
"""Render a TA1 file from a ParseResultTa1 for the ``raw_ta1_text`` field.
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
"""``/api/acks`` — list, detail, and live-tail stream for 999 ACKs.
|
||||
"""``/api/acks`` and ``/api/277ca-acks`` — list, detail, and live-tail.
|
||||
|
||||
These are the persisted acknowledgment rows produced by
|
||||
999 ACKs are the persisted acknowledgment rows produced by
|
||||
``POST /api/parse-999``. The frontend ``useAcks`` hook re-shapes the
|
||||
list payload to its ``Ack`` interface in ``src/types/index.ts``.
|
||||
|
||||
The detail endpoint returns the full ``raw_json`` payload plus the
|
||||
regenerated ``raw_999_text`` so the UI can show "view source" without a
|
||||
second round-trip.
|
||||
277CA ACKs are the persisted Claim Acknowledgment rows produced by
|
||||
``POST /api/parse-277ca``. The frontend ``useAcks`` hook treats them
|
||||
as a second source alongside 999 — the row shape is normalised in
|
||||
``cyclone.store.ui.to_ui_two77ca_ack``.
|
||||
|
||||
The 999 detail endpoint returns the full ``raw_json`` payload plus
|
||||
the regenerated ``raw_999_text`` so the UI can show "view source"
|
||||
without a second round-trip.
|
||||
|
||||
SP25: ``/api/acks/stream`` joins the live-tail triplet — the Acks
|
||||
page mounts ``useTailStream("acks")`` and ``useMergedTail("acks", …)``
|
||||
@@ -32,7 +37,7 @@ from cyclone.auth.deps import matrix_gate
|
||||
from cyclone.parsers.models_999 import ParseResult999
|
||||
from cyclone.parsers.serialize_999 import serialize_999
|
||||
from cyclone.pubsub import EventBus
|
||||
from cyclone.store import store, to_ui_ack
|
||||
from cyclone.store import store, to_ui_ack, to_ui_two77ca_ack
|
||||
|
||||
router = APIRouter(dependencies=[Depends(matrix_gate)])
|
||||
|
||||
@@ -178,3 +183,41 @@ def get_ack_endpoint(ack_id: int) -> dict:
|
||||
else:
|
||||
body["raw_999_text"] = None
|
||||
return body
|
||||
|
||||
|
||||
# -- 277CA ACKs -------------------------------------------------------------
|
||||
# SP36 Task 2: these two endpoints moved here from api.py (lines 1278-1318).
|
||||
# 277CA ACKs share the same shape-of-life contract as 999 ACKs: persisted by
|
||||
# a parse endpoint, listed newest-first, detail returns raw_json so the UI
|
||||
# can show "view source" without a round-trip.
|
||||
|
||||
|
||||
@router.get("/api/277ca-acks")
|
||||
def list_277ca_acks_endpoint(
|
||||
limit: int = Query(100, ge=1, le=5000),
|
||||
) -> Any:
|
||||
"""Return the list of persisted 277CA ACKs, newest first.
|
||||
|
||||
SP28: each item gains ``linked_claim_ids`` (batch-fetched via
|
||||
the shared ``_find_linked_claim_ids_for_acks`` helper — one
|
||||
SELECT keyed on ``ack_kind``, no N+1) so the Acks page row
|
||||
can render the "🔗 N claims" badge inline.
|
||||
"""
|
||||
rows = store.list_277ca_acks()
|
||||
items = [to_ui_two77ca_ack(r) for r in rows[:limit]]
|
||||
ack_ids = [r.id for r in rows]
|
||||
linked_map = _find_linked_claim_ids_for_acks(ack_ids, kind="277ca")
|
||||
for item, aid in zip(items, ack_ids[:limit]):
|
||||
item["linked_claim_ids"] = linked_map.get(aid, [])
|
||||
return {"total": len(rows), "items": items}
|
||||
|
||||
|
||||
@router.get("/api/277ca-acks/{ack_id}")
|
||||
def get_277ca_ack_endpoint(ack_id: int) -> dict:
|
||||
"""Return one persisted 277CA ACK row with its parsed detail."""
|
||||
row = store.get_277ca_ack(ack_id)
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail=f"277CA ACK {ack_id} not found")
|
||||
body = to_ui_two77ca_ack(row)
|
||||
body["raw_json"] = row.raw_json
|
||||
return body
|
||||
|
||||
Reference in New Issue
Block a user