Files
periscope/tests/test_finding_engine.py
T
michele facbaa2305 Show Action on finding cards and inject PCB geometry into AI context.
Layout and AI findings always get action (fallback recommendation). The card
renders that sentence in the body. PCB review context now includes vias under
the footprint, copper thickness, nearby widths, courtyard, and keepout polygons.
2026-09-20 09:13:51 +02:00

174 lines
4.8 KiB
Python

"""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
assert (obj.action or "").strip()
def test_empty_recommendation_gets_action():
f = Finding(designator="U1", finding="note", status="INFO", source="review")
complete_finding(f)
assert f.action.strip()
assert f.recommendation.strip() == f.action.strip()
def test_pcb_review_ai_finding_action_without_recommendation():
f = Finding(
designator="U1",
finding="PowerPAD vias",
why="thermal pad",
status="INFO",
source="pcb_review",
recommendation="",
action="",
)
complete_finding(f)
assert f.action.strip()
assert f.finding_class == "REVIEW"
def test_recommended_rc_cap_is_review_not_rule():
f = Finding(
designator="C1",
finding="10n vs 100n",
why="datasheet typical 100n",
status="WARNING",
rule_id="PE-DEC-002",
source="passive_rail_check",
)
complete_finding(f)
assert f.finding_class == "REVIEW"
assert f.provenance == "RECOMMENDED"
assert f.status != "ERROR"