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:
@@ -0,0 +1,133 @@
|
||||
"""EMI beyond ESD — only when a datasheet FACT names a filter.
|
||||
|
||||
No IEC 61000 invention. Common-mode / ferrite / shield from layout_rules
|
||||
notes or kind, checked against parts on the same HS connector net.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from backend.periscopex.models import (
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
DesignGraph,
|
||||
Finding,
|
||||
LayoutGraph,
|
||||
)
|
||||
from backend.periscopex.pcb_net_match import (
|
||||
normalize_kicad_hierarchy_net,
|
||||
refs_on_matched_net,
|
||||
)
|
||||
from backend.periscopex.si_check import bus_class, skip_si_net
|
||||
from backend.periscopex.validate import _match_constraints
|
||||
|
||||
_EMI_RE = re.compile(
|
||||
r"(emi|emc|common[-\s]?mode|cmc|choke|ferrite\s*bead|shield(?:ing)?|"
|
||||
r"pi\s*filter|lc\s*filter)",
|
||||
re.I,
|
||||
)
|
||||
_EMI_KINDS = frozenset({"emi", "common_mode", "shield"})
|
||||
|
||||
|
||||
def _is_filter_part(comp) -> bool:
|
||||
sub = (comp.component_subtype or "").lower()
|
||||
val = (comp.value or "").lower()
|
||||
blob = f"{sub} {val} {comp.mpn or ''}"
|
||||
if "esd" in sub or "protection" in sub:
|
||||
return False
|
||||
if "ferrite" in blob or "common" in blob or "choke" in blob or "cmc" in blob:
|
||||
return True
|
||||
if comp.component_type == ComponentType.INDUCTOR and _EMI_RE.search(blob):
|
||||
return True
|
||||
if (comp.reference or "").upper().startswith(("FB", "L")) and _EMI_RE.search(blob):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _emi_rules(cons: ComponentConstraints | None) -> list[dict]:
|
||||
if not cons:
|
||||
return []
|
||||
rows: list[dict] = []
|
||||
for rule in cons.layout_rules or []:
|
||||
if not isinstance(rule, dict):
|
||||
continue
|
||||
kind = str(rule.get("kind") or "")
|
||||
note = str(rule.get("note") or "")
|
||||
if kind in _EMI_KINDS or _EMI_RE.search(note) or _EMI_RE.search(kind):
|
||||
rows.append(rule)
|
||||
return rows
|
||||
|
||||
|
||||
def check_emi(
|
||||
graph: DesignGraph,
|
||||
constraints_map: dict[str, ComponentConstraints] | None = None,
|
||||
layout: LayoutGraph | None = None,
|
||||
) -> list[Finding]:
|
||||
"""REVIEW when a sourced EMI/filter FACT has no matching part on the net."""
|
||||
_ = layout
|
||||
cmap = constraints_map or {}
|
||||
out: list[Finding] = []
|
||||
seen: set[tuple[str, str]] = set()
|
||||
|
||||
for ref, comp in sorted(graph.components.items()):
|
||||
if comp.component_type not in (ComponentType.IC, ComponentType.CONNECTOR):
|
||||
continue
|
||||
cons = _match_constraints(comp.mpn or comp.value, cmap)
|
||||
rules = _emi_rules(cons)
|
||||
if not rules:
|
||||
continue
|
||||
for pin, net in sorted(comp.pins.items(), key=lambda x: str(x[0])):
|
||||
if not net or skip_si_net(net):
|
||||
continue
|
||||
if not bus_class(net) and comp.component_type != ComponentType.CONNECTOR:
|
||||
continue
|
||||
key = (ref, normalize_kicad_hierarchy_net(net))
|
||||
if key in seen:
|
||||
continue
|
||||
on_net = refs_on_matched_net(graph, net)
|
||||
if any(
|
||||
r in graph.components and _is_filter_part(graph.components[r])
|
||||
for r in on_net
|
||||
):
|
||||
continue
|
||||
quote = next(
|
||||
(str(r.get("note") or "") for r in rules if r.get("note")),
|
||||
"",
|
||||
)
|
||||
page = next(
|
||||
(r.get("source_page") for r in rules if r.get("source_page")),
|
||||
None,
|
||||
)
|
||||
rec = (
|
||||
f"Add the datasheet EMI/filter part on '{net}' "
|
||||
f"(common-mode / ferrite / shield as quoted). Not IEC 61000."
|
||||
)
|
||||
seen.add(key)
|
||||
out.append(Finding(
|
||||
designator=ref,
|
||||
mpn=comp.mpn or "",
|
||||
aspect="emi",
|
||||
finding=(
|
||||
f"{net} has a datasheet EMI/filter FACT and no ferrite/"
|
||||
f"common-mode/choke part on the net."
|
||||
),
|
||||
facts=(
|
||||
f"net={net}; pin={pin}; filter_parts=none; "
|
||||
f"quote={quote!r}; page={page}."
|
||||
),
|
||||
requirement=quote or "Datasheet layout_rules EMI/filter note.",
|
||||
inference="Filter missing vs sourced note — REVIEW, not IEC.",
|
||||
why="EMI only when the library quotes a filter; ESD is PE-ESD-001.",
|
||||
status="WARNING",
|
||||
recommendation=rec,
|
||||
action=rec,
|
||||
source="emi_check",
|
||||
source_page=int(page) if isinstance(page, int) else None,
|
||||
source_quote=quote,
|
||||
rule_id="PE-EMI-001",
|
||||
evidence_status="SUFFICIENT",
|
||||
net=net,
|
||||
pins=[str(pin)],
|
||||
))
|
||||
return out
|
||||
Reference in New Issue
Block a user