Ship Sprint 0 CAD foundations so findings, KiCad hierarchy, BOM match, eval, and optional PCB ingest have a stable contract.

Extend Finding with rule_id/net/pins, flatten multi-sheet .kicad_sch, flag MPN mismatches, score simple_project, and parse .kicad_pcb without running layout DRC.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-10 21:54:37 +02:00
co-authored by Cursor
parent d31c04ba86
commit e4fd6c3849
29 changed files with 1995 additions and 46 deletions
+17
View File
@@ -61,6 +61,7 @@ function mapProject(p: Record<string, unknown>): Project {
summary: p.summary as Record<string, number> | undefined,
hasNetlist: p.has_netlist as boolean,
hasBom: p.has_bom as boolean,
hasPcb: (p.has_pcb as boolean | undefined) ?? false,
datasheetCount: p.datasheet_count as number,
skippedComponents: (p.skipped_components as SkippedComponent[] | null) ?? undefined,
completedReviewRefs: (p.completed_review_refs as string[] | null) ?? undefined,
@@ -277,6 +278,22 @@ export interface UploadNetlistResult {
designator_pins: NetlistPreviewDesignator[];
}
export async function uploadPcb(
projectId: string, file: File,
): Promise<{ path: string; footprints: number; nets: number; segments: number }> {
const form = new FormData();
form.append("file", file);
const res = await authFetch(
`${BASE}/api/projects/${projectId}/upload/pcb`,
{ method: "POST", body: form },
);
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: "Upload failed" }));
throw new Error(err.detail || "Failed to upload PCB");
}
return res.json();
}
export async function uploadNetlist(
projectId: string, file: File,
): Promise<UploadNetlistResult> {
+7 -1
View File
@@ -12,10 +12,13 @@ const HEADER = [
"Description",
"Recommendation",
"Source",
"Net",
"Pins",
"Rule",
];
// Column widths (in characters), aligned with HEADER order.
const COL_WIDTHS = [12, 20, 12, 10, 44, 60, 50, 24];
const COL_WIDTHS = [12, 20, 12, 10, 44, 60, 50, 24, 16, 16, 14];
// Fold the datasheet reference + page number into a single cell, mirroring the
// finding card's reference button + "Automated check" tag logic.
@@ -47,6 +50,9 @@ export function exportReportToExcel(
f.why ?? "",
f.recommendation ?? "",
formatSource(f),
f.net ?? "",
(f.pins ?? []).join(", "),
f.rule_id ?? "",
]);
const ws = XLSX.utils.aoa_to_sheet([HEADER, ...rows]);
+9
View File
@@ -14,6 +14,12 @@ export interface Finding {
recommendation?: string;
reference: string;
source?: string | null; // pin_mux_check / led_current_check / supply_decoupling_check / i2c_pullup_check / reset_pullup_check = deterministic; null/"review" = LLM
net?: string | null;
pins?: string[];
rule_id?: string | null;
cad_sheet?: string | null;
cad_uuid?: string | null;
variant?: string | null;
}
export interface FindingComment {
@@ -88,6 +94,8 @@ export interface Net {
export interface DesignGraph {
components: Record<string, Component>;
nets: Record<string, Net>;
bom_fields?: Record<string, { mpn?: string | null; value?: string }>;
schematic_fields?: Record<string, { mpn?: string | null; value?: string }>;
}
export interface BomSummaryRow {
@@ -226,6 +234,7 @@ export interface Project {
summary?: Record<string, number>;
hasNetlist: boolean;
hasBom: boolean;
hasPcb?: boolean;
datasheetCount: number;
skippedComponents?: SkippedComponent[];
completedReviewRefs?: string[];