Default to DeepSeek V4.1 Flash, show API cost in USD, and re-analyze after replacing BOM and netlist.

V4.1 is natively multimodal so every stage uses deepseek-flash; pricing and the UI now surface dollars instead of empty credits. KiCad netlists and neighborhood fingerprints land in the same cut so a second run can keep the project and skip unchanged ICs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-10 20:55:35 +02:00
co-authored by Cursor
parent d454cf75af
commit 61f85f519b
29 changed files with 1174 additions and 118 deletions
+91
View File
@@ -0,0 +1,91 @@
"""Reopen a finished project and replace BOM/netlist without deleting it."""
from __future__ import annotations
from fastapi.testclient import TestClient
from backend.services import projects as proj_svc
from backend.services.storage import LocalStorageBackend
_BOM_V1 = (
b"Reference,Value,Manufacturer Part Number\n"
b"U1,MCU,STM32F103C8T6\n"
)
_BOM_V2 = (
b"Reference,Value,Manufacturer Part Number\n"
b"U1,MCU,STM32F103C8T6\n"
b"R1,10k,RC0603FR-0710KL\n"
)
_NETLIST = b"""*PADS-PCB*
*PART*
U1 LQFP48
*NET*
*SIGNAL* GND
U1.1
*END*
"""
def _client(tmp_path) -> TestClient:
from backend.main import app
app.state.storage = LocalStorageBackend(tmp_path)
return TestClient(app)
def test_reopen_then_replace_bom_and_netlist(tmp_path):
client = _client(tmp_path)
meta = client.post("/api/projects", json={"name": "board"}).json()
pid = meta["id"]
resp = client.post(
f"/api/projects/{pid}/upload/bom",
files={"file": ("bom.csv", _BOM_V1, "text/csv")},
)
assert resp.status_code == 200, resp.text
resp = client.post(
f"/api/projects/{pid}/upload/netlist",
files={"file": ("netlist.asc", _NETLIST, "text/plain")},
)
assert resp.status_code == 200, resp.text
storage = client.app.state.storage
proj_svc.update_project(
storage, "local", pid,
status="complete",
total_cost_usd=1.23,
summary={"ERROR": 1, "WARNING": 0, "INFO": 0, "total": 1},
)
prefix = f"users/local/projects/{pid}"
storage.write_json(f"{prefix}/report.json", {"findings": []})
storage.write_text(f"{prefix}/api_logs.jsonl", '{"cost_usd": 1.23}\n')
reopen = client.post(f"/api/projects/{pid}/reopen")
assert reopen.status_code == 200, reopen.text
body = reopen.json()
assert body["status"] == "draft"
assert body["id"] == pid
assert body["has_bom"] is True
assert body["has_netlist"] is True
assert body["total_cost_usd"] == 1.23
assert not storage.exists(f"{prefix}/report.json")
resp = client.post(
f"/api/projects/{pid}/upload/bom",
files={"file": ("bom.csv", _BOM_V2, "text/csv")},
)
assert resp.status_code == 200, resp.text
assert resp.json()["components"] == 2
resp = client.post(
f"/api/projects/{pid}/upload/netlist",
files={"file": ("netlist.asc", _NETLIST, "text/plain")},
)
assert resp.status_code == 200, resp.text
fresh = client.get(f"/api/projects/{pid}").json()
assert fresh["id"] == pid
assert fresh["status"] == "draft"
assert fresh["has_bom"] is True
assert fresh["has_netlist"] is True
assert "RC0603FR-0710KL" in (fresh.get("component_mpns") or {}).get("passive", [])