Add lifecycle, errata, internal-features, and layout_rules checks.
Cache distributor EOL/NRND/RoHS without inventing replacements, flag catalogued errata pull-ups only, and store layout_rules with a closed kind enum so millimetres stay null unless the datasheet stated a number. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -125,6 +125,20 @@ PINTABLE_TOOL = {
|
||||
"required": ["parameter", "unit", "source_page"],
|
||||
},
|
||||
},
|
||||
"internal_features": {
|
||||
"type": "object",
|
||||
"description": "Optional block-diagram extras. Omit or empty if not shown.",
|
||||
"properties": {
|
||||
"esd_clamp_pins": {"type": "array", "items": {"type": "string"}},
|
||||
"pullup_pins": {"type": "array", "items": {"type": "string"}},
|
||||
"analog_switch": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
"layout_rules": {
|
||||
"type": "array",
|
||||
"description": "Optional PCB layout constraints from typical-application pages. kind must be decoupling_proximity, thermal_via, or keepout. max_distance_mm only if the PDF states a number.",
|
||||
"items": {"type": "object"},
|
||||
},
|
||||
},
|
||||
"required": ["component_subtype", "component_subtype_description", "package_info", "pintable"],
|
||||
},
|
||||
@@ -351,6 +365,25 @@ def _coerce_abs_max(raw: object) -> list[dict]:
|
||||
return out
|
||||
|
||||
|
||||
def _coerce_layout_rules(raw: object) -> list[dict]:
|
||||
from backend.pinscopex.layout_rules import validate_layout_rules
|
||||
rows, _errors = validate_layout_rules(raw if isinstance(raw, list) else [])
|
||||
return rows
|
||||
|
||||
|
||||
def _coerce_internal_features(raw: object):
|
||||
from backend.pinscopex.models import InternalFeatures
|
||||
if not isinstance(raw, dict):
|
||||
return None
|
||||
try:
|
||||
feat = InternalFeatures.model_validate(raw)
|
||||
except Exception:
|
||||
return None
|
||||
if not feat.esd_clamp_pins and not feat.pullup_pins and not feat.analog_switch:
|
||||
return None
|
||||
return feat
|
||||
|
||||
|
||||
_GENERATE_SPECS_TOOL = {
|
||||
"name": "save_specs_schema",
|
||||
"description": "Save the standardized parameter schema for a component type.",
|
||||
@@ -623,6 +656,8 @@ async def extract_pintable(
|
||||
result.get("absolute_maximum_ratings") or [],
|
||||
),
|
||||
rules=[],
|
||||
internal_features=_coerce_internal_features(result.get("internal_features")),
|
||||
layout_rules=_coerce_layout_rules(result.get("layout_rules")),
|
||||
)
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -35,6 +35,8 @@ _PAGE_KEYWORDS = re.compile(
|
||||
r"|power\s+supply|thermal\s+(resistance|shutdown|pad)|ESD\s+(rating|tolerance)"
|
||||
r"|decoupling|bypass\s+capacitor|typical\s+application"
|
||||
r"|application\s+(circuit|schematic|information|note)|reference\s+design"
|
||||
r"|block\s+diagram|functional\s+block|internal\s+block"
|
||||
r"|pcb\s+layout|layout\s+consideration|thermal\s+via"
|
||||
r"|ordering\s+information|device\s+information",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
@@ -57,6 +57,9 @@ from backend.pinscopex.thermal_check import check_thermal
|
||||
from backend.pinscopex.power_margin_check import check_power_margin
|
||||
from backend.pinscopex.sequencing_check import check_power_sequencing
|
||||
from backend.pinscopex.dnp_check import check_dnp_enables
|
||||
from backend.pinscopex.lifecycle import check_lifecycle, load_lifecycle_dir
|
||||
from backend.pinscopex.errata_check import check_errata
|
||||
from backend.pinscopex.internal_features_check import check_internal_features
|
||||
|
||||
TRACE_VERSION = 1
|
||||
|
||||
@@ -67,7 +70,8 @@ def _is_deterministic(f: Finding) -> bool:
|
||||
|
||||
|
||||
def _run_deterministic_checks(
|
||||
graph: DesignGraph, constraints_map: dict
|
||||
graph: DesignGraph, constraints_map: dict,
|
||||
lifecycle_map: dict | None = None,
|
||||
) -> list[Finding]:
|
||||
"""Run the deterministic graph checks, fail-soft per check — a check bug
|
||||
can never break the review or the report."""
|
||||
@@ -87,6 +91,9 @@ def _run_deterministic_checks(
|
||||
("power_margin_check", lambda: check_power_margin(graph, constraints_map)),
|
||||
("sequencing_check", lambda: check_power_sequencing(graph, constraints_map)),
|
||||
("dnp_check", lambda: check_dnp_enables(graph, constraints_map)),
|
||||
("lifecycle_check", lambda: check_lifecycle(graph, lifecycle_map)),
|
||||
("errata_check", lambda: check_errata(graph, constraints_map)),
|
||||
("internal_features_check", lambda: check_internal_features(graph, constraints_map)),
|
||||
):
|
||||
try:
|
||||
out.extend(fn())
|
||||
@@ -693,10 +700,17 @@ async def validate_design_async(
|
||||
graph = DesignGraph.model_validate(raw)
|
||||
datasheets = _load_datasheets(datasheets_dir)
|
||||
constraints_map = _build_constraints_map(datasheets)
|
||||
|
||||
# Deterministic graph checks (pin-mux feasibility, LED current). Pure
|
||||
# functions of the graph; fail-soft. Seeded into all_findings below.
|
||||
deterministic_findings = _run_deterministic_checks(graph, constraints_map)
|
||||
lifecycle_map = {}
|
||||
for cand in (
|
||||
Path(datasheets_dir).parent / "lifecycle",
|
||||
Path(datasheets_dir) / "lifecycle",
|
||||
):
|
||||
loaded = load_lifecycle_dir(cand)
|
||||
if loaded:
|
||||
lifecycle_map.update(loaded)
|
||||
deterministic_findings = _run_deterministic_checks(
|
||||
graph, constraints_map, lifecycle_map,
|
||||
)
|
||||
|
||||
pdf_dir_path = Path(pdf_dir)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user