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:
@@ -46,6 +46,7 @@ import {
|
||||
deleteProject,
|
||||
uploadBom,
|
||||
uploadNetlist,
|
||||
uploadPcb,
|
||||
uploadDatasheet,
|
||||
startPipeline,
|
||||
checkLibrary,
|
||||
@@ -548,6 +549,7 @@ export function CreateProjectDialog({
|
||||
const [name, setName] = useState("");
|
||||
const [bomFile, setBomFile] = useState<File | null>(null);
|
||||
const [netlistFile, setNetlistFile] = useState<File | null>(null);
|
||||
const [pcbFile, setPcbFile] = useState<File | null>(null);
|
||||
const [netlistNetCount, setNetlistNetCount] = useState<number | null>(null);
|
||||
const [netlistError, setNetlistError] = useState<string | null>(null);
|
||||
|
||||
@@ -1879,6 +1881,10 @@ export function CreateProjectDialog({
|
||||
setProgress("Uploading netlist...");
|
||||
await uploadNetlist(project.id, netlistFile!);
|
||||
}
|
||||
if (pcbFile) {
|
||||
setProgress("Uploading PCB...");
|
||||
await uploadPcb(project.id, pcbFile);
|
||||
}
|
||||
} catch (uploadErr) {
|
||||
if (!projectAlreadyExists) {
|
||||
try {
|
||||
@@ -2112,6 +2118,19 @@ export function CreateProjectDialog({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<FileUploadZone
|
||||
label="PCB (.kicad_pcb, optional)"
|
||||
accept=".kicad_pcb"
|
||||
files={pcbFile ? [pcbFile] : []}
|
||||
onFilesChange={(files) => setPcbFile(files[0] ?? null)}
|
||||
/>
|
||||
<p className="text-[11px] text-muted-foreground leading-tight px-1">
|
||||
Optional. Layout checks (trace width, 3W, placement mm) stay
|
||||
off until a board is present. Schema review still runs
|
||||
without it.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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[];
|
||||
|
||||
Reference in New Issue
Block a user