Add /api/library datasheet import and component GET/PUT with no exam. Reorganize pytest into datasheet, library, schematic, PCB, and AF+AI. Document Rust criteria (none chosen; no rustup) and coding conformity.
224 lines
6.2 KiB
Python
224 lines
6.2 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.review_parse import assign_finding_ids, parse_submit_review as _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"
|