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:
2026-09-19 22:54:17 +02:00
parent a2011dad91
commit 16c606ae3d
28 changed files with 1906 additions and 32 deletions
+43
View File
@@ -110,6 +110,8 @@ function mapProject(p: Record<string, unknown>): Project {
netlistSubdesigns: (p.netlist_subdesigns as string[] | null) ?? null,
placementStatus: (p.placement_status as Project["placementStatus"]) ?? "draft",
placementState: (p.placement_state as Record<string, unknown> | null) ?? null,
pcbStatus: (p.pcb_status as Project["pcbStatus"]) ?? "draft",
pcbState: (p.pcb_state as Record<string, unknown> | null) ?? null,
};
}
@@ -614,6 +616,47 @@ export function placementEventsUrl(projectId: string): string {
return `${BASE}/api/pipeline/${projectId}/placement/events`;
}
export async function startPcbPipeline(projectId: string) {
const res = await authFetch(
`${BASE}/api/pipeline/${projectId}/pcb/start`,
{ method: "POST" },
);
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: "Failed to start PCB review" }));
throw new Error(err.detail || "Failed to start PCB review");
}
return res.json();
}
export async function cancelPcbPipeline(projectId: string) {
const res = await authFetch(
`${BASE}/api/pipeline/${projectId}/pcb/cancel`,
{ method: "POST" },
);
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: "Failed to cancel PCB review" }));
throw new Error(err.detail || "Failed to cancel PCB review");
}
return res.json();
}
export function pcbEventsUrl(projectId: string): string {
return `${BASE}/api/pipeline/${projectId}/pcb/events`;
}
export async function fetchPcbInventory(projectId: string): Promise<{
nets: Array<Record<string, unknown>>;
domains: string[];
group_count: number;
}> {
const res = await authFetch(`${BASE}/api/pipeline/${projectId}/pcb/inventory`);
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: "PCB inventory not found" }));
throw new Error(err.detail || "PCB inventory not found");
}
return res.json();
}
export async function fetchPlacementPlan(projectId: string): Promise<PlacementPlan> {
const res = await authFetch(`${BASE}/api/pipeline/${projectId}/placement/plan`);
if (!res.ok) {
+2
View File
@@ -283,6 +283,8 @@ export interface Project {
// Placement pipeline (parallel to analysis — topology only).
placementStatus?: "draft" | "queued" | "running" | "complete" | "error" | "cancelled";
placementState?: Record<string, unknown> | null;
pcbStatus?: "draft" | "queued" | "running" | "complete" | "error" | "cancelled";
pcbState?: Record<string, unknown> | null;
}
export type RoleHint =