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>
74 lines
2.5 KiB
Python
74 lines
2.5 KiB
Python
"""BOM vs schematic property matching.
|
|
|
|
Compares per-reference MPN/value from the schematic property table against
|
|
the uploaded BOM. Silent when the schematic map is empty (PADS/EDIF) so
|
|
we never invent orphans from a format that has no schematic properties.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from backend.pinscopex.models import Finding
|
|
|
|
|
|
def _norm_mpn(value: object) -> str:
|
|
return " ".join(str(value or "").split()).upper()
|
|
|
|
|
|
def check_bom_schematic_match(
|
|
schematic: dict[str, dict],
|
|
bom: dict[str, dict],
|
|
) -> list[Finding]:
|
|
if not schematic:
|
|
return []
|
|
|
|
findings: list[Finding] = []
|
|
refs = sorted(set(schematic) | set(bom))
|
|
for ref in refs:
|
|
if ref.startswith("#"):
|
|
continue
|
|
sch = schematic.get(ref) or {}
|
|
bom_row = bom.get(ref) or {}
|
|
if ref not in schematic:
|
|
findings.append(Finding(
|
|
designator=ref,
|
|
mpn=str(bom_row.get("mpn") or ""),
|
|
aspect="bom_match",
|
|
source="bom_match",
|
|
status="WARNING",
|
|
finding=(
|
|
f"BOM lists {ref} but the schematic has no such reference."
|
|
),
|
|
why=(
|
|
"An extra BOM line that is not in the netlist will not be "
|
|
"validated against a datasheet and may indicate a stale BOM."
|
|
),
|
|
recommendation=f"Remove {ref} from the BOM or add it to the schematic.",
|
|
rule_id="PS-BOM-002",
|
|
pins=[],
|
|
))
|
|
continue
|
|
sch_mpn = _norm_mpn(sch.get("mpn"))
|
|
bom_mpn = _norm_mpn(bom_row.get("mpn"))
|
|
if sch_mpn and bom_mpn and sch_mpn != bom_mpn:
|
|
findings.append(Finding(
|
|
designator=ref,
|
|
mpn=str(sch.get("mpn") or ""),
|
|
aspect="bom_match",
|
|
source="bom_match",
|
|
status="ERROR",
|
|
finding=(
|
|
f"{ref} schematic MPN '{sch.get('mpn')}' does not match "
|
|
f"BOM MPN '{bom_row.get('mpn')}'."
|
|
),
|
|
why=(
|
|
"Datasheet review and library lookup follow one MPN. "
|
|
"A mismatch means the wrong die or a stale BOM row."
|
|
),
|
|
recommendation=(
|
|
f"Make {ref}'s BOM and schematic MPN identical, then re-run."
|
|
),
|
|
rule_id="PS-BOM-001",
|
|
pins=[ref],
|
|
))
|
|
return findings
|