Add /api/library datasheet import and component GET/PUT with no exam.
Reorganize pytest into datasheet, library, schematic, PCB, and AF+AI.
Document Rust criteria (none chosen; no rustup) and coding conformity.
Versione codice **2.63.0** (`periscope/src/frontend/content/changelog.md`). Letto in `/Users/michelebigi/Development/periscope`. Costituzione senza eccezioni: via ≠ pad ≠ traccia ≠ zona; niente I/Z/mm inventati; skip se manca evidenza. **Non è DRC KiCad.** DRC = KiCad. Questo documento mappa l’analisi, non implementa.
I test pytest stanno in `tests/datasheet/`, `tests/library/`, `tests/schematic/`, `tests/pcb/`, `tests/af_ai/` (stessi file di 2.62.1, cartelle di mestiere). I path `tests/test_*.py` sotto i nodi restano i nomi file.
2.**Esame PCB** — `MODE=pcb`, `services/pcb_pipeline.py` → `run_pcb_checks` (`pcb_checks.py`) + review AI PCB (`pcb_review.py` / `pcb_validation.py`). Non auto-place.
I certifier USB-C / Ethernet / PoE girano su **entrambe** le pipeline (grafo, non geometria). DDR / CPU / FPGA / USB-PD sono la stessa qualità di finding ma partono da `run_pcb_checks` (grafo). Niente pezzo sul grafo → silenzio, non N/A.
Nodi analisi: **108** (99 PE-* + 1 LED senza `rule_id` + 8 processo estrazione/AI). Test pytest citati sotto i nodi: nomi reali da `tests/`.
- Test: `tests/test_inductor_specs.py` (13, Z ferrite solo se il PDF dice N Ω @ freq); `tests/test_pcb_software_bugs.py` (`test_lqw_decoder_and_unknown_skip`, `test_fb1_blm_is_bead_not_dcr_resistor`).
#### Resolve DigiKey / catalogo (esatto MPN)
Modulo: `services/digikey.py`, `passive_from_distributor.py`, `passive_from_mpn.py`. Analizza: parametri catalogo per auto-resolve. Hit fuzzy rifiutati.
- Skip se: MPN non esatto; API assente (fail-soft).
Suite deterministica end-to-end: `tests/test_validation_deterministic_seed.py``test_deterministic_findings_seeded_and_not_reviewed`; `tests/test_eval_report.py``test_simple_project_eval_matches_committed_golden`.
### 1.4 Certifier di classe (grafo — schema **e** PCB)
Moduli: `interface_class_check.py` (schema **e** PCB), `hf_bus_class.py` (2.62.0, solo `run_pcb_checks`), `usb_pd_check.py` (2.62.1, solo PCB). Niente geometria. Niente connettore/dispositivo → **silenzio**.
#### USB-C — solo se receptacle USB-C sul grafo
- **`PE-USBC-001`** classe USB2 vs USB3 da BOM/footprint/MPN/net.
- **`PE-USBC-002`** CC1 e CC2.
- **`PE-USBC-003`** Rd 5.1 kΩ ±10% o Rp 56/22/10 kΩ. Skip/INSUFFICIENT se R ignota o CC verso controller senza R.
- **`PE-USBC-004`** VBUS e GND.
- **`PE-USBC-005`** SuperSpeed solo se classe USB3; USB2 = N/A (non ERROR).
Moduli: `review_session.py`, `pcb_review.py` non entra qui. DeepSeek, PDF una volta per IC, fail-soft. Prompt tools grafo (`find_connected_components`, `get_net_for_pin`, `get_pintable`, `get_datasheet_excerpt`). Finding LLM = REVIEW, mai RULE ERROR (`finding_engine`).
Skip se: niente MPN; niente PDF (`not_reviewed`); fingerprint invariato (`review_fingerprint.py`).
Stadi: `ensure_graph` → `parse_pcb` → `classify` → `inventory` → **`run_pcb_checks`** → `ai_review` → `pcb_report.json`. ID finding `PCB-{ref}-{001}`. Mutex vs analisi schema e vs placement. Inventario net (`pcb_inventory.py`) è numeri, non finding.
KiCad DRC **non** è un nodo Periscope. `tests/test_pcb_software_bugs.py``test_drc_unconnected_items_count` e `test_hubaudio_pcb_lay004_vs_drc_order_of_magnitude` servono solo a **non** copiare il DRC.
Suite orchestrazione: `tests/test_pcb_review.py``test_run_pcb_checks_assigns_pcb_ids_and_recommendations`, `test_pcb_pipeline_imports_build_placement_plan`; `tests/test_pcb_phase_b.py``test_run_pcb_checks_phase_b_absent_is_silent`, `test_phase_b_rule_catalog`; `tests/test_interface_class_check.py``test_merge_drops_duplicate_pcb_interface_findings`; `tests/test_pcb_sse_terminal.py` (4).
### 2.1 Layout ingest (non DRC)
`parsers_kicad_pcb.py` → `LayoutGraph`. Nessun default 1 oz.
Via in courtyard vs `min_via_count`. Skip senza vertici courtyard e senza min.
- Test: `tests/test_placement_check.py``test_via_count_is_calculated_from_courtyard_and_min_parameter` (geometria `_in_poly`; non emette ancora il finding end-to-end).
- Test: `tests/test_fase_b_checks.py` — `test_timing_skips_without_numbers`, `test_timing_reset_rc_vs_t_reset`. **`PE-TIM-002` non ha un test che fa scattare il finding** (solo skip condiviso).
#### `PE-PI-001` / `PE-PI-002` — `pi_check.py`
001 cap locale sul rail IC; 002 locale vs bulk se i valori C esistono. Distanza mm solo da `layout_rules` (altrimenti è PE-PLC-001). Skip net NC.
- Test: `tests/test_fase_b_checks.py` — `test_pi_missing_local_is_risk_not_error`, `test_pi_any_cap_on_slash_prefixed_rail_suppresses_missing_local`, `test_pi_bulk_only_does_not_claim_no_local`; `tests/test_pcb_software_bugs.py``test_emi_and_pi_skip_unconnected_nets`. **`PE-PI-002` non ha un assert dedicato sul `rule_id`.**
### 2.8 ESD / return / EMI / SPOF
#### `PE-ESD-001` — `esd_return_check.check_esd`
Net J* ∩ IC senza parte ESD: REVIEW, non ERROR. Niente GPIO/NC/rail spam.
Silenzio Fase B se assente: `tests/test_pcb_phase_b.py``test_run_pcb_checks_phase_b_absent_is_silent`. Catalogo: `test_phase_b_rule_catalog`.
### 2.11 Review AI PCB
Modulo: `pcb_review.py` + `services/pcb_validation.py`. Stesso oggetto finding. Spiega FACT deterministici; **non** rilegge il PDF; non inventa interfacce / PoE / SuperSpeed / mm / Z / I.
**Auth**: i test auth esistono (`tests/test_local_auth.py`, `tests/test_periscope_auth_rewrite.py`, `tests/test_pinscope_compat.py`, più overlay che tengono il contratto login). Non si descrivono segreti, JWT, password, o flussi di autenticazione.
Altri non-analisi: rewrite frontend/Docker (`test_periscope_*_rewrite.py`, `test_periscope_leftover_package_requirements.py`, `test_periscope_boot_without_dependency_product.py`); delete progetto; cost estimator; event bridge; job runner; `test_deepseek_only.py` (routing modello, non finding).
---
## Conteggio nodi
| Gruppo | Nodi |
| --- | --- |
| Check solo schema (27 PE-* + LED senza ID) | 28 |
| USB-C / ETH / PoE (schema e PCB) | 13 |
| `PE-PWR-001` (due source: schema e PCB) | 1 |
| DDR / CPU / FPGA / PD (grafo, invocati da `run_pcb_checks`) | 11 |
| Check solo PCB (layout, SI/HF, potenza, Fase B, …) | 47 |
| Processo: 3 skill + DigiKey + value + IC AI + PCB AI + tab RF | 8 |
| **Totale nodi analisi** | **108** |
Check PE-* in codice: **99** (tutti elencati). Più LED senza `rule_id`. `PE-LAY-004`, `PE-PLC-005`, `PE-TH-002` sono in codice; `PE-LAY-004` / `PE-PLC-005` non sono in `_seed()` di `finding_engine.py`.
Buchi di test noti (il check c’è, il fire test no): `PE-TIM-002`, `PE-PI-002` (nessun assert `rule_id`), `PE-PLC-002` (solo geometria `_in_poly`), `PE-SI-004` / `PE-SI-005` / `PE-SI-006` / `PE-SI-008` (gate SI, niente caso dedicato).
Costituzione: `docs/development/CODING_CONSTITUTION.md` (*code that fits in your head*). **Per sezione**, non per funzione: conforme sì / no e perché. Taglio 2.62.1 + questa macro-fase (libreria come porta, test riorganizzati, AF+AI extra). Auth/JWT/users: **non toccati**; la sezione esiste e viene giudicata, non modificata.
Giudizio: **conforme** = un ingegnere legge la sezione senza ricostruire un sistema nascosto. **Non conforme** = troppe responsabilità, stato implicito, eccezioni ingoiate, o semantica a rischio. Un file lungo può essere conforme se il flusso è ovvio; uno corto no se fa tre mestieri.
Zero eccezioni al principio. Le sezioni “non conformi” restano debito: non si “passa” la costituzione dichiarandole OK.
---
## 1. Albero nativo / dependency
**Non conforme.**`periscope/src` (nativo) e `periscope/dependency` (PinScope ereditato) si fondono con `sys.path` + `pkgutil.extend_path`. Chi vince dipende dall’ordine di insert (`backend/__init__.py` vs `tests/conftest.py`). Due `projects.py`, due `pipeline.py`, due `extraction.py`. Per capire un import serve la mappa dei shadow, non il modulo. È un adattatore temporaneo con path di rimozione (Fase C) ma **oggi** non sta in testa.
## 2. Glue di root (Docker, compose, `backend/__init__.py`, script)
**Conforme a metà.**`backend/__init__.py` è corto e dice il mestiere. `scripts/update-periscope.sh` è una procedura esplicita (`/root/periscope`, niente `data/`). `docker-compose.yml` è piccolo. Il debito è il merge dei due tree a boot, non gli script.
## 3. Motore finding (`periscopex/finding_engine.py`, schema)
**Conforme.** Un oggetto finding, FACT / REQUIREMENT / INFERENCE, clamp verso il basso, `INSUFFICIENT`. Flusso visibile. Test stretti su tipo, codice, severity. ~450 righe: lungo ma un mestiere.
## 4. Modello semantico (`models.py`)
**Conforme.** Pad, via, track, zone, pin, net sono tipi distinti. Pydantic, niente god-class UI. ~534 righe di dati, non di orchestrazione.
## 5. Parser schematico (PADS, EDIF, KiCad sch)
**Conforme a metà.** Funzioni pure, fixture `simple_project`. `parsers_kicad.py` (~760) e `parsers_edif.py` (~465) sono grandi; il mestiere resta “testo → grafo”. Non classificano via PCB come pad (coperto da test rewrite). Debito: taglia dei file, non la semantica.
## 6. Parser PCB KiCad (`parsers_kicad_pcb.py`)
**Conforme.** Via ≠ pad è esplicito e testato. Un input `.kicad_pcb` → `LayoutGraph`. Non è DRC. ~446 righe.
## 7. Grafo (`graph.py`, `netlist_bundle.py`)
**Conforme.** Costruzione da BOM+netlist, helper di attraversamento. Ferrite Z è un innesto a parte (`ferrite_z.py`), non un if U1.
**Conforme nel disegno, non nel dispatcher.** Ogni modulo (`si_check`, `hf_line_check`, `stackup_check`, …) ha un mestiere e skip senza evidenza. `run_pcb_checks` è un elenco esplicito — si legge. **Non conforme:**`except Exception: log + skip` per ogni check (fallimento strumento vs finding vs dati, collassati). `pcb_power_thermal.py` (~748) e `si_check.py` (~862) e `interface_class_check.py` (~831) sono al limite: ancora un dominio, ma non “piccoli moduli”.
Non è DRC (clearance/track_width/annular restano KiCad). FEM assente: **conforme al vincolo**.
**Conforme.** Un file ≈ un check, grafo in → finding out, niente mm inventati. Alcuni file >400 righe (`passive_rail_check`, `filter_check`) ma il flusso è lo stesso.
## 10. Review AI (validate, pcb_review, review_tools)
**Non conforme come sezione unica.** L’autorità deterministica è nei check; l’LLM spiega — questo è il disegno giusto (costituzione §8). In pratica `review_tools.py` (~940) e `validation.py` (dependency + native) sono loop di tool + state. `pcb_review.py` (~327) è il prompt + vicinato: quello sta in testa. Il loop live dipende ancora da pezzi ereditati. AF+AI (ipotesi HF → indagine deterministica) è un modulo extra, non sostituisce `run_pcb_checks`.
**Non conforme.**`datasheet_extract.py` ~1322 righe e `extraction.py` ereditato ~1298: prompt, tool, coerce, taxonomy, auto-resolve nello stesso file. Il mestiere “PDF → JSON libreria” è uno, l’implementazione no. Native vs inherited duplicato finché il pipeline non switcha. **Conforme nel ruolo:** LLM estrae, non è il verificatore.
## 12. Libreria componenti (store + porta HTTP/UI)
**Prima: non conforme come porta.** Catalogo e `library_has_*` vivevano in `services/projects.py` (CRUD progetti + libreria). La pagina `/library` era sola lettura e l’empty state mandava a “crea un progetto e lancia la review”. Admin `/admin/components` mescola libreria e users.
**Dopo questa macro-fase: conforme come porta, con debito.**`services/library.py` + `routers/library.py` + `/library` (import PDF senza esame, GET/PUT scheda). Store content-addressed (`datasheet_store.py`) resta esplicito. `library_gate.py` è piccolo e duro (niente pintable velenosa). Debito: `projects.py` re-export per non riscrivere la pipeline; `list_library_catalog` prima ingoiava JSON rotti — ora logga e salta la riga (dato rotto ≠ crash dello store).
**Non conforme.**`pipeline.py` ~1522 righe: stage, storage, LLM, copy-in-library, graph. `pcb_pipeline.py` è più lineare (ensure_graph → parse → checks → AI → report) e si segue. Job/SSE sono un secondo asse di stato. Flusso reale: sì, ma non locale.
## 14. Storage (`storage.py`, GCS)
**Conforme.** Backend con chiavi stringa, locale vs GCS. Prefissi `users/…/projects/` e `library/` visibili.
**Non conforme per `routers/projects.py` (~1004)** e `pipeline.py` router (~783): troppi mestieri (CRUD, upload KiCad, DigiKey, LCSC). Report e impedance sono sezioni più piccole. La nuova `routers/library.py` è la porta libreria (un mestiere).
## 16. Auth / JWT / users
**Non toccata in questa macro-fase (vincolo).** La sezione **non è conforme** alla costituzione (Clerk + JWT locale + `admin.py` users nello stesso router, middleware che decide tre modi). Non si “sistema” qui. Non si aggiunge auth alla libreria.
## 17. Frontend — shell e pagina libreria
**Conforme a metà.** Overlay `src` su `dependency` (materialize) è un secondo albero da tenere in testa. Pagine progetto/report/dashboard sono lunghe ma a mestiere. `/library` era browse-only; ora è porta (import + modifica scheda) con form/editor spezzati. `admin/page.tsx` ~1780: **non conforme** (libreria + users + usage). Non toccato (users).
## 18. Frontend `api.ts` / types
**Non conforme.**`api.ts` nativo ~1179 righe, client unico per tutto. Types allineati ai modelli: sì. Un file non sta in testa. Nuove funzioni libreria restano lì per non inventare un secondo client.
## 19. Skills e taxonomy
**Conforme.**`skills/*/SKILL.md` + `validate.py` in-process; taxonomy JSON per tipo. `repo_paths` al posto di `Path(__file__)` magici (test rewrite). Non si chiama `upload_skills.py`.
## 20. Vendor ImpedenceFinder
**Conforme come confine, non come licenza.** Core Z0 chiuso, non FEM, non secondo set di formule (test). LICENSE UNKNOWN resta debito legale, non di forma del codice.
## 21. Test
**Prima: non conforme come mappa.** Un flat `tests/test_*.py` mescolava datasheet, libreria, schema, PCB, rewrite, auth. Copertura vera, ordine mentale no.
**Dopo: conforme come mappa, copertura tenuta.**
| Cartella | Mestiere |
| --- | --- |
| `tests/datasheet/` | preliminare PDF / pin / abs-max / I / Z / ferrite / DigiKey |
| `tests/library/` | porta libreria (anche senza esame) |
| `tests/pcb/` | `run_pcb_checks`, geometria, SI/HF già in 2.62.1 |
| `tests/impedancefinder/` | vendor Z0 (stesso nome package del vendor — non va sotto `pcb/`) |
| `tests/af_ai/` | extra: ipotesi HF → indagine deterministica (non DRC, no Z inventata) |
| `tests/` root | identity/rewrite/auth/job — non analisi |
`conftest.py` e `paths.py` restano in root. Test stretti (codice finding, via≠pad) restano la regola; i rewrite “il file sta in src” sono deboli ma sono recinti di albero, non di fisica.
## 22. Costituzioni Cursor (`.cursor/rules`, questo doc)
**Conforme.** Un master Markdown, `.mdc` operativi, niente Rust speculativo. Questo file è il giudizio, non una seconda costituzione.
## 23. Git / deploy
**Conforme al testo, fragile in pratica.** Commit+push a fine fase; deploy a fine macro-fase; no force. Il worktree locale era un pointer a `~/Development/pinscope` sparito: history recuperata da `github``cursor/pcb-hf-analisi-675d` @ `20733f0` (2.62.1). Non si tocca `~/Development/pinscope` per edit di prodotto.
---
## Sintesi
| Sezione | Conforme |
| --- | --- |
| Finding engine + modelli + parser PCB | sì |
| Check PE-* come moduli | sì (dispatcher fail-soft no) |
| Libreria come porta | sì dopo questa fase (re-export debito) |
| Test a cartelle di mestiere | sì dopo questa fase |
Priorità debito (non questa fase): spezzare `pipeline.py` / `datasheet_extract.py`; togliere lo shadow dependency quando un modulo nativo è provato; non ingoiare Exception nei check PCB (distinguere tool failure).
**Niente Rust in questa macro-fase** — vedi `docs/rust-criteri.md`. FEM fuori. DRC = KiCad.
La libreria **non** vive dentro l’esame. È una porta a sé: pagina `/library` + API `/api/library`. L’analisi (preliminare datasheet, schematico, PCB) *usa* la libreria; non è l’unico modo per riempirla.
## Flusso
```text
PDF + MPN
↓
POST /api/library/datasheets
↓
blob MD5 + ref MPN
↓
catalogo (tab Datasheets)
```
Nessun progetto, nessun `MODE=run` / `MODE=pcb`. Stesso store `library/datasheets/{blobs,refs}`.
Aggiornare una scheda già estratta:
```text
GET /api/library/components/{ic|passive|passive_part|simple}/{mpn}
PUT stesso path (JSON)
```
Pintable IC: `library_gate` rifiuta pintable vuota o senza nomi (niente stub in libreria condivisa).
## Cosa non è
- Non è admin/Clerk/JWT (non si tocca auth).
- Non è DRC, non è FEM.
- Non estrae da sola il pintable (LLM = preliminare datasheet nell’analisi, o un job futuro). Il PDF c’è comunque.
- Non sostituisce `library_has_*` usati dalla pipeline.
## Test
`tests/library/` — catalogo, alias MPN, import PDF senza progetto, GET/PUT scheda, rifiuto PDF non valido.
Costituzione: Python è il default. Rust **non** è un default. Questo documento dice *quando* un pezzo può entrare in Rust. Non sceglie ancora un pezzo. **Niente `rustup`, crate, né FFI finché una riga sotto non è scelta *e* misurata.**
FEM / OpenEMS / field solver: fuori dal prodotto. Non sono candidati Rust.
DRC: KiCad. Non è un candidato Rust in Periscope.
## Non solo velocità
Un bottleneck di CPU non basta. Un pezzo va in Rust solo se **tutti** i punti seguenti sono veri.
1.**Correttezza geometrica** — pad ≠ via ≠ traccia ≠ zona deve sopravvivere al confine. Se Rust collassa oggetti per “è più facile in un AABB”, il pezzo è rifiutato anche se è più veloce.
2.**Memoria / ownership** — pressione reale (PCB grandi, segmenti, zone) o lifetime che Python non può possedere senza copie cieche. Non “magari un giorno”.
3.**Parser o hot path** — il costo sta in un ciclo stretto (parse `.kicad_pcb`, walk segmenti, predicate geometriche) dopo profiling. Non nell’orchestrazione, non nell’I/O HTTP, non nell’LLM.
4.**Determinismo** — stessa input + config → stesso modello e stessi numeri. Niente hash random, niente ordine di hash map visibile nei finding, niente dipendenza dall’LLM.
5.**Confine Python grosso** — una chiamata: struct in → struct out. Vietato un round-trip per pad, per via, per “is_point_in_poly” dal loop Python.
Se manca anche uno solo di questi, il pezzo resta in Python.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.