Ship DeepSeek roadmap P0-5/P2 integrations.

Smoke simple_project offline, reviewer shortest_path, library promotion
gate, PDF drop logs, and per-stage cache hit-rate helper — without
touching Layout placement packing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-12 15:48:39 +02:00
co-authored by Cursor
parent 2405093bbb
commit 30aaf55de6
11 changed files with 503 additions and 3 deletions
+41
View File
@@ -0,0 +1,41 @@
"""Shared-library promotion gates for extracted IC JSON."""
from __future__ import annotations
import hashlib
import json
from typing import Any
def pintable_checksum(pintable: list[Any]) -> str:
"""Stable hash of pin number+name pairs (order-independent)."""
rows: list[tuple[str, str]] = []
for pin in pintable or []:
if isinstance(pin, dict):
num = str(pin.get("number") or "").strip()
name = str(pin.get("name") or "").strip()
else:
num = str(getattr(pin, "number", "") or "").strip()
name = str(getattr(pin, "name", "") or "").strip()
if num:
rows.append((num, name))
payload = json.dumps(sorted(rows), separators=(",", ":"))
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
def should_promote_extraction(data: dict) -> tuple[bool, str]:
"""Return (ok, reason). Reject empty / tiny pintables from shared library."""
pins = data.get("pintable") or []
if not isinstance(pins, list) or len(pins) == 0:
return False, "empty pintable"
if len(pins) < 2:
return False, "pintable has fewer than 2 pins"
# Require at least one named pin so a number-only stub cannot poison the library.
named = 0
for pin in pins:
name = pin.get("name") if isinstance(pin, dict) else getattr(pin, "name", None)
if name and str(name).strip() and str(name).strip() != "~":
named += 1
if named == 0:
return False, "pintable has no named pins"
return True, pintable_checksum(pins)
+6
View File
@@ -35,6 +35,12 @@ def extract_pdf_document_text(pdf_path: Path | str, *, max_chars: int) -> str:
parts.append(f"--- page {i} ---\n{body}")
blob = "\n\n".join(parts)
if len(blob) > max_chars:
# Count how many page markers survive the cut for observability.
kept = blob[:max_chars].count("--- page ")
log.info(
"PDF text truncated: %s full=%d chars cap=%d kept_pages≈%d/%d",
path.name, len(blob), max_chars, kept, n,
)
blob = blob[:max_chars] + "\n\n[truncated: remaining pages omitted]"
return blob
+142
View File
@@ -256,6 +256,104 @@ def get_net_for_pin(
return f"Pin {pin}{pin_name} on {designator} -> {net_name} [{net.net_type.value}{voltage_str}]"
def shortest_path(
graph: DesignGraph,
constraints_map: ConstraintsMap,
designator_a: str,
pin_a: str,
designator_b: str,
pin_b: str,
*,
max_hops: int = 12,
) -> str:
"""BFS through the bipartite graph from A.pin to B.pin.
Hops alternate component→net→component. Returns the hop list or a
clear miss message. Caps depth so the reviewer cannot explode memory
on dense power nets.
"""
a = graph.components.get(designator_a)
b = graph.components.get(designator_b)
if not a:
return f"Component '{designator_a}' not found."
if not b:
return f"Component '{designator_b}' not found."
net_a = a.pins.get(str(pin_a))
net_b = b.pins.get(str(pin_b))
if not net_a:
return f"Pin {pin_a} on {designator_a} is not connected in the netlist."
if not net_b:
return f"Pin {pin_b} on {designator_b} is not connected in the netlist."
if designator_a == designator_b and str(pin_a) == str(pin_b):
return f"Same endpoint: {designator_a}.{pin_a} on {net_a}."
if net_a == net_b:
return (
f"Direct (same net): {designator_a}.{pin_a} —[{net_a}]— "
f"{designator_b}.{pin_b}"
)
# BFS on component nodes; edges are nets shared between components.
from collections import deque
start = designator_a
goal = designator_b
queue: deque[str] = deque([start])
# prev[ref] = (previous_ref, via_net)
prev: dict[str, tuple[str, str] | None] = {start: None}
hops = 0
found = False
while queue and hops < max_hops:
hops += 1
for _ in range(len(queue)):
cur = queue.popleft()
for net_name, others in graph.neighbors(cur).items():
for other in others:
if other in prev:
continue
prev[other] = (cur, net_name)
if other == goal:
found = True
queue.clear()
break
queue.append(other)
if found:
break
if found:
break
if not found or goal not in prev:
return (
f"No path within {max_hops} hops from "
f"{designator_a}.{pin_a} ({net_a}) to "
f"{designator_b}.{pin_b} ({net_b})."
)
# Reconstruct component chain, then decorate endpoints with pins.
chain_refs: list[str] = []
via_nets: list[str] = []
node = goal
while node != start:
chain_refs.append(node)
parent, via = prev[node] # type: ignore[misc]
via_nets.append(via)
node = parent
chain_refs.append(start)
chain_refs.reverse()
via_nets.reverse()
parts: list[str] = [f"{designator_a}.{pin_a}"]
for i, via in enumerate(via_nets):
nxt = chain_refs[i + 1]
if nxt == designator_b:
parts.append(f"—[{via}]— {designator_b}.{pin_b}")
else:
parts.append(f"—[{via}]— {nxt}")
return f"Path ({len(via_nets)} hop(s)): " + " ".join(parts)
def get_pintable(
graph: DesignGraph,
constraints_map: ConstraintsMap,
@@ -590,6 +688,38 @@ GET_NET_FOR_PIN_SCHEMA = {
},
}
SHORTEST_PATH_SCHEMA = {
"name": "shortest_path",
"description": (
"Find the shortest hop path through the netlist between two pins "
"(component.pin → nets → components). Use to verify whether two "
"pins share a rail path, or how a signal reaches another IC, "
"instead of guessing from neighborhood context."
),
"input_schema": {
"type": "object",
"properties": {
"designator_a": {
"type": "string",
"description": "Start component reference, e.g. 'U1'",
},
"pin_a": {
"type": "string",
"description": "Start pin number, e.g. '12'",
},
"designator_b": {
"type": "string",
"description": "End component reference, e.g. 'U3'",
},
"pin_b": {
"type": "string",
"description": "End pin number, e.g. '5'",
},
},
"required": ["designator_a", "pin_a", "designator_b", "pin_b"],
},
}
GET_PINTABLE_SCHEMA = {
"name": "get_pintable",
"description": (
@@ -727,6 +857,7 @@ GET_DATASHEET_EXCERPT_SCHEMA = {
GRAPH_TOOLS = [
FIND_CONNECTED_COMPONENTS_SCHEMA,
GET_NET_FOR_PIN_SCHEMA,
SHORTEST_PATH_SCHEMA,
GET_PINTABLE_SCHEMA,
GET_DATASHEET_EXCERPT_SCHEMA,
]
@@ -770,6 +901,17 @@ def execute_tool(
),
None,
)
if tool_name == "shortest_path":
return (
shortest_path(
graph, constraints_map,
tool_input["designator_a"],
tool_input["pin_a"],
tool_input["designator_b"],
tool_input["pin_b"],
),
None,
)
if tool_name == "get_pintable":
return (
get_pintable(
+24
View File
@@ -104,3 +104,27 @@ class ApiLogger:
key = f"{project_prefix(user_id, project_id)}/api_logs.jsonl"
storage.write_text(key, text)
def cache_stats_by_stage(entries: list[dict]) -> dict[str, dict]:
"""Roll up prompt-cache hit rate per pipeline stage.
Returns ``{stage: {calls, input_tokens, cache_read_tokens, hit_ratio}}``.
``hit_ratio`` is cache_read / input when input > 0, else 0.
"""
out: dict[str, dict] = {}
for e in entries:
stage = str(e.get("stage") or "unknown")
bucket = out.setdefault(
stage,
{"calls": 0, "input_tokens": 0, "cache_read_tokens": 0, "hit_ratio": 0.0},
)
bucket["calls"] += 1
bucket["input_tokens"] += int(e.get("input_tokens") or 0)
bucket["cache_read_tokens"] += int(e.get("cache_read_input_tokens") or 0)
for bucket in out.values():
inp = bucket["input_tokens"]
bucket["hit_ratio"] = (
round(bucket["cache_read_tokens"] / inp, 4) if inp else 0.0
)
return out
+10
View File
@@ -133,6 +133,16 @@ def render_pdf_page_jpegs(
return []
try:
total = len(doc)
omitted = [i + 1 for i in range(total) if i not in set(page_indices)]
if omitted:
log.info(
"PDF page images: %s rendering %d/%d pages; omitted e.g. %s",
pdf_path.name,
min(len(page_indices), max_pages),
total,
omitted[:12],
)
matrix = fitz.Matrix(zoom, zoom)
for i in page_indices:
if i < 0 or i >= len(doc):
+22 -2
View File
@@ -839,10 +839,30 @@ async def _stage_ic_extraction(ctx: PipelineContext) -> None:
api_logger=private,
)
# Upload to storage, then copy to library
# Upload to storage, then copy to library only if pintable is usable
extracted_key = f"{ctx.ws.prefix}/extracted/{safe}.json"
ctx.storage.upload_from_local(json_path, extracted_key)
proj_svc.save_to_library(ctx.storage, extracted_key, "extracted", f"{safe}.json")
try:
from backend.pinscopex.library_gate import should_promote_extraction
payload = json.loads(json_path.read_text(encoding="utf-8"))
ok, reason = should_promote_extraction(payload)
if ok:
proj_svc.save_to_library(
ctx.storage, extracted_key, "extracted", f"{safe}.json",
)
else:
logger.warning(
"Skipping library promote for %s: %s (kept project-local)",
mpn, reason,
)
except Exception:
logger.exception(
"Library gate failed for %s — promoting anyway", mpn,
)
proj_svc.save_to_library(
ctx.storage, extracted_key, "extracted", f"{safe}.json",
)
# Upload source datasheet PDF to library (content-addressed)
store_datasheet(ctx.storage, pdf_path, mpn)