Add MODE=pcb layout exam job with AI and deterministic checks.
Parallel pcb_status pipeline: parse board, classify domains/groups, inventory traces, PE-LAY/PLC/SI/DRT checks, per-IC datasheet review. Findings merge into the report UI. No auto-place or pcbnew write-back.
This commit is contained in:
@@ -26,7 +26,6 @@ from backend.periscopex.models import (
|
||||
ComponentType,
|
||||
DesignGraph,
|
||||
Finding,
|
||||
LayoutGraph,
|
||||
NetType,
|
||||
ValidationReport,
|
||||
)
|
||||
@@ -61,8 +60,6 @@ from backend.periscopex.dnp_check import check_dnp_enables
|
||||
from backend.periscopex.lifecycle import check_lifecycle, load_lifecycle_dir
|
||||
from backend.periscopex.errata_check import check_errata
|
||||
from backend.periscopex.internal_features_check import check_internal_features
|
||||
from backend.periscopex.placement_check import check_placement
|
||||
from backend.periscopex.si_check import check_si
|
||||
from backend.periscopex.crystal_cl_check import check_crystal_cl
|
||||
from backend.periscopex.nc_pin_check import check_nc_pins
|
||||
|
||||
@@ -77,7 +74,6 @@ def _is_deterministic(f: Finding) -> bool:
|
||||
def _run_deterministic_checks(
|
||||
graph: DesignGraph, constraints_map: dict,
|
||||
lifecycle_map: dict | None = None,
|
||||
layout: LayoutGraph | None = None,
|
||||
) -> list[Finding]:
|
||||
"""Run the deterministic graph checks, fail-soft per check — a check bug
|
||||
can never break the review or the report."""
|
||||
@@ -100,8 +96,6 @@ def _run_deterministic_checks(
|
||||
("lifecycle_check", lambda: check_lifecycle(graph, lifecycle_map)),
|
||||
("errata_check", lambda: check_errata(graph, constraints_map)),
|
||||
("internal_features_check", lambda: check_internal_features(graph, constraints_map)),
|
||||
("placement_check", lambda: check_placement(graph, constraints_map, layout)),
|
||||
("si_check", lambda: check_si(graph, constraints_map, layout)),
|
||||
("crystal_cl_check", lambda: check_crystal_cl(graph)),
|
||||
("nc_pin_check", lambda: check_nc_pins(graph, constraints_map)),
|
||||
):
|
||||
@@ -112,17 +106,6 @@ def _run_deterministic_checks(
|
||||
return out
|
||||
|
||||
|
||||
def _load_layout_graph(graph_path: str) -> LayoutGraph | None:
|
||||
path = Path(graph_path).with_name("layout_graph.json")
|
||||
if not path.is_file():
|
||||
return None
|
||||
try:
|
||||
return LayoutGraph.model_validate_json(path.read_text())
|
||||
except Exception:
|
||||
log.exception("layout_graph.json invalid — skipping placement_check")
|
||||
return None
|
||||
|
||||
|
||||
def _assistant_text(blocks) -> str:
|
||||
"""Best-effort extraction of text content from a completion's raw
|
||||
assistant blocks. Provider-agnostic and never raises."""
|
||||
@@ -312,6 +295,9 @@ async def review_ic_async(
|
||||
pdf_dir: Path | None = None,
|
||||
storage=None,
|
||||
excerpt_cache: dict | None = None,
|
||||
extra_context: str = "",
|
||||
system_prompt: str | None = None,
|
||||
log_stage: str = "review",
|
||||
) -> tuple[ReviewResult, dict]:
|
||||
"""Review one IC against its datasheet. Async, multi-turn.
|
||||
|
||||
@@ -373,7 +359,7 @@ async def review_ic_async(
|
||||
|
||||
session = await provider.create_session(
|
||||
model=model,
|
||||
system=SYSTEM_PROMPT,
|
||||
system=system_prompt or SYSTEM_PROMPT,
|
||||
# Gemini 2.5/3 thinking models count thoughts against this cap.
|
||||
# 4096 was too tight: U3 (largest IC) burned the entire budget
|
||||
# on thinking and emitted zero visible output, dropping its
|
||||
@@ -387,13 +373,16 @@ async def review_ic_async(
|
||||
)
|
||||
try:
|
||||
context = build_component_context(graph, constraints_map, ic_ref)
|
||||
user_text = f"Review this component's usage:\n\n{context}"
|
||||
if extra_context.strip():
|
||||
user_text += "\n\n" + extra_context.strip()
|
||||
|
||||
initial_msg = Message(
|
||||
role="user",
|
||||
content=[
|
||||
PdfBlock(path=Path(trimmed_pdf), cacheable=True),
|
||||
TextBlock(
|
||||
text=f"Review this component's usage:\n\n{context}",
|
||||
text=user_text,
|
||||
cacheable=True,
|
||||
),
|
||||
],
|
||||
@@ -503,7 +492,7 @@ async def review_ic_async(
|
||||
)
|
||||
if api_logger:
|
||||
api_logger.log(
|
||||
stage="review", identifier=ic_ref,
|
||||
stage=log_stage, identifier=ic_ref,
|
||||
model=model, provider=provider.name,
|
||||
input_tokens=total_input, output_tokens=total_output,
|
||||
cache_creation_input_tokens=total_cache_creation,
|
||||
@@ -607,7 +596,7 @@ async def review_ic_async(
|
||||
trace["duration_ms"] = int((time.monotonic() - t0) * 1000)
|
||||
if api_logger:
|
||||
api_logger.log(
|
||||
stage="review", identifier=ic_ref,
|
||||
stage=log_stage, identifier=ic_ref,
|
||||
model=model, provider=provider.name,
|
||||
input_tokens=total_input, output_tokens=total_output,
|
||||
cache_creation_input_tokens=total_cache_creation,
|
||||
@@ -730,7 +719,7 @@ async def validate_design_async(
|
||||
if loaded:
|
||||
lifecycle_map.update(loaded)
|
||||
deterministic_findings = _run_deterministic_checks(
|
||||
graph, constraints_map, lifecycle_map, layout=_load_layout_graph(graph_path),
|
||||
graph, constraints_map, lifecycle_map,
|
||||
)
|
||||
|
||||
pdf_dir_path = Path(pdf_dir)
|
||||
|
||||
Reference in New Issue
Block a user