Treat finished PCB review as complete, not a raw SSE error.

The status watcher synthesized pcb_error with pcb_status=complete (terminal)
when the worker finished before pcb_complete arrived. Map that hatch to
pcb_complete, publish the event before flipping status, and send /pcb to
the layout report tree.
This commit is contained in:
2026-09-20 10:23:53 +02:00
parent 1f3876eadc
commit 187605763d
6 changed files with 136 additions and 22 deletions
+33 -5
View File
@@ -38,6 +38,34 @@ _ANALYSIS_BUSY = frozenset({
_PLACEMENT_ACTIVE = frozenset({"queued", "running"})
def pcb_sse_terminal_from_status(
pcb_status: str | None,
pcb_state: dict | None,
reason: str | None = None,
) -> tuple[str, dict]:
"""Map a terminal ``pcb_status`` to the SSE event the UI expects.
The events watcher used to emit ``pcb_error`` with
``pcb_status=complete (terminal)`` when the worker finished but the
log lagged — the progress page then showed that raw string.
"""
state = pcb_state if isinstance(pcb_state, dict) else {}
pst = pcb_status or "draft"
blob = f"{reason or ''} {state.get('error') or ''}"
if pst == "complete" or "pcb_status=complete" in blob:
return "pcb_complete", {
"findings": int(state.get("findings") or 0),
"domains": int(state.get("domains") or 0),
"groups": int(state.get("groups") or 0),
"synthetic": True,
}
if pst == "cancelled" or "pcb_status=cancelled" in blob:
return "pcb_cancelled", {"synthetic": True}
err = state.get("error") or reason or "pcb worker terminated without a terminal event"
return "pcb_error", {"error": err, "synthetic": True}
def _load_constraints_map(
extracted_dir: Path,
storage: StorageBackend | None = None,
@@ -262,6 +290,11 @@ async def run_pcb_pipeline(
ws._upload_file("periscope-findings.json")
_step(project_id, "write_report", "complete", f"{len(findings)} findings")
_publish(project_id, "pcb_complete", {
"findings": len(findings),
"domains": len(plan.domains),
"groups": len(plan.groups),
})
proj_svc.update_project(
storage, user_id, project_id,
pcb_status="complete",
@@ -273,11 +306,6 @@ async def run_pcb_pipeline(
},
pcb_cancel_requested=False,
)
_publish(project_id, "pcb_complete", {
"findings": len(findings),
"domains": len(plan.domains),
"groups": len(plan.groups),
})
except Exception as e:
logger.exception("pcb pipeline failed for %s", project_id)
proj_svc.update_project(