Files
periscope/tests/test_finding_engine.py
T
michele 342792df2d Ship Fase B slices: hierarchy, derating bands, timing, PI, ESD/return.
ERROR stays only for RULE+MANDATORY. Hierarchy walks component→pin→net→block.
Derating is PASS/MARGIN/RISK from Vop/Vrated. Timing and PI skip without numbers.
ESD and HS return path are REVIEW, not RULE ERROR.
2026-09-20 09:35:04 +02:00

224 lines
6.1 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 == "WARNING"
def test_review_and_risk_cannot_stay_error():
review = Finding(
designator="U1",
finding="PowerPAD vias",
why="layout note",
status="ERROR",
source="pcb_review",
facts="0 vias under U1",
requirement="datasheet PowerPAD",
source_quote="Connect the thermal pad with vias to GND.",
evidence_status="SUFFICIENT",
)
complete_finding(review)
assert review.finding_class == "REVIEW"
assert review.status == "WARNING"
risk = Finding(
designator="C1",
finding="high utilization",
why="Vop near Vr",
status="ERROR",
rule_id="PE-DRT-002",
source="pcb_derating",
evidence_status="SUFFICIENT",
facts="3.0 / 3.3 V",
requirement="utilization band",
)
complete_finding(risk)
assert risk.finding_class == "RISK"
assert risk.status == "WARNING"
def test_mandatory_rule_stays_error():
f = Finding(
designator="U1",
finding="NC tied",
why="NC must float",
status="ERROR",
rule_id="PE-NC-001",
source="nc_pin_check",
facts="NC on GND",
requirement="NC pins must not be connected.",
evidence_status="SUFFICIENT",
)
complete_finding(f)
assert f.finding_class == "RULE"
assert f.provenance == "MANDATORY"
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"