Apply the finding engine to schematic and PCB reviews.
FACT, REQUIREMENT, and INFERENCE are separate fields; datasheet provenance lives in the rule DB so Recommended cannot stay ERROR; LLM output is REVIEW. Designer decisions persist across rescans. Schematic and PCB share one library and the same finding object in the report UI.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""Finding engine — FACT/REQUIREMENT/INFERENCE, provenance clamp, decisions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.periscopex.finding_engine import (
|
||||
DesignerDecision,
|
||||
apply_decisions,
|
||||
complete_finding,
|
||||
finding_fingerprint,
|
||||
lookup_rule,
|
||||
)
|
||||
from backend.periscopex.models import Finding
|
||||
from backend.periscopex.validate import assign_finding_ids, _parse_review
|
||||
|
||||
|
||||
def test_recommended_never_error():
|
||||
f = Finding(
|
||||
designator="U1",
|
||||
finding="layout note",
|
||||
why="place close",
|
||||
status="ERROR",
|
||||
rule_id="PE-THM-001",
|
||||
source="pcb_power_thermal",
|
||||
evidence_status="SUFFICIENT",
|
||||
facts="no pour",
|
||||
requirement="datasheet layout page",
|
||||
)
|
||||
complete_finding(f)
|
||||
assert f.provenance == "RECOMMENDED"
|
||||
assert f.status != "ERROR"
|
||||
assert f.finding_class != "RULE"
|
||||
|
||||
|
||||
def test_llm_review_is_never_rule():
|
||||
result = _parse_review(
|
||||
{
|
||||
"findings": [{
|
||||
"finding": "strap",
|
||||
"why": "datasheet",
|
||||
"status": "ERROR",
|
||||
"source_page": 2,
|
||||
"source_quote": "PSEL must be high for 3.3 V.",
|
||||
"recommendation": "Tie PSEL high.",
|
||||
}],
|
||||
"checked_areas": [],
|
||||
},
|
||||
"U1",
|
||||
"PART",
|
||||
)
|
||||
f = result.findings[0]
|
||||
complete_finding(f)
|
||||
assert f.finding_class == "REVIEW"
|
||||
assert f.facts
|
||||
assert f.requirement
|
||||
assert f.status == "ERROR"
|
||||
|
||||
|
||||
def test_insufficient_evidence_does_not_stay_error():
|
||||
f = Finding(
|
||||
designator="U1",
|
||||
finding="narrow trace",
|
||||
why="",
|
||||
status="ERROR",
|
||||
source="pcb_power_thermal",
|
||||
rule_id="PE-PWR-001",
|
||||
evidence_status="INSUFFICIENT",
|
||||
)
|
||||
complete_finding(f)
|
||||
assert f.status != "ERROR"
|
||||
assert "Insufficient evidence" in f.inference
|
||||
assert f.finding_class != "RULE" or f.evidence_status == "INSUFFICIENT"
|
||||
|
||||
|
||||
def test_decision_suppresses_rescan_nag():
|
||||
f = Finding(
|
||||
designator="U1",
|
||||
finding="PSEL low",
|
||||
why="must be high",
|
||||
status="ERROR",
|
||||
rule_id="PE-MUX-001",
|
||||
source="pin_mux_check",
|
||||
net="PSEL",
|
||||
aspect="config",
|
||||
facts="PSEL net is GND",
|
||||
requirement="PSEL high for 3V3",
|
||||
)
|
||||
complete_finding(f)
|
||||
assert f.status == "ERROR"
|
||||
d = DesignerDecision(
|
||||
decision_id="DEC-1",
|
||||
fingerprint=finding_fingerprint(f),
|
||||
rule_id="PE-MUX-001",
|
||||
designator="U1",
|
||||
net="PSEL",
|
||||
aspect="config",
|
||||
intent="wontfix",
|
||||
reason="PSEL low intentional",
|
||||
)
|
||||
apply_decisions([f], [d])
|
||||
assert f.suppressed is True
|
||||
assert f.status == "INFO"
|
||||
assert f.finding_class == "INFO"
|
||||
assert "PSEL low intentional" in f.inference
|
||||
|
||||
|
||||
def test_schematic_assign_ids_fills_engine_fields():
|
||||
f = Finding(
|
||||
designator="U3",
|
||||
finding="NC tied",
|
||||
why="NC must float",
|
||||
status="ERROR",
|
||||
rule_id="PE-NC-001",
|
||||
source="nc_pin_check",
|
||||
)
|
||||
assign_finding_ids([f])
|
||||
assert f.finding_id == "U3-001"
|
||||
assert f.facts == "NC tied"
|
||||
assert f.requirement
|
||||
assert f.provenance == "MANDATORY"
|
||||
assert f.finding_class == "RULE"
|
||||
assert f.confidence is not None
|
||||
|
||||
|
||||
def test_same_object_pcb_and_schema():
|
||||
assert lookup_rule("PE-NC-001").domain == "schema"
|
||||
assert lookup_rule("PE-LAY-001").domain == "pcb"
|
||||
sch = Finding(designator="U1", finding="x", status="INFO", rule_id="PE-NC-001", source="nc_pin_check")
|
||||
pcb = Finding(designator="U1", finding="y", status="INFO", rule_id="PE-LAY-001", source="pcb_net_match")
|
||||
complete_finding(sch)
|
||||
complete_finding(pcb)
|
||||
for obj in (sch, pcb):
|
||||
assert obj.facts
|
||||
assert obj.finding_class
|
||||
assert obj.provenance
|
||||
assert obj.evidence_status
|
||||
Reference in New Issue
Block a user