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.
99 lines
3.4 KiB
Python
99 lines
3.4 KiB
Python
"""Lifecycle from distributor payload.
|
|
|
|
Favor: DigiKey Obsolete → PE-LF-001 WARNING and uses ProductSubstitutions;
|
|
NRND → PE-LF-002 INFO; explicit RoHS Non-Compliant → PE-LF-003.
|
|
Against: Active is silent; RoHS Not Applicable is not a fail; missing
|
|
catalog row is silent; no substitution key means no invented replacement.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from backend.periscopex.lifecycle import (
|
|
check_lifecycle,
|
|
parse_distributor_product,
|
|
)
|
|
from backend.periscopex.models import Component, ComponentType, DesignGraph, Net, NetType, PinConnection
|
|
|
|
|
|
def _graph(mpn="PARTX"):
|
|
u = Component(
|
|
reference="U1", value="", footprint="",
|
|
component_type=ComponentType.IC, mpn=mpn,
|
|
pins={"1": "VDD"},
|
|
)
|
|
return DesignGraph(
|
|
components={"U1": u},
|
|
nets={"VDD": Net(name="VDD", net_type=NetType.POWER, pins=[
|
|
PinConnection(component_ref="U1", pin_number="1"),
|
|
])},
|
|
)
|
|
|
|
|
|
def test_obsolete_is_warning_and_uses_distributor_replacement():
|
|
rec = parse_distributor_product("ABC", {
|
|
"ManufacturerProductNumber": "ABC",
|
|
"ProductStatus": "Obsolete",
|
|
"RoHSStatus": "RoHS3 Compliant",
|
|
"QuantityAvailable": 12,
|
|
"ManufacturerLeadWeeks": "8",
|
|
"ProductSubstitutions": [{"ManufacturerProductNumber": "ABC-B"}],
|
|
})
|
|
assert rec.lifecycle == "eol"
|
|
assert rec.rohs_compliant is True
|
|
assert rec.stock == 12
|
|
assert rec.replacement == "ABC-B"
|
|
findings = check_lifecycle(_graph("ABC"), {"ABC": rec})
|
|
assert len(findings) == 1
|
|
assert findings[0].rule_id == "PE-LF-001"
|
|
assert findings[0].status == "WARNING"
|
|
assert findings[0].source == "lifecycle_check"
|
|
assert "ABC-B" in (findings[0].recommendation or "")
|
|
|
|
|
|
def test_nrnd_is_info():
|
|
rec = parse_distributor_product("N1", {"ProductStatus": "Not For New Designs"})
|
|
assert rec.lifecycle == "nrnd"
|
|
findings = check_lifecycle(_graph("N1"), {"N1": rec})
|
|
assert len(findings) == 1
|
|
assert findings[0].rule_id == "PE-LF-002"
|
|
assert findings[0].status == "INFO"
|
|
|
|
|
|
def test_explicit_rohs_non_compliant_is_warning():
|
|
rec = parse_distributor_product("R1", {
|
|
"ProductStatus": "Active",
|
|
"RoHSStatus": "Non-Compliant",
|
|
})
|
|
assert rec.rohs_compliant is False
|
|
findings = check_lifecycle(_graph("R1"), {"R1": rec})
|
|
assert len(findings) == 1
|
|
assert findings[0].rule_id == "PE-LF-003"
|
|
assert findings[0].status == "WARNING"
|
|
|
|
|
|
def test_active_is_silent():
|
|
rec = parse_distributor_product("A1", {"ProductStatus": "Active"})
|
|
assert rec.lifecycle == "active"
|
|
assert check_lifecycle(_graph("A1"), {"A1": rec}) == []
|
|
|
|
|
|
def test_rohs_not_applicable_is_not_a_fail():
|
|
rec = parse_distributor_product("R2", {
|
|
"ProductStatus": "Active",
|
|
"RoHSStatus": "Not Applicable",
|
|
})
|
|
assert rec.rohs_compliant is None
|
|
assert check_lifecycle(_graph("R2"), {"R2": rec}) == []
|
|
|
|
|
|
def test_missing_catalog_and_missing_substitute_are_not_guessed():
|
|
assert check_lifecycle(_graph("NOPE"), {}) == []
|
|
rec = parse_distributor_product("EOLX", {"ProductStatus": "Discontinued"})
|
|
assert rec.lifecycle == "eol"
|
|
assert rec.replacement is None
|
|
findings = check_lifecycle(_graph("EOLX"), {"EOLX": rec})
|
|
assert len(findings) == 1
|
|
assert findings[0].rule_id == "PE-LF-001"
|
|
assert "ABC-B" not in (findings[0].recommendation or "")
|
|
assert rec.replacement is None
|