Resolve passives from catalog, BOM value, and Murata LQW18AN without an LLM.

Skip the model (and the credit gate) when LCSC/DigiKey already list C/V/Z, when the BOM value is a single R/C/L/FB token, or when the MPN is an LQW18AN inductor. Ferrite beads keep impedance, not invented henries; value-only specs stay out of the shared library.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-28 20:25:34 +02:00
co-authored by Cursor
parent 2113e3975e
commit edbb47a08b
14 changed files with 596 additions and 88 deletions
+88
View File
@@ -0,0 +1,88 @@
import asyncio
from pathlib import Path
import pytest
from backend.services.passive_from_value import (
is_placeholder_value,
specs_from_bom_value,
)
def test_capacitor_picofarads():
model = specs_from_bom_value("18pF", "18pF", "C")
assert model is not None
assert model.specs.specs_type == "capacitor"
assert abs(model.specs.value_farads - 18e-12) < 1e-18
def test_resistor_kilo_and_euro():
k = specs_from_bom_value("4.7k", "4.7k", "R")
assert k is not None
assert abs(k.specs.value_ohms - 4700) < 1e-6
euro = specs_from_bom_value("4k7", "4k7", "R")
assert euro is not None
assert abs(euro.specs.value_ohms - 4700) < 1e-6
def test_inductor_uh():
model = specs_from_bom_value("10uH", "10uH", "L")
assert model is not None
assert abs(model.specs.value_henries - 10e-6) < 1e-12
def test_ferrite_impedance_at_freq():
model = specs_from_bom_value("600R@100MHz", "600R@100MHz", "FB")
assert model is not None
assert model.specs.component_subtype == "passive.ferrite_bead"
assert model.specs.impedance_ohm == 600
assert model.specs.value_henries is None
assert "100MHz" in model.specs.value_formatted
def test_placeholders_skip():
for raw in ("DNP", "NC", "JUMPER", "TBD", "-"):
assert is_placeholder_value(raw)
assert specs_from_bom_value(raw, raw, "R") is None
def test_ambiguous_string_returns_none():
assert specs_from_bom_value("mystery", "do not stuff", "R") is None
def test_resolve_from_value_skips_llm_when_parseable(monkeypatch):
from backend.services.extraction import resolve_from_value
async def boom(*_a, **_k):
raise AssertionError("LLM must not run")
monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom)
tax = Path(__file__).resolve().parents[1] / "taxonomy"
model = asyncio.run(
resolve_from_value(
mpn="18pF",
value="18pF",
ref_prefix="C",
taxonomy_dir=tax,
)
)
assert abs(model.specs.value_farads - 18e-12) < 1e-18
def test_resolve_from_value_placeholder_no_llm(monkeypatch):
from backend.services.extraction import resolve_from_value
async def boom(*_a, **_k):
raise AssertionError("LLM must not run")
monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom)
tax = Path(__file__).resolve().parents[1] / "taxonomy"
with pytest.raises(ValueError, match="Placeholder"):
asyncio.run(
resolve_from_value(
mpn="DNP",
value="DNP",
ref_prefix="R",
taxonomy_dir=tax,
)
)