Tighten PCB findings: actions, stitch gating, real copper.
Fill action on every finding and show it in the report. Stitch/return only on large HS/power nets. Parse stackup thickness, arcs, and vias from the board. IC courtyard pad copper is not PE-PLC-004 RULE. Recommended RC/cap stay REVIEW.
This commit is contained in:
@@ -18,7 +18,10 @@ Provenance = Literal["MANDATORY", "RECOMMENDED", "TYPICAL", "EXAMPLE"]
|
||||
FindingClass = Literal["RULE", "RISK", "REVIEW", "INFO"]
|
||||
EvidenceStatus = Literal["SUFFICIENT", "INSUFFICIENT"]
|
||||
|
||||
_LLM_SOURCES = frozenset({None, "review", "pcb_review"})
|
||||
_DEFAULT_ACTION = (
|
||||
"Review this finding against the datasheet and the board, then change "
|
||||
"the design if it applies."
|
||||
)
|
||||
_SOFT_PROVENANCE = frozenset({"RECOMMENDED", "TYPICAL", "EXAMPLE"})
|
||||
|
||||
|
||||
@@ -71,17 +74,20 @@ def _seed() -> None:
|
||||
("PE-MUX-001", "Pin function must match the mux / datasheet pin table."),
|
||||
("PE-NC-001", "NC pins must not be connected."),
|
||||
("PE-DEC-001", "Supply pin requires a decoupling capacitor on that net."),
|
||||
("PE-DEC-002", "Decoupling capacitor value vs datasheet."),
|
||||
("PE-I2C-001", "I2C bus requires pull-ups."),
|
||||
("PE-I2C-002", "I2C pull-up value vs datasheet."),
|
||||
("PE-RST-001", "Reset pin requires the specified pull."),
|
||||
("PE-RST-002", "Reset pull value vs datasheet."),
|
||||
("PE-BOM-001", "BOM MPN must match the netlist part."),
|
||||
("PE-BOM-002", "BOM line missing from the netlist."),
|
||||
("PE-DNP-001", "DNP / fitted state vs netlist."),
|
||||
("PE-DRT-001", "Operating voltage must not exceed the capacitor rating."),
|
||||
):
|
||||
_add(rid, "MANDATORY", "RULE", domain="schema", requirement=req)
|
||||
for rid, req in (
|
||||
("PE-DEC-002", "Decoupling capacitor value vs datasheet (recommended)."),
|
||||
("PE-I2C-002", "I2C pull-up value vs datasheet (recommended)."),
|
||||
("PE-RST-002", "Reset pull value vs datasheet (recommended)."),
|
||||
):
|
||||
_add(rid, "RECOMMENDED", "REVIEW", domain="schema", requirement=req)
|
||||
for rid in ("PE-TH-001", "PE-TH-003"):
|
||||
_add(rid, "TYPICAL", "RISK", domain="schema",
|
||||
requirement="Thermal estimate only with I_load and θJA from specs.")
|
||||
@@ -93,12 +99,12 @@ def _seed() -> None:
|
||||
requirement="Trace width × copper thickness vs I_load (IPC-2221 when ΔT known).")
|
||||
for rid, prov, cls, req in (
|
||||
("PE-SEQ-001", "RECOMMENDED", "RISK", "Power sequencing from datasheet notes."),
|
||||
("PE-FLT-001", "RECOMMENDED", "RISK", "Filter topology vs datasheet."),
|
||||
("PE-FLT-002", "RECOMMENDED", "RISK", "Filter component vs datasheet."),
|
||||
("PE-FLT-003", "RECOMMENDED", "RISK", "Filter cutoff vs datasheet."),
|
||||
("PE-FLT-001", "RECOMMENDED", "REVIEW", "Filter topology vs datasheet."),
|
||||
("PE-FLT-002", "RECOMMENDED", "REVIEW", "Filter component vs datasheet."),
|
||||
("PE-FLT-003", "RECOMMENDED", "REVIEW", "Filter cutoff vs datasheet."),
|
||||
("PE-XTAL-001", "MANDATORY", "RULE", "Crystal load capacitance vs Cl."),
|
||||
("PE-XTAL-002", "RECOMMENDED", "RISK", "Crystal layout / load-cap note."),
|
||||
("PE-XTAL-003", "RECOMMENDED", "RISK", "Crystal drive / Cl note."),
|
||||
("PE-XTAL-002", "RECOMMENDED", "REVIEW", "Crystal layout / load-cap note."),
|
||||
("PE-XTAL-003", "RECOMMENDED", "REVIEW", "Crystal drive / Cl note."),
|
||||
("PE-LF-001", "TYPICAL", "INFO", "Lifecycle / NRND."),
|
||||
("PE-LF-002", "TYPICAL", "INFO", "Lifecycle / last-time-buy."),
|
||||
("PE-LF-003", "TYPICAL", "INFO", "Lifecycle / obsolete."),
|
||||
@@ -118,10 +124,11 @@ def _seed() -> None:
|
||||
("PE-PLC-001", "Decoupling proximity max_distance_mm from layout_rules."),
|
||||
("PE-PLC-002", "Thermal via min_via_count from layout_rules."),
|
||||
("PE-PLC-003", "same_layer decoupling from layout_rules."),
|
||||
("PE-PLC-004", "Keepout from layout_rules."),
|
||||
("PE-SI-001", "Differential skew vs length_match mm."),
|
||||
):
|
||||
_add(rid, "MANDATORY", "RULE", domain="pcb", requirement=req)
|
||||
_add("PE-PLC-004", "RECOMMENDED", "REVIEW", domain="pcb",
|
||||
requirement="Keepout from layout_rules — not a DRC for pad copper.")
|
||||
_add("PE-VIA-001", "TYPICAL", "INFO", domain="pcb",
|
||||
requirement="Via current share from I_load and via count (no invented table).")
|
||||
_add("PE-THM-001", "RECOMMENDED", "RISK", domain="pcb",
|
||||
@@ -160,8 +167,8 @@ def complete_finding(f: Finding) -> Finding:
|
||||
if not (f.requirement or "").strip():
|
||||
f.requirement = (rec.requirement if rec and rec.requirement else None) or f.why
|
||||
if not (f.action or "").strip():
|
||||
f.action = f.recommendation or ""
|
||||
if not (f.recommendation or "").strip() and f.action:
|
||||
f.action = (f.recommendation or "").strip() or _DEFAULT_ACTION
|
||||
if not (f.recommendation or "").strip():
|
||||
f.recommendation = f.action
|
||||
|
||||
llm = (f.source in {None, "review", "pcb_review"}) and not f.rule_id
|
||||
@@ -219,6 +226,10 @@ def complete_finding(f: Finding) -> Finding:
|
||||
f.why = f.requirement
|
||||
if not (f.finding or "").strip():
|
||||
f.finding = f.facts
|
||||
if not (f.action or "").strip():
|
||||
f.action = _DEFAULT_ACTION
|
||||
if not (f.recommendation or "").strip():
|
||||
f.recommendation = f.action
|
||||
return f
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user