Register library components from a datasheet without an exam (2.63.1).

POST /api/library/datasheets now writes an IC inbox card or a
passive/discrete model. Empty pintables never enter library/extracted.
AF Board+AI runs after unchanged run_pcb_checks (PE-SI / HF line stay).
This commit is contained in:
2026-09-21 22:33:30 +02:00
parent 4df04df5d4
commit 6d9d21a023
18 changed files with 451 additions and 51 deletions
+76 -2
View File
@@ -137,19 +137,33 @@ def test_library_import_pdf_without_project(tmp_path):
before = client.get("/api/projects").json()
resp = client.post(
"/api/library/datasheets",
data={"mpn": "CH340E"},
data={"mpn": "CH340E", "kind": "ic"},
files={"file": ("ch.pdf", PDF, "application/pdf")},
)
assert resp.status_code == 200
body = resp.json()
assert body["mpn"] == "CH340E"
assert body["has_extraction"] is False
assert body["component_type"] == "ic"
assert body["status"] == "needs_pintable"
assert client.get("/api/projects").json() == before
catalog = client.get("/api/library").json()
assert any(d["mpn"] == "CH340E" for d in catalog["datasheets"])
assert any(d["mpn"] == "CH340E" and d.get("has_component") for d in catalog["datasheets"])
ics = [c for c in catalog["ics"] if c["mpn"] == "CH340E"]
assert len(ics) == 1
assert ics[0]["pin_count"] == 0
assert ics[0]["library_status"] == "needs_pintable"
pdf = client.get("/api/library/datasheet/CH340E")
assert pdf.status_code == 200
assert pdf.content.startswith(b"%PDF-")
card = client.get("/api/library/components/ic/CH340E")
assert card.status_code == 200
assert card.json()["status"] == "needs_pintable"
from backend.main import app
from backend.services.library import library_has_extraction
assert library_has_extraction(app.state.storage, "CH340E") is None
assert not app.state.storage.exists("library/extracted/CH340E.json")
def test_library_import_rejects_non_pdf(tmp_path):
@@ -210,3 +224,63 @@ def test_library_put_rejects_empty_pintable(tmp_path):
)
assert resp.status_code == 400
assert client.get("/api/library/components/ic/FAKEIC").status_code == 404
def test_library_import_ic_put_pintable_promotes_out_of_inbox(tmp_path):
client = _client(tmp_path)
assert client.post(
"/api/library/datasheets",
data={"mpn": "CH340E", "kind": "ic"},
files={"file": ("ch.pdf", PDF, "application/pdf")},
).status_code == 200
payload = {
"mpn": "CH340E",
"component_subtype": "ic.interface.usb_uart_bridge",
"pintable": [
{"number": "1", "name": "VCC"},
{"number": "2", "name": "GND"},
],
"absolute_maximum_ratings": [],
"rules": [],
}
saved = client.put("/api/library/components/ic/CH340E", json=payload)
assert saved.status_code == 200
from backend.main import app
assert app.state.storage.exists("library/extracted/CH340E.json")
assert not app.state.storage.exists("library/inbox/CH340E.json")
extracted = app.state.storage.read_json("library/extracted/CH340E.json")
assert "status" not in extracted
assert extracted["pintable"][0]["name"] == "VCC"
cat = client.get("/api/library").json()
ic = next(c for c in cat["ics"] if c["mpn"] == "CH340E")
assert ic["pin_count"] == 2
assert ic["library_status"] == "extracted"
def test_library_import_passive_without_exam(tmp_path):
client = _client(tmp_path)
resp = client.post(
"/api/library/datasheets",
data={"mpn": "CL10B474KA8NNNC", "kind": "passive_part"},
files={"file": ("c.pdf", PDF, "application/pdf")},
)
assert resp.status_code == 200
assert resp.json()["component_type"] == "passive_part"
assert client.get("/api/projects").json() == []
cat = client.get("/api/library").json()
assert any(p["mpn"] == "CL10B474KA8NNNC" for p in cat["passive_parts"])
got = client.get("/api/library/components/passive_part/CL10B474KA8NNNC")
assert got.status_code == 200
assert got.json()["specs"]["specs_type"] == "passive"
def test_library_import_rejects_unknown_kind(tmp_path):
client = _client(tmp_path)
resp = client.post(
"/api/library/datasheets",
data={"mpn": "CH340E", "kind": "project"},
files={"file": ("ch.pdf", PDF, "application/pdf")},
)
assert resp.status_code == 400
assert client.get("/api/library").json()["ics"] == []