# Periscope Periscope is full **PCB analysis** for a board you intend to send to fab with confidence. Schematic review is a stage, not the deliverable. Give it a netlist, a BOM, datasheet PDFs, and a KiCad PCB. It builds a queryable graph of the design, extracts manufacturer constraints once into a shared library, then checks both the circuit and the copper: pads, tracks, vias, zones, measured geometry, and the interfaces that are actually on **this** board. Live instance: [https://periscope.michelebigi.it](https://periscope.michelebigi.it) This tree is the Periscope product (operator: Michele Bigi). It is derived from [Faradworks/Pinscope](https://github.com/Faradworks/Pinscope); Faradworks does not operate this instance. ## What it checks — and what it will not invent USB pair impedance, Ethernet class, and trace current versus width are **examples** of gated checks. They run when that bus, connector, or datasheet number exists on the board. They are not a catalog of every interface in electronics. **Semantic objects stay distinct.** A via is not a pad. A track is not a via. A zone is not a pad. Sharing a net, sitting inside a courtyard, or having nearby coordinates does not change the object type. **Evidence before conclusion.** If the datasheet, netlist, BOM, or `.kicad_pcb` does not supply the number, Periscope records `INSUFFICIENT` (or skips the check). It does not invent current, impedance, copper weight, geometry, IEC creepage, or “typical USB 500 mA”. **Trace current uses datasheet current only.** Width versus load (`PE-PWR`) runs when the datasheet reports `I_load` / `Imax` / `I_abs`. Missing that current is a skip — not a guessed ampacity table, not via-IPC folklore. **Interface class certifiers are gated on parts that are present.** USB-C, RJ45/Ethernet, PoE, and DDR-style certifiers fire only if that connector or device is on the graph. No USB-C receptacle → no USB-C findings (silence, not N/A). A bare RJ45 is Ethernet, not PoE. No DDR device → no DDR findings. Periscope does **not** claim FEM, thermal spreading, OpenEMS, or a field solver. Closed-form Z0 comes from vendored ImpedenceFinder (`vendor/impedancefinder/`; upstream license UNKNOWN). Findings keep FACT, REQUIREMENT, and INFERENCE apart. Recommended datasheet notes are not errors. The same finding object is used for schematic (`MODE=run`) and PCB (`MODE=pcb`). ## Repository layout | Path | Role | | --- | --- | | `periscope/src/` | Native Periscope (finding engine, PCB/placement, DeepSeek, Dockerfiles, KiCad plugin) | | `periscope/dependency/` | Inherited PinScope (in-tree AGPL dependency — do not delete) | | `backend/__init__.py` | Merges the two `backend` packages for local imports | | `scripts/` | `update-periscope.sh`, `materialize-frontend.sh`, smoke wrapper | | `vendor/impedancefinder/` | Third-party Z0 core (not FEM) | | `LICENSE` | GNU AGPL v3 | See `periscope/README.md` for the physical split. ## Run locally Python 3.12+, Node 20+, and a [DeepSeek API key](https://platform.deepseek.com/) for live extraction and review. Offline graph/smoke does not need the key. ```bash python3 -m venv .venv source .venv/bin/activate pip install -r periscope/src/backend/requirements.txt cp periscope/src/backend/.env.example .env # set DEEPSEEK_API_KEY python3 -m uvicorn backend.main:app --reload --host 127.0.0.1 --port 18741 ``` Frontend — overlay native UI onto the inherited shell, then start Next.js (this is what CI runs): ```bash ./scripts/materialize-frontend.sh cd .merge/frontend && npm install NEXT_PUBLIC_API_URL=http://127.0.0.1:18741 npm run dev -- --port 18742 --hostname 127.0.0.1 ``` Open the frontend URL, create a project, and upload a netlist plus BOM. A `.kicad_pcb` is required for the PCB job; without it the project can complete schematic review only. `periscope/dependency/simple_project/` is a small bundled schematic fixture (MSPM0G3507 + CH340E + SPX3819). Projects and the extraction library live in `data/` (gitignored). Extraction skills are local `periscope/src/skills/*/SKILL.md`. Do not run an Anthropic Console uploader. Tests from the repo root: ```bash pip install pytest pytest-asyncio pytest tests/ -q python3 scripts/smoke_simple_project.py # offline ``` ## Docker ```bash cp periscope/src/backend/.env.example .env # set DEEPSEEK_API_KEY docker compose up --build ``` Backend listens on port 8080, frontend on port 3000 (`docker-compose.yml`). ### Live host The public site is rebuilt from **`/root/periscope`** with the script that actually ships: ```bash cd /root/periscope ./scripts/update-periscope.sh ``` `--no-pull` skips git. `SITE=https://other.host ./scripts/update-periscope.sh` overrides the public URL. The script refuses to run unless the checkout is `/root/periscope` (override with `CANONICAL_ROOT` only if the host layout differs). It does not touch `data/`. ## Source | Remote | URL | | --- | --- | | GitHub | [https://github.com/manvalan/pinscope](https://github.com/manvalan/pinscope) (`git@github.com:manvalan/pinscope.git`) | | Gitea | [http://192.168.1.71:3000/michele/periscope.git](http://192.168.1.71:3000/michele/periscope.git) | Push both: `git push github && git push gitea` (no force). ## License [AGPL-3.0](LICENSE) — GNU Affero General Public License v3.0. This tree is derived from [Faradworks/Pinscope](https://github.com/Faradworks/Pinscope). For a commercial license of **upstream Pinscope**, contact Faradworks as they publish it. For this Periscope instance, contact Michele Bigi (mikbigi@gmail.com).