Fill ferrite bead impedance from the MPN datasheet PDF.

graph_build searches project uploads and library blob refs, persists recovered Z, and still loads when the PDF has no impedance. Ohms are not written to henries.
This commit is contained in:
2026-09-21 00:29:12 +02:00
parent a2d5900b64
commit 72bcab50a1
8 changed files with 210 additions and 15 deletions
+106
View File
@@ -147,3 +147,109 @@ def test_bead_mpn_datasheet_without_z_does_not_invent():
assert filled.impedance_ohm is None
model = ComponentModel(mpn="BLM21PG121SN1D", specs=filled)
assert model.specs.impedance_ohm is None
def _write_text_pdf(path: Path, text: str) -> None:
"""Write a one-page PDF pypdf can extract without PyMuPDF."""
escaped = (
text.replace("\\", "\\\\").replace("(", "\\(").replace(")", "\\)")
)
lines = escaped.split("\n")
ops = ["BT /F1 12 Tf 72 720 Td"]
for i, line in enumerate(lines):
if i:
ops.append("0 -16 Td")
ops.append(f"({line}) Tj")
ops.append("ET")
stream = "\n".join(ops).encode("latin-1", "replace")
objects = [
b"1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n",
b"2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n",
(
b"3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] "
b"/Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj\n"
),
b"4 0 obj << /Length %d >> stream\n" % len(stream) + stream + b"\nendstream endobj\n",
b"5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj\n",
]
header = b"%PDF-1.4\n"
body = header
offsets = [0]
for obj in objects:
offsets.append(len(body))
body += obj
xref_pos = len(body)
xref = [b"xref\n0 6\n0000000000 65535 f \n"]
for off in offsets[1:]:
xref.append(f"{off:010d} 00000 n \n".encode())
trailer = (
b"trailer << /Root 1 0 R /Size 6 >>\nstartxref\n"
+ str(xref_pos).encode()
+ b"\n%%EOF\n"
)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(body + b"".join(xref) + trailer)
_BEAD_MODEL = {
"mpn": "BLM21PG121SN1D",
"specs": {
"specs_type": "inductor",
"component_subtype": "passive.ferrite_bead",
"value_formatted": "FB",
"current_rating_a": "3A",
"dcr_ohms": 0.03,
},
}
def test_bead_mpn_datasheet_pdf_fills_z(tmp_path: Path):
models = tmp_path / "models"
models.mkdir()
(models / "BLM21PG121SN1D.json").write_text(json.dumps(_BEAD_MODEL) + "\n")
_write_text_pdf(
tmp_path / "uploads" / "datasheets" / "BLM21PG121SN1D.pdf",
"BLM21PG121SN1D\nImpedance (at 100MHz / 20C) 120 ohm\nRated current 3A",
)
loaded = _load_component_models(models)
assert loaded["BLM21PG121SN1D"].impedance_ohm == 120.0
assert loaded["BLM21PG121SN1D"].value_henries is None
persisted = json.loads((models / "BLM21PG121SN1D.json").read_text())
assert persisted["specs"]["impedance_ohm"] == 120.0
assert persisted["specs"]["value_henries"] is None
assert "100" in (persisted["specs"].get("value_formatted") or "")
def test_bead_mpn_datasheet_pdf_without_z_does_not_invent(tmp_path: Path):
models = tmp_path / "models"
models.mkdir()
(models / "BLM21PG121SN1D.json").write_text(json.dumps(_BEAD_MODEL) + "\n")
_write_text_pdf(
tmp_path / "uploads" / "datasheets" / "BLM21PG121SN1D.pdf",
"BLM21PG121SN1D\nRated current 3 A\nDCR 0.03 ohm max\nPackage 0805",
)
loaded = _load_component_models(models)
assert "BLM21PG121SN1D" in loaded
assert loaded["BLM21PG121SN1D"].impedance_ohm is None
assert loaded["BLM21PG121SN1D"].value_henries is None
def test_bead_mpn_library_blob_fills_z(tmp_path: Path):
models = tmp_path / "proj" / "models"
models.mkdir(parents=True)
(models / "BLM21PG121SN1D.json").write_text(json.dumps(_BEAD_MODEL) + "\n")
blob = tmp_path / "library" / "datasheets" / "blobs" / "deadbeef.pdf"
_write_text_pdf(
blob,
"Impedance (at 100MHz / 20C) 120 ohm\nRated current 3A",
)
ref = tmp_path / "library" / "datasheets" / "refs" / "BLM21PG121SN1D.json"
ref.parent.mkdir(parents=True, exist_ok=True)
ref.write_text(json.dumps({
"hash": "deadbeef",
"blob_key": "library/datasheets/blobs/deadbeef.pdf",
"mpn": "BLM21PG121SN1D",
}) + "\n")
loaded = _load_component_models(models, extra_pdf_dirs=[tmp_path])
assert loaded["BLM21PG121SN1D"].impedance_ohm == 120.0
assert loaded["BLM21PG121SN1D"].value_henries is None