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:
@@ -247,23 +247,16 @@ def _build_deduped(
|
||||
new_why = "Unverified: " + new_why
|
||||
|
||||
try:
|
||||
result.append(Finding(
|
||||
finding_id=canon.finding_id,
|
||||
designator=canon.designator,
|
||||
mpn=canon.mpn,
|
||||
aspect=canon.aspect,
|
||||
finding=str(group.get("finding") or canon.finding),
|
||||
why=new_why,
|
||||
source_page=group.get("source_page", canon.source_page),
|
||||
source_quote=canon.source_quote,
|
||||
source_designator=canon.source_designator,
|
||||
status=final_status,
|
||||
recommendation=str(
|
||||
result.append(canon.model_copy(update={
|
||||
"finding": str(group.get("finding") or canon.finding),
|
||||
"why": new_why,
|
||||
"source_page": group.get("source_page", canon.source_page),
|
||||
"status": final_status,
|
||||
"recommendation": str(
|
||||
group.get("recommendation") or canon.recommendation
|
||||
),
|
||||
reference=str(group.get("reference") or canon.reference),
|
||||
source=canon.source,
|
||||
))
|
||||
"reference": str(group.get("reference") or canon.reference),
|
||||
}))
|
||||
except Exception:
|
||||
log.exception("dedupe: failed to build merged Finding")
|
||||
return None
|
||||
|
||||
@@ -394,20 +394,15 @@ def _build_normalized(
|
||||
new_why = "Unverified: " + new_why
|
||||
|
||||
try:
|
||||
result.append(Finding(
|
||||
finding_id=canon.finding_id,
|
||||
designator=canon.designator,
|
||||
mpn=canon.mpn,
|
||||
aspect=canon.aspect,
|
||||
finding=str(entry.get("finding") or canon.finding),
|
||||
why=new_why,
|
||||
source_page=entry.get("source_page", canon.source_page),
|
||||
source_quote=str(entry.get("source_quote") or canon.source_quote),
|
||||
source_designator=canon.source_designator,
|
||||
status=final_status,
|
||||
recommendation=str(entry.get("recommendation") or canon.recommendation),
|
||||
reference=str(entry.get("reference") or canon.reference),
|
||||
))
|
||||
result.append(canon.model_copy(update={
|
||||
"finding": str(entry.get("finding") or canon.finding),
|
||||
"why": new_why,
|
||||
"source_page": entry.get("source_page", canon.source_page),
|
||||
"source_quote": str(entry.get("source_quote") or canon.source_quote),
|
||||
"status": final_status,
|
||||
"recommendation": str(entry.get("recommendation") or canon.recommendation),
|
||||
"reference": str(entry.get("reference") or canon.reference),
|
||||
}))
|
||||
except Exception:
|
||||
log.exception("normalize: failed to build merged Finding")
|
||||
return None
|
||||
|
||||
@@ -1496,6 +1496,25 @@ async def _stage_passive_extraction(ctx: PipelineContext) -> None:
|
||||
{"stage": "passive_extraction", "status": "complete"})
|
||||
|
||||
|
||||
def _write_layout_graph(ws: PipelineWorkspace, project_id: str) -> None:
|
||||
"""Parse optional `.kicad_pcb`. Fail-soft — schema validation must still run."""
|
||||
pcb = ws.local_path("uploads/pcb.kicad_pcb")
|
||||
if not pcb.is_file():
|
||||
return
|
||||
try:
|
||||
from backend.pinscopex.parsers_kicad_pcb import parse_kicad_pcb
|
||||
|
||||
layout = parse_kicad_pcb(pcb)
|
||||
out = ws.local_path("layout_graph.json")
|
||||
out.write_text(layout.model_dump_json(indent=2) + "\n")
|
||||
logger.info(
|
||||
"layout_graph: %s footprints, %s nets for %s",
|
||||
len(layout.footprints), len(layout.nets), project_id,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("kicad_pcb parse failed — continuing without layout")
|
||||
|
||||
|
||||
async def _stage_graph_build(ctx: PipelineContext) -> None:
|
||||
"""Stage 4 — Build the design graph from netlist, BOM, and extracted data."""
|
||||
broker.publish(ctx.project_id, "step_update",
|
||||
@@ -1525,6 +1544,7 @@ async def _stage_graph_build(ctx: PipelineContext) -> None:
|
||||
|
||||
graph_path = ctx.ws.local_path("design_graph.json")
|
||||
graph_path.write_text(ctx.graph.model_dump_json(indent=2) + "\n")
|
||||
_write_layout_graph(ctx.ws, ctx.project_id)
|
||||
|
||||
broker.publish(ctx.project_id, "step_update",
|
||||
{"stage": "graph_build", "status": "complete",
|
||||
@@ -2088,6 +2108,7 @@ async def run_regen_pipeline(
|
||||
|
||||
graph_path = ws.local_path("design_graph.json")
|
||||
graph_path.write_text(graph.model_dump_json(indent=2) + "\n")
|
||||
_write_layout_graph(ws, project_id)
|
||||
|
||||
broker.publish(project_id, "step_update",
|
||||
{"stage": "graph_build", "status": "complete",
|
||||
|
||||
@@ -5,6 +5,7 @@ Each project lives at users/{user_id}/projects/{id}/ with:
|
||||
uploads/bom.csv — uploaded BOM
|
||||
uploads/netlist.asc — uploaded netlist
|
||||
uploads/datasheets/*.pdf — uploaded datasheets
|
||||
uploads/pcb.kicad_pcb — optional KiCad board (layout checks)
|
||||
extracted/ — IC extraction output
|
||||
patterns/ — passive patterns
|
||||
models/ — cached component specs
|
||||
@@ -82,6 +83,7 @@ class ProjectMeta(BaseModel):
|
||||
updated: str = ""
|
||||
has_bom: bool = False
|
||||
has_netlist: bool = False
|
||||
has_pcb: bool = False
|
||||
# "pads" | "edif" | None — None for legacy projects (pre-EDIF-support).
|
||||
# Legacy reads fall back to looking for netlist.asc on disk.
|
||||
netlist_format: str | None = None
|
||||
@@ -658,6 +660,15 @@ def save_netlist(
|
||||
return key
|
||||
|
||||
|
||||
def save_pcb(
|
||||
storage: StorageBackend, user_id: str, project_id: str, data: bytes
|
||||
) -> str:
|
||||
key = f"{_project_prefix(user_id, project_id)}/uploads/pcb.kicad_pcb"
|
||||
storage.write_bytes(key, data)
|
||||
update_project(storage, user_id, project_id, has_pcb=True)
|
||||
return key
|
||||
|
||||
|
||||
def save_datasheet(
|
||||
storage: StorageBackend, user_id: str, project_id: str, mpn: str, data: bytes
|
||||
) -> str:
|
||||
|
||||
@@ -49,6 +49,7 @@ from backend.pinscopex.passive_rail_check import (
|
||||
check_reset_pullups,
|
||||
check_supply_decoupling,
|
||||
)
|
||||
from backend.pinscopex.bom_match_check import check_bom_schematic_match
|
||||
|
||||
TRACE_VERSION = 1
|
||||
|
||||
@@ -70,6 +71,9 @@ def _run_deterministic_checks(
|
||||
("supply_decoupling_check", lambda: check_supply_decoupling(graph, constraints_map)),
|
||||
("i2c_pullup_check", lambda: check_i2c_pullups(graph, constraints_map)),
|
||||
("reset_pullup_check", lambda: check_reset_pullups(graph, constraints_map)),
|
||||
("bom_match_check", lambda: check_bom_schematic_match(
|
||||
graph.schematic_fields, graph.bom_fields,
|
||||
)),
|
||||
):
|
||||
try:
|
||||
out.extend(fn())
|
||||
|
||||
Reference in New Issue
Block a user