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 import __version__, db
|
||||||
from cyclone.auth.deps import matrix_gate
|
from cyclone.auth.deps import matrix_gate
|
||||||
from cyclone.clearhouse import InboundFile
|
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 import desc, or_
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from cyclone.inbox_state import apply_999_rejections
|
from cyclone.inbox_state import apply_999_rejections
|
||||||
@@ -96,7 +96,6 @@ from cyclone.store import (
|
|||||||
InvalidStateError,
|
InvalidStateError,
|
||||||
dashboard_kpis,
|
dashboard_kpis,
|
||||||
store,
|
store,
|
||||||
to_ui_two77ca_ack,
|
|
||||||
utcnow,
|
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:
|
def _serialize_ta1(result) -> str:
|
||||||
"""Render a TA1 file from a ParseResultTa1 for the ``raw_ta1_text`` field.
|
"""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
|
``POST /api/parse-999``. The frontend ``useAcks`` hook re-shapes the
|
||||||
list payload to its ``Ack`` interface in ``src/types/index.ts``.
|
list payload to its ``Ack`` interface in ``src/types/index.ts``.
|
||||||
|
|
||||||
The detail endpoint returns the full ``raw_json`` payload plus the
|
277CA ACKs are the persisted Claim Acknowledgment rows produced by
|
||||||
regenerated ``raw_999_text`` so the UI can show "view source" without a
|
``POST /api/parse-277ca``. The frontend ``useAcks`` hook treats them
|
||||||
second round-trip.
|
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
|
SP25: ``/api/acks/stream`` joins the live-tail triplet — the Acks
|
||||||
page mounts ``useTailStream("acks")`` and ``useMergedTail("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.models_999 import ParseResult999
|
||||||
from cyclone.parsers.serialize_999 import serialize_999
|
from cyclone.parsers.serialize_999 import serialize_999
|
||||||
from cyclone.pubsub import EventBus
|
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)])
|
router = APIRouter(dependencies=[Depends(matrix_gate)])
|
||||||
|
|
||||||
@@ -178,3 +183,41 @@ def get_ack_endpoint(ack_id: int) -> dict:
|
|||||||
else:
|
else:
|
||||||
body["raw_999_text"] = None
|
body["raw_999_text"] = None
|
||||||
return body
|
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