Pinscope open-source core

Agentic schematic validation: datasheet extraction via Claude Console
Skills, netlist/BOM design graph, per-IC direct datasheet review with
page citations, capacitor derating, Next.js report UI.

Extracted from the Pinscope cloud codebase. Auth and billing live in the
private gateway repo behind stable seams (billing_hook.py, adapter files
listed in CLAUDE.md).
This commit is contained in:
Siddharth Kothari
2026-07-16 21:29:45 -07:00
commit 6672d2be57
254 changed files with 56662 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
---
skill_name: extract-specs
description: Extract pin table, package info, and electrical specifications from a discrete/simple component datasheet PDF. Returns structured data via the save_specs tool.
---
# Extract Component Specifications & Pin Table
Extract the pin table, package info, and key electrical specifications from a component datasheet and return them as structured JSON via the `save_specs` tool.
## Steps
### 1. Read the datasheet PDF
The datasheet PDF is provided in the user message. Focus on these sections:
- **Pin configuration / pin assignment table** — Pin number, pin name, description
- **Package information** — Pin count, package type
- **Electrical characteristics** — The primary source of parameter values
- **Absolute maximum ratings** — Maximum voltage, current, and power limits
### 2. Identify the component subtype
The system prompt provides a list of taxonomy subtypes. Choose the best match for this component. If none match, propose a new subtype following the dotted naming convention.
### 3. Extract the pin table
For every pin on the component, extract:
- `number` (int or str) — The pin number as printed in the datasheet
- `name` (str) — The pin name exactly as printed (e.g., `"A"` for anode, `"K"` for cathode, `"G"` for gate)
- `description` (str or null) — A brief description if the datasheet provides one
- `functions` (list[str] or null) — Alternate functions if the pin supports them
Rules for pin extraction:
- Include ALL pins — including pad/tab/exposed pad pins
- Use pin names verbatim from the datasheet — do not rename or normalize
- Pay careful attention to pin numbering — off-by-one errors break downstream validation
- For multi-pin packages (e.g., SOT-23 transistor), ensure the pin assignment matches the specific package variant
### 4. Extract package info
Decode the MPN and package details:
- `base_family` (str) — The base part family (e.g., `"BAT54"` from `"BAT54S"`)
- `package` (str) — Package name (e.g., `"SOT-23"`, `"SOD-123"`, `"TO-220"`)
- `pin_count` (int) — Number of pins
- `description` (str) — Human-readable decoding of the full MPN
### 5. Extract specifications
The system prompt contains a "PARAMETERS TO EXTRACT" section listing the **ONLY** parameters you should extract. These are the standardized parameters for this component type that are useful for schematic validation.
**CRITICAL: Extract ONLY the parameters listed in "PARAMETERS TO EXTRACT".** Do not add any other parameters, even if they appear in the datasheet. Parameters like contact material, insulator material, processing temperature, orientation, mounting type, plating, etc. are NOT useful for schematic validation and MUST be excluded.
For each listed parameter:
- **Search systematically**: Check electrical characteristics tables, absolute maximum ratings, and application notes
- **Prefer typical operating values** where available, but note maximums for rating parameters
- **Use SPICE multiplier prefixes** for all values with units: `T`=1e12, `G`=1e9, `M`=1e6, `k`=1e3, `m`=1e-3, `u`=1e-6, `n`=1e-9, `p`=1e-12. Pick the multiplier that gives the most readable number.
- Good: `"30V"`, `"240mV"`, `"500mA"`, `"47mohm"`, `"18pF"`, `"8MHz"`, `"10nC"`
- Bad: `"0.24V"`, `"0.5A"`, `"0.047ohm"`, `"0.000000000018F"`, `"8000000Hz"`
- **Always include the unit** with the multiplier in the value string
- **Use numeric values** only when the parameter is inherently unitless (e.g., turns ratio, pin count, hFE)
- **Use null** for parameters that are not applicable to this component or not found in the datasheet
Rules:
- Extract from the datasheet only — do not infer or calculate values
- If a parameter has different values at different conditions, use the value at the most common/standard condition
- For parameters with min/typ/max, prefer typical; include all in the string if they matter (e.g., `"550mV typ, 850mV max"`)
- **ONLY use parameter names from the "PARAMETERS TO EXTRACT" list** — any extra keys will be discarded
### 6. Call save_specs
Call the `save_specs` tool with:
- `component_subtype`: The dotted taxonomy path (e.g., `"discrete.diode.schottky"`)
- `component_subtype_description`: A brief description if this is a new subtype
- `package_info`: Package details (base_family, package, pin_count, description)
- `pintable`: Array of pin objects (number, name, description, functions)
- `values`: An object mapping parameter names to their extracted values
+49
View File
@@ -0,0 +1,49 @@
{
"type": "object",
"properties": {
"component_subtype": {
"type": "string",
"description": "Dotted taxonomy path, e.g. discrete.diode.schottky, connector.usb",
"pattern": "^[a-z][a-z0-9_]+(\\.[a-z][a-z0-9_]+)*$"
},
"component_subtype_description": {
"type": "string",
"description": "Brief description of the component subtype. Used when this is a new taxonomy entry."
},
"package_info": {
"type": "object",
"properties": {
"base_family": {"type": "string"},
"package": {"type": "string"},
"pin_count": {"type": "integer"},
"description": {"type": "string"}
},
"required": ["base_family", "package", "pin_count"]
},
"pintable": {
"type": "array",
"description": "Pin table for the component. Include ALL pins.",
"items": {
"type": "object",
"properties": {
"number": {},
"name": {"type": "string"},
"description": {"type": "string"},
"functions": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["number", "name"]
}
},
"values": {
"type": "object",
"description": "Extracted parameter values keyed ONLY by parameter names from the PARAMETERS TO EXTRACT list. Use SPICE multiplier prefixes (k, M, m, u, n, p) with units. Use null for missing/inapplicable parameters. Do NOT add parameters not in the list.",
"additionalProperties": {
"type": ["string", "number", "null"]
}
}
},
"required": ["component_subtype", "component_subtype_description", "package_info", "pintable", "values"]
}
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Validate extraction output against the specs schema."""
import json
import sys
from pathlib import Path
SCHEMA_PATH = Path(__file__).parent / "schema.json"
def validate(data: dict) -> list[str]:
"""Return list of validation errors (empty = valid)."""
errors = []
schema = json.loads(SCHEMA_PATH.read_text())
for field in schema.get("required", []):
if field not in data:
errors.append(f"Missing required field: {field}")
if "component_subtype" in data:
st = data["component_subtype"]
if not isinstance(st, str) or "." not in st:
errors.append(f"component_subtype must be dotted path, got: {st!r}")
if "package_info" in data:
pkg = data["package_info"]
for f in ["base_family", "package", "pin_count"]:
if f not in pkg:
errors.append(f"package_info missing required field: {f}")
if "pin_count" in pkg and not isinstance(pkg["pin_count"], int):
errors.append(f"package_info.pin_count must be integer, got: {type(pkg['pin_count']).__name__}")
if "pintable" in data:
pins = data["pintable"]
if not isinstance(pins, list) or len(pins) == 0:
errors.append("pintable must be a non-empty array")
else:
numbers = []
for i, pin in enumerate(pins):
if "number" not in pin:
errors.append(f"pintable[{i}] missing required field: number")
if "name" not in pin:
errors.append(f"pintable[{i}] missing required field: name")
if "number" in pin:
numbers.append(pin["number"])
dupes = [n for n in set(numbers) if numbers.count(n) > 1]
if dupes:
errors.append(f"Duplicate pin numbers: {dupes}")
if "values" in data:
values = data["values"]
if not isinstance(values, dict):
errors.append(f"values must be an object, got: {type(values).__name__}")
else:
for k, v in values.items():
if v is not None and not isinstance(v, (str, int, float)):
errors.append(f"values[{k!r}] must be string, number, or null, got: {type(v).__name__}")
return errors
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 validate.py '<json string>'")
sys.exit(1)
try:
data = json.loads(sys.argv[1])
except json.JSONDecodeError as e:
print(f"INVALID JSON: {e}")
sys.exit(1)
errors = validate(data)
if errors:
print("VALIDATION FAILED:")
for err in errors:
print(f" - {err}")
sys.exit(1)
else:
print("VALIDATION PASSED")