Ship remaining PCB plan slices: BOM chain, SPOF, EMI FACT, gated Tj.

Package/pinout/ratings mismatches, SPOF as REVIEW, EMI only with a
datasheet quote, and Tj from copper+vias+θJA when every parameter exists.
Re-extract SI layout_rules from 1.12.0; ImpedenceFinder license stays UNKNOWN.
This commit is contained in:
2026-09-20 12:29:27 +02:00
parent 65a91419a2
commit 1b3529501f
20 changed files with 1337 additions and 30 deletions
+33 -10
View File
@@ -6,11 +6,14 @@ from typing import Any
from packaging.version import Version
KNOWN_KINDS = frozenset({
"decoupling_proximity", "thermal_via", "keepout", "length_match",
"impedance", "max_length", "spacing", "ref_plane", "si_via",
"layer", "series_resistor", "return_path", "si",
SI_KINDS = frozenset({
"impedance", "length_match", "max_length", "spacing",
"ref_plane", "si_via", "layer", "series_resistor", "return_path", "si",
})
EMI_KINDS = frozenset({"emi", "common_mode", "shield"})
KNOWN_KINDS = frozenset({
"decoupling_proximity", "thermal_via", "keepout",
}) | SI_KINDS | EMI_KINDS
def _num(v: Any) -> float | None:
@@ -36,25 +39,45 @@ def has_any_layout_rule(raw: object) -> bool:
return False
def has_si_layout_rule(raw: object) -> bool:
if not isinstance(raw, list):
return False
for row in raw:
if isinstance(row, dict) and str(row.get("kind") or "").strip() in SI_KINDS:
return True
return False
def needs_layout_rules_refresh(
data: dict,
*,
min_scan_version: str,
) -> bool:
"""True when layout_rules are empty and the extract predates the scan version.
"""True when this extract should be re-run against the current pintable skill.
After a successful extract at ``min_scan_version`` or newer, an empty
``layout_rules`` list means the datasheet had no guidance — do not loop.
After a successful extract at ``min_scan_version`` or newer, empty
``layout_rules`` means the datasheet had no guidance — do not loop.
From skill 1.11.0, SI kinds are first-class. An older JSON that only
has decoupling/thermal rules is stale and must be re-extracted so
ImpedenceFinder checks are not starved.
"""
if has_any_layout_rule(data.get("layout_rules")):
return False
ver = str(data.get("model_version") or "0.0.0")
if not min_scan_version or min_scan_version == "0.0.0":
return False
try:
return Version(ver) < Version(min_scan_version)
stale = Version(ver) < Version(min_scan_version)
except Exception:
return True
if not stale:
return False
try:
need_si = Version(min_scan_version) >= Version("1.11.0")
except Exception:
need_si = False
if need_si:
return not has_si_layout_rule(data.get("layout_rules"))
return not has_any_layout_rule(data.get("layout_rules"))
def validate_layout_rules(raw: list | None) -> tuple[list[dict], list[str]]: