Based on manvalan/pinscope main. Default LLM is DeepSeek with local skills and PDF ingest. Datasheets are fetched from LCSC/TI, stored in the component library, and review extracts abs-max with a deeper checklist. Adds scripts/update-pinscope.sh for the production host.
12 KiB
Pinscope — Agentic Schematic Validation
Pinscope validates hardware schematics against component datasheets. It extracts constraints from PDFs, parses netlists and BOMs into a queryable graph, and runs an agentic validation loop to flag design violations.
Open-core note. This is the open-source core. A small set of files are "gateway-owned seams" — pass-through stubs here (
frontend/src/proxy.ts,use-optional-auth.ts,clerk-theme-provider.tsx,components/billing/*,sidebar-auth.tsx,pricing-section.tsx,analytics/*,lib/csp-hosts.ts) that the hosted-cloud repo replaces with auth/billing implementations. Keep their export signatures stable, and never import auth/billing SDKs anywhere else in the frontend. On the backend, everything reaches billing only throughbackend/services/billing_hook.py:get_billing()(a no-op here).
System Overview
Three layers:
| Layer | Location | Purpose |
|---|---|---|
| Core library | backend/pinscopex/ |
Models, parsers, graph builder, agentic validator, passive resolver, taxonomy, BOM summary, derating |
| Backend | backend/ |
FastAPI app — async pipeline orchestration, SSE progress, project/file storage |
| Frontend | frontend/ |
Next.js 16 app — project dashboard, pipeline progress, report viewer, derating, admin dashboard |
Plus skills/ — extraction prompts (pintable, patterns, specs) inlined locally for DeepSeek; optional Anthropic Console Skills if you route a stage to Anthropic.
The pipeline stages: Parse BOM → Extract IC Pintables → Extract Simple Components → Extract Passives → DigiKey Auto-Resolve + Value Fallback → Build Graph → Direct Datasheet Review. Pipeline runs can be cancelled mid-execution via POST /api/pipeline/{id}/cancel.
Example Project
simple_project/ is the reference design for development and testing:
- MCU: TI MSPM0G3507SPTR (U3) — 48-pin LQFP
- USB-UART Bridge: CH340E (U2)
- LDO Regulator: SPX3819M5-L-3-3 (U1) — 5V to 3.3V
- ESD Protection: USBLC6-2SC6 (D1)
- Crystal: 8 MHz (X1) with 18pF load caps (C9, C10)
Files: .asc (PADS-PCB netlist; .edn EDIF 2.0.0 also accepted), .csv/.xlsx (BOM), design_graph.json (committed reference fixture used by tests).
Architecture Principles
- Modular extractors — Domain-specific extraction per component type, unified constraint schema
- Netlist as graph — Queryable bipartite graph (components + nets) with traversal helpers
- LLM API for PDF extraction — Forced tool calls for structured output (pintable, passive patterns, specs). Default provider is DeepSeek.
- Prompt caching — Anthropic stamps
cache_control; Gemini uses CachedContent; DeepSeek uses automatic prefix cache (cache-hit tokens in usage). - Local extraction skills —
skills/*/SKILL.mdis inlined andvalidate.pyruns in-process. Anthropic Console Skills remain optional viascripts/upload_skills.py. - Direct datasheet review — The model reads the IC datasheet plus circuit neighborhood, compares to the reference application circuit, and flags issues via graph query tools (
find_connected_components,get_net_for_pin,get_pintable). DeepSeek converts PDFs to text (and page images on the vision model). - Datasheet page trimming — Large PDFs are keyword-trimmed to relevant pages before sending to Claude, reducing token cost (
pypdf) - DigiKey fallback (exact MPN only) — When pattern-based and direct extraction fail, DigiKey API fetches product parameters for auto-resolve. DigiKey matches only on exact MPN; fuzzy hits are rejected to avoid polluting the shared library with wrong-dielectric / wrong-voltage parts.
- Value-string fallback — When DigiKey misses an R/C/L/FB passive, a value-string resolver maps the BOM
Valuestring to typed passive specs. Value-derived specs are persisted per-project only — never to the shared library. - Per-IC review error isolation — Direct datasheet review runs each IC independently; one malformed payload or bad response cannot kill the whole run. Failed ICs surface as skipped components with the error.
- Cross-IC excerpt budget (per-neighbor) — To verify an interface finding the reviewer can pull a connected IC's datasheet pages (
get_datasheet_excerpt). The budget is a global per-review page ceiling plus a per-neighbor sub-budget, so verifying one interface is never starved by pages already spent on other neighbors. - Finding normalization is downgrade-only — A post-review per-IC normalize pass (
services/normalize_findings.py) drops self-cancelling findings, merges same-root-cause findings, and re-grades severity — but only ever downward. A deterministic clamp caps each finding at the reviewer's calibrated severity (and anyUnverified:finding at WARNING, preserving the prefix). - Cross-IC finding dedup — After all per-IC reviews complete, a single pass (
services/dedupe_findings.py) collapses one physical interface defect reported from both endpoints into a single finding. Gated bycross_ic_dedup_enabled; fail-soft. - Capacitor voltage derating — Deterministic derating table computed from graph (ceramic/tantalum/electrolytic percentages, pass/fail per capacitor)
- Deterministic checks over heuristics — Exact checks where possible
- Zero coupling between layers — Backend calls pinscopex functions with paths; frontend talks to backend via REST + SSE
- Library deduplication — Shared library (
library/extracted/,library/patterns/,library/models/,library/passives/,library/datasheets/) caches extractions across projects - Content-addressed datasheets —
library/datasheets/blobs/{md5}.pdfstores unique PDFs once;library/datasheets/refs/{safe_mpn}.jsonmaps MPNs to blobs (dedupe + multi-MPN sharing) - Taxonomy-driven extraction — Living component taxonomy (
taxonomy/) with per-subtype classification and specs schemas - Per-stage model config — Each pipeline stage can use a different Claude model (e.g., Sonnet for review, Haiku for auto-resolve)
- API call logging — Every Claude API call is logged with token counts, cost, and timing per pipeline run
- Report versioning — Each project run is stamped with the current app version on the first
/starttransition (ProjectMeta.pinscope_version). The version comes fromfrontend/content/changelog.md's latest##heading — single source of truth — read at backend startup viabackend/_version.py.
Datasheet Extraction
Extracted data lives in library/extracted/ (shared) or per-project under the storage backend. One JSON per MPN, schema in backend/pinscopex/models.py.
Per-MPN IC extraction captures:
- Pintable — Pin number + name (required), description + alt functions (optional)
- Package info — Base family, package, pin count, description
- Component subtype — Dotted taxonomy path (e.g.,
ic.mcu,ic.power.ldo)
For discrete/simple components: 4. Specs — Component specs (value, tolerance, package, voltage rating, etc.); parameters are filtered against taxonomy specs schemas
Extraction inlines local skills (skills/*/SKILL.md + validate.py). Anthropic Console Skills are optional when PROVIDER_*=anthropic and a skill_id is in backend/skills_manifest.json.
Claude Console Skills
skills/
├── extract-pintable/ # Pin table + package info + taxonomy
│ ├── SKILL.md # System prompt (YAML frontmatter + markdown)
│ ├── schema.json # Tool output schema
│ └── validate.py # Validation script
├── extract-pattern/ # Passive MPN pattern
└── extract-specs/ # Component specs (discrete, connectors, crystals, etc.)
Taxonomy
Living component taxonomy in taxonomy/ — one JSON file per top-level type (ic, passive, connector, crystal, discrete, fuse, switch, test_point, transformer). Each subtype entry includes description and example_mpn.
Key taxonomy features:
- Ref prefix mapping —
U→ic,R/C/L→passive,D/Q→discrete,X→crystal, etc. - Dotted subtype paths — e.g.,
ic.mcu,passive.capacitor.ceramic,ic.protection.esd - Dynamic growth —
add_subtype()adds new entries; concurrent-safe JSON writes - Specs schema auto-generation — Type-level and subtype-level parameter specs schemas are auto-generated via Claude when a taxonomy entry has none; extraction discards parameters not in the schema (
extra_specsfield)
Scripts
scripts/upload_skills.py— Create, update, or list Claude Console Skills. Reads/writes skill IDs tobackend/skills_manifest.jsonscripts/migrate_datasheets_to_library.py— One-time migration: copy per-project datasheets tolibrary/datasheets/(dry-run by default,--applyto execute)scripts/migrate_datasheets_to_blobs.py— Migrate named-PDF datasheets into the content-addressed blobs/refs layout (dry-run by default,--applyto execute)scripts/dedup_library_datasheets.py— Remove redundant per-MPN datasheet PDFs when a passive pattern already has adatasheet_key(dry-run by default,--applyto execute)scripts/gc_orphan_blobs.py— Garbage-collectlibrary/datasheets/blobs/*.pdfnot referenced by any ref filescripts/clear_rules_from_extractions.py— Strip deprecatedrules/absolute_maximum_ratingsfrom existing library extractions
Tech Stack
- Core: Python 3.12+, Pydantic 2.x, OpenAI SDK (DeepSeek), Anthropic SDK (optional), google-genai (optional), openpyxl, pypdf, PyMuPDF
- Backend: FastAPI, uvicorn, sse-starlette, pydantic-settings
- Frontend: Next.js 16 (App Router, Turbopack), React 19, Tailwind CSS v4, shadcn/ui (Base UI), react-pdf
- AI: DeepSeek Chat Completions (OpenAI-compatible) with forced tool calls for extraction and agentic review. Optional Anthropic / Gemini fallbacks.
- Model:
deepseek-v4-flash-vision-expfor extraction,deepseek-v4-profor review,deepseek-v4-flashfor auto-resolve (per-stage overrides via.env) - Skills: Local SKILL.md + validate.py (DeepSeek/Gemini); optional Anthropic Console Skills
- External APIs: DigiKey API v4 (OAuth2) — optional datasheet auto-fetch and parameter-based auto-resolve (
DIGIKEY_CLIENT_ID,DIGIKEY_CLIENT_SECRET)
Extracted Model Versioning
All ComponentConstraints extracted JSON files carry a model_version semver field:
- Initial value — set from
default_model_versioninbackend/skills_manifest.json(starts at1.0.0) - Minor bump —
default_model_versioninskills_manifest.jsonis incremented byscripts/upload_skills.py --update, so all new extractions after a skill update start at the new minor (e.g.1.0.0→1.1.0)
Rule: When committing or pushing changes under skills/, run python3 scripts/upload_skills.py --update before the commit/push to sync skill versions and bump default_model_version.
Development Guidelines
- Write tests against
simple_project/— it's the ground truth - Netlist parser and BOM parser are pure functions with no side effects
- All data structures use Pydantic models in
backend/pinscopex/models.py - Frontend types in
frontend/src/lib/types.tsmust stay in sync withbackend/pinscopex/models.py - Extraction prompts live in
skills/(SKILL.md + schema.json + validate.py) and run locally against DeepSeek - Never swallow exceptions silently — prefer logging or re-raising over bare
except: continue. Silent failures hide real bugs.
Running
# Backend (copy backend/.env.example to .env at repo root first)
python3 -m uvicorn backend.main:app --reload # localhost:8000
# Frontend
cd frontend && npm run dev # localhost:3000
Local mode needs no cloud services and no auth — projects are stored in data/ and you are user_id="local" with admin access.