Add Domains and Power rails views for functional groups.

Sidebar tabs show routing-first topology from placement_plan/functional_groups, with IC groups and satellite roles highlighted per domain and per supply rail.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-13 17:17:16 +02:00
co-authored by Cursor
parent e124ac2de7
commit fc11df59fc
10 changed files with 553 additions and 48 deletions
+5 -3
View File
@@ -1551,9 +1551,11 @@ def _write_functional_groups(ws: PipelineWorkspace, graph) -> None:
if extracted_dir.is_dir():
cmap = _build_constraints_map(_load_datasheets(extracted_dir))
report = build_functional_groups(graph, cmap)
out = ws.local_path("functional_groups.json")
out.write_text(report.model_dump_json(indent=2) + "\n")
ws._upload_file("functional_groups.json")
payload = report.model_dump_json(indent=2) + "\n"
for name in ("functional_groups.json", "placement_plan.json"):
out = ws.local_path(name)
out.write_text(payload)
ws._upload_file(name)
except Exception:
logger.exception("functional_groups.json write failed — continuing")
+71
View File
@@ -326,6 +326,77 @@ def heal_if_pipeline_finished(
return None
def heal_if_placement_stuck(
storage: StorageBackend, user_id: str, project_id: str,
) -> ProjectMeta | None:
"""Unstick placement_status queued/running when the worker is gone.
- Last event ``placement_complete`` → ``complete``
- Dead worker + plan artifact present → ``complete``
- Dead worker otherwise → ``error``
"""
meta = get_project(storage, user_id, project_id)
if meta is None:
return None
pst = meta.placement_status or "draft"
if pst not in ("queued", "running"):
return None
prefix = _project_prefix(user_id, project_id)
events_prefix = f"{prefix}/events/"
last_event = None
try:
keys = sorted(
k for k in storage.list_prefix(events_prefix)
if k.endswith(".json") and "/events/" in k
)
if keys:
last_event = storage.read_json(keys[-1])
except Exception:
last_event = None
if (last_event or {}).get("event") == "placement_complete":
data = (last_event or {}).get("data") or {}
return update_project(
storage, user_id, project_id,
placement_status="complete",
placement_cancel_requested=False,
placement_state={
"domains": data.get("domains"),
"groups": data.get("groups"),
},
)
from backend.services import job_runner
exec_name = meta.placement_execution_name or f"local/placement/{project_id}"
try:
state = job_runner.get_execution_state(exec_name)
except Exception:
state = "unknown"
if state in ("pending", "running"):
return None
has_plan = (
storage.exists(f"{prefix}/placement_plan.json")
or storage.exists(f"{prefix}/functional_groups.json")
)
if has_plan:
return update_project(
storage, user_id, project_id,
placement_status="complete",
placement_cancel_requested=False,
placement_state=meta.placement_state,
)
return update_project(
storage, user_id, project_id,
placement_status="error",
placement_cancel_requested=False,
placement_state={"error": f"Placement worker terminated ({state})"},
)
# --- CRUD ---