Skip PE-PWR/PE-VIA without datasheet current (2.61.1).

Trace-width vs current runs only with I_load/Imax/I_abs. Missing I is a
skip, not INFO INSUFFICIENT, not USB 500 mA, not Iout_max-as-load.
This commit is contained in:
2026-09-21 12:11:52 +02:00
parent 6dc04b5cc7
commit c6abdae762
12 changed files with 115 additions and 64 deletions
@@ -102,7 +102,10 @@ def _seed() -> None:
requirement="I_load vs regulator capability (not Iout_max).") requirement="I_load vs regulator capability (not Iout_max).")
_add("PE-PWR-001", "MANDATORY", "RULE", domain="pcb", _add("PE-PWR-001", "MANDATORY", "RULE", domain="pcb",
source="pcb_power_thermal", source="pcb_power_thermal",
requirement="Trace width × copper thickness vs I_load (IPC-2221 when ΔT known).") requirement=(
"Trace width × copper vs datasheet I_load/Imax/I_abs "
"(IPC-2221 when ΔT known). Skip if that current is missing."
))
for rid, prov, cls, req in ( for rid, prov, cls, req in (
("PE-SEQ-001", "RECOMMENDED", "RISK", "Power sequencing from datasheet notes."), ("PE-SEQ-001", "RECOMMENDED", "RISK", "Power sequencing from datasheet notes."),
("PE-FLT-001", "RECOMMENDED", "REVIEW", "Filter topology vs datasheet."), ("PE-FLT-001", "RECOMMENDED", "REVIEW", "Filter topology vs datasheet."),
@@ -152,7 +155,10 @@ def _seed() -> None:
_add("PE-PLC-004", "RECOMMENDED", "REVIEW", domain="pcb", _add("PE-PLC-004", "RECOMMENDED", "REVIEW", domain="pcb",
requirement="Keepout from layout_rules — not a DRC for pad copper.") requirement="Keepout from layout_rules — not a DRC for pad copper.")
_add("PE-VIA-001", "TYPICAL", "INFO", domain="pcb", _add("PE-VIA-001", "TYPICAL", "INFO", domain="pcb",
requirement="Via current share from I_load and via count (no invented table).") requirement=(
"Via count on a net with datasheet I_load/Imax/I_abs "
"(no invented ampacity table). Skip if that current is missing."
))
_add("PE-THM-001", "RECOMMENDED", "RISK", domain="pcb", _add("PE-THM-001", "RECOMMENDED", "RISK", domain="pcb",
requirement="Dissipation vs courtyard pour/vias when P is known.") requirement="Dissipation vs courtyard pour/vias when P is known.")
_add("PE-KEL-001", "RECOMMENDED", "RISK", domain="pcb", _add("PE-KEL-001", "RECOMMENDED", "RISK", domain="pcb",
@@ -2,7 +2,8 @@
Not generic SI. Not via ampacity. Not thermal FEM. Graph connectivity only: Not generic SI. Not via ampacity. Not thermal FEM. Graph connectivity only:
class from BOM / footprint / net FACT, then connection integrity. Missing class from BOM / footprint / net FACT, then connection integrity. Missing
numbers are INSUFFICIENT_EVIDENCE. A bare RJ45 is not PoE. numbers are INSUFFICIENT_EVIDENCE. A bare RJ45 is not PoE. No invented
VBUS/Ethernet current (PE-PWR skips when the datasheet has no I).
""" """
from __future__ import annotations from __future__ import annotations
@@ -1,6 +1,8 @@
"""Power-trace, via, thermal-copper, and Kelvin checks on a LayoutGraph. """Power-trace, via, thermal-copper, and Kelvin checks on a LayoutGraph.
Skip when current, width, copper thickness, or datasheet numbers are missing. Trace-width vs current runs only if the datasheet reports I_load / Imax / I_abs.
Missing that number → skip (no invented I, no USB 500 mA, no INFO INSUFFICIENT).
Iout_max is a rating, not load. Via ampacity is never invented. No thermal FEM.
IPC-2221 is cited only when ΔT can be taken from datasheet Tjmax 25 °C. IPC-2221 is cited only when ΔT can be taken from datasheet Tjmax 25 °C.
""" """
@@ -30,6 +32,14 @@ from backend.periscopex.thermal_check import (
) )
from backend.periscopex.constraints_lookup import match_constraints as _match_constraints from backend.periscopex.constraints_lookup import match_constraints as _match_constraints
# Width/via current: I_load, Imax, or I_abs from the datasheet. Never Iout_max.
_TRACE_CURRENT_KEYS = _LOAD_KEYS + (
"i_max", "i_max_a", "imax", "imax_a",
"i_abs", "i_abs_a", "iabs",
"i_abs_max", "i_abs_max_a",
"absolute_maximum_current_a",
)
# IPC-2221 §6.2 (empirical): I = k · ΔT^0.44 · A^0.725, A in mil², I in A. # IPC-2221 §6.2 (empirical): I = k · ΔT^0.44 · A^0.725, A in mil², I in A.
_IPC_B = 0.44 _IPC_B = 0.44
_IPC_C = 0.725 _IPC_C = 0.725
@@ -105,18 +115,25 @@ def _external_layers(layers: list[str]) -> bool:
) )
def _datasheet_trace_current(comp) -> float | None:
"""I_load / Imax / I_abs from specs. None if absent or ≤0. Never Iout_max."""
i = _first(_specs_values(comp), _TRACE_CURRENT_KEYS)
if i is None or i <= 0:
return None
return i
def _load_on_output( def _load_on_output(
graph: DesignGraph, cmap: dict, ref: str, graph: DesignGraph, cmap: dict, ref: str,
) -> tuple[float, str, str] | None: ) -> tuple[float, str, str] | None:
"""I_load (not Iout_max) on an IC output net, plus that net name.""" """Datasheet current (not Iout_max) on an IC output net, plus that net name."""
comp = graph.components.get(ref) comp = graph.components.get(ref)
if not comp or comp.component_type != ComponentType.IC: if not comp or comp.component_type != ComponentType.IC:
return None return None
cons = _match_constraints(comp.mpn or comp.value, cmap) i_load = _datasheet_trace_current(comp)
values = _specs_values(comp)
i_load = _first(values, _LOAD_KEYS)
if i_load is None: if i_load is None:
return None return None
cons = _match_constraints(comp.mpn or comp.value, cmap)
vout = _pin_net_by_role(graph, comp, cons, _VOUT_PIN) vout = _pin_net_by_role(graph, comp, cons, _VOUT_PIN)
if not vout: if not vout:
return None return None
@@ -128,7 +145,10 @@ def check_pcb_power_traces(
constraints_map: dict, constraints_map: dict,
layout: LayoutGraph | None, layout: LayoutGraph | None,
) -> list[Finding]: ) -> list[Finding]:
"""Trace width × copper thickness vs datasheet I_load (IPC-2221 when ΔT known).""" """Trace width × copper vs datasheet I_load/Imax/I_abs (IPC-2221 when ΔT known).
Missing datasheet current → skip. No invented I, no USB 500 mA, no INFO.
"""
if layout is None: if layout is None:
return [] return []
t_mm = layout.stackup.copper_thickness_mm if layout.stackup else None t_mm = layout.stackup.copper_thickness_mm if layout.stackup else None
@@ -137,7 +157,9 @@ def check_pcb_power_traces(
for ref, comp in sorted(graph.components.items()): for ref, comp in sorted(graph.components.items()):
if comp.component_type != ComponentType.IC: if comp.component_type != ComponentType.IC:
continue continue
i_load = _first(_specs_values(comp), _LOAD_KEYS) i_load = _datasheet_trace_current(comp)
if i_load is None:
continue
cons = _match_constraints(comp.mpn or comp.value, constraints_map) cons = _match_constraints(comp.mpn or comp.value, constraints_map)
vout = _pin_net_by_role(graph, comp, cons, _VOUT_PIN) vout = _pin_net_by_role(graph, comp, cons, _VOUT_PIN)
if not vout: if not vout:
@@ -147,25 +169,6 @@ def check_pcb_power_traces(
continue continue
seen_nets.add(key) seen_nets.add(key)
net = vout net = vout
if i_load is None or i_load <= 0:
out.append(Finding(
designator=ref,
mpn=comp.mpn or "",
aspect="layout_power",
finding=(
f"{net} has no datasheet I_load; IPC-2221 ampacity is not applied."
),
why="I_load is required; width/current are never invented.",
status="INFO",
recommendation="Add I_load to the shared library extraction, then re-run PCB review.",
source="pcb_power_thermal",
rule_id="PE-PWR-001",
evidence_status="INSUFFICIENT",
assumptions=["No default current; IPC-2221 skipped."],
net=net,
pins=[],
))
continue
w_mm, layers = _min_width_layers(layout, net) w_mm, layers = _min_width_layers(layout, net)
if w_mm is None: if w_mm is None:
continue continue
@@ -230,7 +233,10 @@ def check_pcb_power_traces(
f"{_IPC_K_EXT if ext else _IPC_K_INT}, ΔT={dt:.0f} °C, " f"{_IPC_K_EXT if ext else _IPC_K_INT}, ΔT={dt:.0f} °C, "
f"w={w_mm:g} mm, t={t_mm * 1000:g} µm)." f"w={w_mm:g} mm, t={t_mm * 1000:g} µm)."
), ),
why="IPC-2221 §6.2 I = k·ΔT^0.44·A^0.725. I_load is the datasheet/typical load, not Iout_max.", why=(
"IPC-2221 §6.2 I = k·ΔT^0.44·A^0.725. "
"Current is datasheet I_load / Imax / I_abs, not Iout_max, not USB 500 mA."
),
status="ERROR", status="ERROR",
recommendation=( recommendation=(
f"Widen {net} (and/or add copper/vias/planes) so ampacity ≥ {i_load:.3g} A, " f"Widen {net} (and/or add copper/vias/planes) so ampacity ≥ {i_load:.3g} A, "
@@ -253,7 +259,7 @@ def check_pcb_via_current(
constraints_map: dict, constraints_map: dict,
layout: LayoutGraph | None, layout: LayoutGraph | None,
) -> list[Finding]: ) -> list[Finding]:
"""Share I_load across vias; no invented via-ampacity table.""" """Share datasheet I across vias; skip if I is missing. No invented via table."""
if layout is None: if layout is None:
return [] return []
out: list[Finding] = [] out: list[Finding] = []
@@ -261,7 +267,9 @@ def check_pcb_via_current(
for ref, comp in sorted(graph.components.items()): for ref, comp in sorted(graph.components.items()):
if comp.component_type != ComponentType.IC: if comp.component_type != ComponentType.IC:
continue continue
i_load = _first(_specs_values(comp), _LOAD_KEYS) i_load = _datasheet_trace_current(comp)
if i_load is None:
continue
cons = _match_constraints(comp.mpn or comp.value, constraints_map) cons = _match_constraints(comp.mpn or comp.value, constraints_map)
net = _pin_net_by_role(graph, comp, cons, _VOUT_PIN) net = _pin_net_by_role(graph, comp, cons, _VOUT_PIN)
if not net: if not net:
@@ -270,24 +278,6 @@ def check_pcb_via_current(
if key in seen: if key in seen:
continue continue
seen.add(key) seen.add(key)
if i_load is None or i_load <= 0:
out.append(Finding(
designator=ref,
mpn=comp.mpn or "",
aspect="layout_power",
finding=(
f"{net} has no datasheet I_load; via current is not rated."
),
why="I_load is required; via ampacity is never invented.",
status="INFO",
recommendation="Add I_load to the shared library extraction, then re-run PCB review.",
source="pcb_power_thermal",
rule_id="PE-VIA-001",
evidence_status="INSUFFICIENT",
net=net,
pins=[],
))
continue
n, _drill = _via_stats(layout, net) n, _drill = _via_stats(layout, net)
if n > 0: if n > 0:
# Geometry is present; no via-ampacity table in the library — do not # Geometry is present; no via-ampacity table in the library — do not
@@ -39,7 +39,8 @@ Do not claim antenna keepout geometry is missing if keepout zones are listed.
- Interface class: USB-C CC/Rd/VBUS, Ethernet 10/100 vs GbE magnetics, \ - Interface class: USB-C CC/Rd/VBUS, Ethernet 10/100 vs GbE magnetics, \
PoE only with evidence. Deterministic certifiers already ran; explain \ PoE only with evidence. Deterministic certifiers already ran; explain \
those FACTS. Never invent PoE on a bare RJ45 or SuperSpeed on USB2 Type-C. \ those FACTS. Never invent PoE on a bare RJ45 or SuperSpeed on USB2 Type-C. \
Never invent millimetres, Z, or I. Never invent millimetres, Z, or I. Do not assume USB 500 mA — PE-PWR \
skips when the datasheet has no I_load/Imax/I_abs.
### Evidence ### Evidence
Cite numbers from "Parsed board geometry". Say insufficient evidence ONLY \ Cite numbers from "Parsed board geometry". Say insufficient evidence ONLY \
@@ -43,7 +43,7 @@ compensation, feedback divider, sense resistor).
- Dedicated interface class (USB-C CC1/CC2 Rp/Rd 5.1 kΩ VBUS/GND; Ethernet \ - Dedicated interface class (USB-C CC1/CC2 Rp/Rd 5.1 kΩ VBUS/GND; Ethernet \
10/100 vs GbE magnetics; PoE only with evidence). Deterministic certifiers \ 10/100 vs GbE magnetics; PoE only with evidence). Deterministic certifiers \
own those FACTS. Do not invent PoE on a bare RJ45, SuperSpeed on USB 2.0 \ own those FACTS. Do not invent PoE on a bare RJ45, SuperSpeed on USB 2.0 \
Type-C, or geometry/Z/I. Type-C, or geometry/Z/I. Do not assume USB 500 mA.
Then work the areas one at a time. For EACH area, don't just confirm a \ Then work the areas one at a time. For EACH area, don't just confirm a \
part is present — ask what specific failure mode would make it wrong \ part is present — ask what specific failure mode would make it wrong \
@@ -10,7 +10,7 @@ Non è un unico check SI generico. Non è il DRC KiCad. Non è “via ampacity I
Classe dichiarata o inferita (FACT/REQUIREMENT da BOM, net, footprint, sheet). Se manca evidenza: INSUFFICIENT_EVIDENCE, niente geometria/Z/I inventati. Via ≠ pad ≠ track. Classe dichiarata o inferita (FACT/REQUIREMENT da BOM, net, footprint, sheet). Se manca evidenza: INSUFFICIENT_EVIDENCE, niente geometria/Z/I inventati. Via ≠ pad ≠ track.
## Prima fetta (shipped 2.61.0) ## Prima fetta (shipped 2.61.0, skip-I 2.61.1)
| ID | Cosa certifica | | ID | Cosa certifica |
| --- | --- | | --- | --- |
@@ -27,7 +27,7 @@ Classe dichiarata o inferita (FACT/REQUIREMENT da BOM, net, footprint, sheet). S
| PE-POE-002 | Classe/tipo 802.3af/at/bt se citati; altrimenti INSUFFICIENT | | PE-POE-002 | Classe/tipo 802.3af/at/bt se citati; altrimenti INSUFFICIENT |
| PE-POE-003 | Isolamento/magnetics solo con evidenza PoE | | PE-POE-003 | Isolamento/magnetics solo con evidenza PoE |
Motore: `periscopex/interface_class_check.py` (grafo, non geometria). Schema `MODE=run` e PCB `MODE=pcb`. LIA spiega i FACT, non certifica. Motore: `periscopex/interface_class_check.py` (grafo, non geometria). Schema `MODE=run` e PCB `MODE=pcb`. LIA spiega i FACT, non certifica. Nessun I inventato (niente USB 500 mA): la corrente vs larghezza è PE-PWR, e **skip** se il datasheet non riporta I_load/Imax/I_abs.
## Dopo ## Dopo
+3 -3
View File
@@ -27,7 +27,7 @@ Vedi `motore-finding.md`. Invarianti:
3. Classi **RULE | RISK | REVIEW | INFO**. LLM = REVIEW, mai RULE. 3. Classi **RULE | RISK | REVIEW | INFO**. LLM = REVIEW, mai RULE.
4. `decisions.json` + fingerprint: rescan non ri-naga (PSEL low intenzionale, ecc.). 4. `decisions.json` + fingerprint: rescan non ri-naga (PSEL low intenzionale, ecc.).
5. `status`, `confidence`, `evidence_status` indipendenti. 5. `status`, `confidence`, `evidence_status` indipendenti.
6. Evidenza insufficiente → lo si dice, niente 1 oz / 10 °C / 50 Ω / IEC inventati. 6. Evidenza insufficiente → lo si dice, niente 1 oz / 10 °C / 50 Ω / IEC inventati. **Eccezione PE-PWR/PE-VIA:** se manca I_load/Imax/I_abs, skip silenzioso (niente INFO, niente 500 mA USB).
7. Oggetto riproducibile: facts, requirement, source, calculation, assumptions, confidence, severity, action. 7. Oggetto riproducibile: facts, requirement, source, calculation, assumptions, confidence, severity, action.
UI report (schema e layout): stessa `FindingCard` — Fact / Requirement / Inference, badge classe e provenance. UI report (schema e layout): stessa `FindingCard` — Fact / Requirement / Inference, badge classe e provenance.
@@ -58,7 +58,7 @@ Mutex vs analisi e vs placement. SSE `pcb_*`. IDs `PCB-{ref}-{001}`. KiCad `/net
| PE-SI-002 | ImpedenceFinder Zavg/min/max vs `impedance` window | | PE-SI-002 | ImpedenceFinder Zavg/min/max vs `impedance` window |
| PE-SI-003…009 | max length, spacing, ref plane, vias, return, series R — each a datasheet number | | PE-SI-003…009 | max length, spacing, ref plane, vias, return, series R — each a datasheet number |
| PE-SI-010 | HS bus measured, library has no SI FACT (not 90 Ω folklore) | | PE-SI-010 | HS bus measured, library has no SI FACT (not 90 Ω folklore) |
| PE-PWR-001 | I_load + width; IPC solo con thickness e Tjmax | | PE-PWR-001 | I_load/Imax/I_abs + width; **skip** se manca I (no 500 mA, no INFO); IPC solo con thickness e Tjmax |
| PE-DRT-001 | Vop e Vrated, Vop > Vr → RULE ERROR | | PE-DRT-001 | Vop e Vrated, Vop > Vr → RULE ERROR |
| PE-DRT-002 | 0.8 < Vop/Vr ≤ 1 → RISK MARGIN | | PE-DRT-002 | 0.8 < Vop/Vr ≤ 1 → RISK MARGIN |
| PE-DRT-003 | Vop/Vr ≤ 0.8 → PASS summary INFO | | PE-DRT-003 | Vop/Vr ≤ 0.8 → PASS summary INFO |
@@ -66,7 +66,7 @@ Mutex vs analisi e vs placement. SSE `pcb_*`. IDs `PCB-{ref}-{001}`. KiCad `/net
| PE-TIM-001/002 | RC / strap solo con numeri | | PE-TIM-001/002 | RC / strap solo con numeri |
| PE-PI-001/002 | local vs bulk; mm solo da layout_rules | | PE-PI-001/002 | local vs bulk; mm solo da layout_rules |
| PE-ESD-001 / PE-RET-001 | REVIEW unless mandatory FACT | | PE-ESD-001 / PE-RET-001 | REVIEW unless mandatory FACT |
| PE-VIA-001 | via count + I_load (INFO, no tabella inventata) | | PE-VIA-001 | via count + I datasheet (INFO, no tabella inventata); **skip** se manca I |
| PE-THM-001 | P=I_load×drop, courtyard senza pour/via | | PE-THM-001 | P=I_load×drop, courtyard senza pour/via |
| PE-THM-002 | Tj=Ta+P·θJA solo con P, θJA, area rame, conteggio via | | PE-THM-002 | Tj=Ta+P·θJA solo con P, θJA, area rame, conteggio via |
| PE-BOM-010…014 | package / pinout / V / T / I quando BOM, PCB e datasheet hanno i numeri | | PE-BOM-010…014 | package / pinout / V / T / I quando BOM, PCB e datasheet hanno i numeri |
@@ -2,6 +2,13 @@
What's new in Periscope. What's new in Periscope.
## 2.61.1 — 2026-09-21 — Skip trace-width vs current without datasheet I
PE-PWR-001 (and PE-VIA-001) run only when the datasheet reports I_load / Imax / I_abs. Missing that number is a skip — not INFO INSUFFICIENT, not a typical USB 500 mA, not Iout_max-as-load. USB-C / Ethernet / PoE class certifiers still have no current/ampacity check. No via IPC table. No FEM.
- [Fixed] PE-PWR-001 / PE-VIA-001 skip when datasheet current is absent.
- [Fixed] Imax / I_abs accepted for width; Iout_max is still not load.
## 2.61.0 — 2026-09-21 — Interface class certifiers (USB-C, Ethernet, PoE) ## 2.61.0 — 2026-09-21 — Interface class certifiers (USB-C, Ethernet, PoE)
Dedicated per-interface certifiers (deterministic + AI explanation). Not generic SI. USB-C: CC1/CC2, Rp/Rd 5.1 kΩ, VBUS/GND, SuperSpeed only if the class is USB3. Ethernet: 10/100 vs GbE; magnetics required for GbE. PoE only with PoE evidence — a bare RJ45 is N/A, never invented PoE. Missing generation/class/ohms → INSUFFICIENT. DDR3 and via IPC ampacity are out of this slice. No FEM. Dedicated per-interface certifiers (deterministic + AI explanation). Not generic SI. USB-C: CC1/CC2, Rp/Rd 5.1 kΩ, VBUS/GND, SuperSpeed only if the class is USB3. Ethernet: 10/100 vs GbE; magnetics required for GbE. PoE only with PoE evidence — a bare RJ45 is N/A, never invented PoE. Missing generation/class/ohms → INSUFFICIENT. DDR3 and via IPC ampacity are out of this slice. No FEM.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "periscope-web", "name": "periscope-web",
"version": "2.61.0", "version": "2.61.1",
"private": true, "private": true,
"scripts": { "scripts": {
"sync-version": "node scripts/sync-version.mjs", "sync-version": "node scripts/sync-version.mjs",
+13
View File
@@ -607,3 +607,16 @@ def test_does_not_use_layout_geometry():
assert "LayoutPad" not in src assert "LayoutPad" not in src
assert "LayoutSegment" not in src assert "LayoutSegment" not in src
assert "LayoutZone" not in src assert "LayoutZone" not in src
def test_usbc_vbus_does_not_invent_usb_500ma():
"""No typical USB current and no PE-PWR check without datasheet I."""
findings = check_interface_classes(_usbc_device_graph())
blob = " ".join(
" ".join(filter(None, (f.finding, f.why, f.facts, f.requirement, f.inference)))
for f in findings
).lower()
assert "500 ma" not in blob
assert "500ma" not in blob
assert "0.5 a" not in blob
assert not any(f.rule_id in {"PE-PWR-001", "PE-VIA-001"} for f in findings)
+36 -1
View File
@@ -515,9 +515,44 @@ def test_power_trace_skips_without_i_load():
graph.components["U1"].specs.values["i_load_a"] = None # type: ignore[union-attr] graph.components["U1"].specs.values["i_load_a"] = None # type: ignore[union-attr]
graph.components["U1"].specs = None graph.components["U1"].specs = None
findings = check_pcb_power_traces(graph, cmap, layout) findings = check_pcb_power_traces(graph, cmap, layout)
assert findings == []
def test_power_trace_skips_iout_max_as_load():
from backend.periscopex.pcb_power_thermal import check_pcb_power_traces
graph, cmap, layout = _ldo_graph_layout(i_load=10)
graph.components["U1"].specs.values.clear() # type: ignore[union-attr]
graph.components["U1"].specs.values["iout_max_a"] = 10.0 # type: ignore[union-attr]
graph.components["U1"].specs.values["tj_max"] = 150.0 # type: ignore[union-attr]
findings = check_pcb_power_traces(graph, cmap, layout)
assert findings == []
def test_power_trace_uses_imax_when_i_load_absent():
from backend.periscopex.pcb_power_thermal import check_pcb_power_traces
graph, cmap, layout = _ldo_graph_layout(i_load=10)
vals = graph.components["U1"].specs.values # type: ignore[union-attr]
vals.pop("i_load_a", None)
vals["i_max"] = 10.0
findings = check_pcb_power_traces(graph, cmap, layout)
assert findings assert findings
assert findings[0].rule_id == "PE-PWR-001" assert findings[0].rule_id == "PE-PWR-001"
assert findings[0].evidence_status == "INSUFFICIENT" assert findings[0].status == "ERROR"
def test_power_trace_uses_i_abs_when_i_load_absent():
from backend.periscopex.pcb_power_thermal import check_pcb_power_traces
graph, cmap, layout = _ldo_graph_layout(i_load=10)
vals = graph.components["U1"].specs.values # type: ignore[union-attr]
vals.pop("i_load_a", None)
vals["i_abs"] = 10.0
findings = check_pcb_power_traces(graph, cmap, layout)
assert findings
assert findings[0].rule_id == "PE-PWR-001"
assert findings[0].status == "ERROR"
def test_kelvin_sense_pin_on_shared_net(): def test_kelvin_sense_pin_on_shared_net():
+3 -5
View File
@@ -307,7 +307,7 @@ def test_fb1_blm_is_bead_not_dcr_resistor():
assert getattr(dcr.specs, "value_ohms", None) is None assert getattr(dcr.specs, "value_ohms", None) is None
def test_via_current_insufficient_without_i_load(): def test_via_current_skips_without_i_load():
from backend.periscopex.models import LayoutStackup from backend.periscopex.models import LayoutStackup
cons = ComponentConstraints( cons = ComponentConstraints(
@@ -339,11 +339,9 @@ def test_via_current_insufficient_without_i_load():
], ],
) )
findings = check_pcb_via_current(graph, {"LDO1": cons}, layout) findings = check_pcb_via_current(graph, {"LDO1": cons}, layout)
assert findings and findings[0].rule_id == "PE-VIA-001" assert findings == []
assert findings[0].evidence_status == "INSUFFICIENT"
pwr = check_pcb_power_traces(graph, {"LDO1": cons}, layout) pwr = check_pcb_power_traces(graph, {"LDO1": cons}, layout)
assert pwr and pwr[0].rule_id == "PE-PWR-001" assert pwr == []
assert pwr[0].evidence_status == "INSUFFICIENT"
def test_thinking_reasoning_content_echo_still_present(): def test_thinking_reasoning_content_echo_still_present():