Make the component library a standalone product door (2.63.0).

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.
This commit is contained in:
2026-09-21 22:12:00 +02:00
parent 20733f0ec6
commit 4df04df5d4
101 changed files with 2060 additions and 302 deletions
+663
View File
@@ -0,0 +1,663 @@
# Albero delle analisi Periscope
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 lanalisi, 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.
Due pipeline:
1. **Schematico / review AI**`MODE=run`, `services/pipeline.py``validate_design_async` (`validation.py`): estrazione datasheet, check deterministici, review IC DeepSeek.
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/`.
---
```
Periscope 2.62.1
├── 1. Schematico / review AI (MODE=run)
│ ├── ingest (non è analisi: parser → grafo)
│ ├── estrazione datasheet
│ ├── check deterministici schema
│ ├── certifier di classe (condivisi con PCB)
│ └── review AI per-IC + post-pass
└── 2. Esame PCB (MODE=pcb)
├── ingest layout (non è DRC)
├── run_pcb_checks
│ ├── net / footprint
│ ├── placement vs layout_rules
│ ├── SI + linee HF (2.62.0 Fase A)
│ ├── DDR / CPU / FPGA (2.62.0)
│ ├── potenza / via / termico
│ ├── derating / timing / PI
│ ├── ESD / return / EMI / SPOF
│ ├── BOM ↔ PCB ↔ datasheet
│ └── resto Fase B (2.62.1)
├── review AI PCB
└── tab antenna RF (non in run_pcb_checks)
```
---
## 1. Schematico / review AI (`MODE=run`)
Orchestrazione: `pipeline.py` stadi `bom_parse``ic_extraction``simple_extraction``passive_extraction``graph_build``validation`. Check deterministici: `_run_deterministic_checks` in `validation.py`. Finding ID schema `PS-{ref}-{nnn}` (non `PCB-`).
### 1.1 Ingest (prerequisito, non analisi)
Parser e grafo non emettono PE-*. Skip MPN: `ic_mpn_skip_reason` (silenzio onesto, niente MPN inventato).
- Test parser/grafo: `tests/test_bom_parser.py` (`test_parse_bom_uses_value_for_ic_when_mpn_column_empty`, `test_parse_bom_keeps_pnm_and_datasheet_url`); `tests/test_hubaudio_bom.py` (tutti e 4); `tests/test_netlist_parser.py` (tutti e 3); `tests/test_edif_parser.py` (14); `tests/test_kicad_parser.py` (17); `tests/test_kicad_project.py` (19); `tests/test_netlist_bundle.py` (9); `tests/test_functional_groups.py` (8).
- Test overlay parser: `tests/test_periscope_parsers_rewrite.py`, `tests/test_periscope_graph_rewrite.py`, `tests/test_periscope_models_rewrite.py`.
### 1.2 Estrazione datasheet (processo)
Skip se manca PDF o lo skill non ha numero. Libreria `library/extracted` condivisa schema+PCB. PCB review non rilegge il PDF (`pdf_path=None`).
#### `extract-pintable` — skill `skills/extract-pintable/`
Modulo: `services/datasheet_extract.py` + `llm/local_skill.py`. Analizza: pin table, package, taxonomy IC.
- Skip se: niente PDF; IC già in libreria alla `model_version` minima.
- Test: `tests/test_native_datasheet_extract.py` (`test_coerce_abs_max_matches_inherited`, `test_native_extract_has_no_anthropic_import`); `tests/test_pdf_ingest.py` (6); `tests/test_deepseek_provider.py` (`test_local_skills_load`, `test_extract_pdf_text_includes_page_markers`, `test_wroom_rejects_bare_soc_pin1_ant`); `tests/test_periscope_taxonomy_skills_src.py` (`test_pintable_skill_mentions_save_tool_and_wroom`, `test_skills_dir_is_src_and_local`).
#### `extract-specs` — skill `skills/extract-specs/`
Analizza: specs discreti/connettori/cristalli; parametri fuori taxonomy → scartati.
- Skip se: niente PDF o tipo non in `SIMPLE_TYPES`.
- Test: `tests/test_pdf_ingest.py` (`test_coerce_abs_max_keeps_valid_drops_junk`); `tests/test_layout_rules.py` (5 — `layout_rules` numerici, niente mm inventati).
#### `extract-pattern` — skill `skills/extract-pattern/`
Analizza: pattern MPN passivi (R/C/L/FB).
- Skip se: MPN opaco; DigiKey/value fallback copre prima.
- 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).
- Test: `tests/test_digikey_match.py` (3); `tests/test_passive_from_distributor.py` (7); `tests/test_passive_from_mpn.py` (7); `tests/test_datasheet_finder.py` (17); `tests/test_library.py` (5).
#### Resolve da stringa Value (solo progetto)
Modulo: `services/passive_from_value.py`. Specs value-derived **non** in libreria condivisa.
- Skip se: stringa ambigua / placeholder.
- Test: `tests/test_passive_from_value.py` (8).
### 1.3 Check deterministici schema
Fail-soft per modulo. Senza evidenza: skip (lista vuota), non folklore.
#### `PE-MUX-001` — `pin_mux_check.py`
Analizza: funzione pin vs net (UART/SPI/I2C mux).
Skip se: `functions` vuote; link inter-device stesso peripheral; net opaco.
- Test: `tests/test_pin_mux_check.py``test_real_defect_uart5_swapped_is_error`, `test_correct_assignment_no_finding`, `test_inter_device_same_peripheral_link_is_skipped`, `test_transceiver_peer_without_peripheral_still_fires`, `test_empty_functions_skipped`, `test_pin_exposes_peripheral_but_not_signal_no_complement`, `test_opaque_net_not_flagged`, `test_token_parser_and_normalizer`, `test_spi_legacy_net_names_match_modern_pin_functions`, `test_spi_controller_peripheral_names_are_synonyms`, `test_simple_project_uart0_nets_are_feasible_on_mspm0_pins`, `test_simple_project_uart0_swapped_on_mspm0_is_error`, `test_spi_genuine_infeasibility_still_fires_with_modern_names`, `test_finding_prints_full_raw_capability_list_and_intent_caveat`, `test_legacy_report_without_source_validates`.
#### `PE-NC-001` — `nc_pin_check.py`
Analizza: pin NC collegato a net attivo.
Skip se: niente pintable.
- Test: `tests/test_nc_pin_check.py``test_nc_pin_on_active_net_warns`, `test_nc_pin_on_nc_net_silent`, `test_no_pintable_silent`.
#### `PE-DEC-001` / `PE-DEC-002` — `passive_rail_check.py` `check_supply_decoupling`
Analizza: condensatore sul net di alimentazione IC (001 presenza; 002 valore vs datasheet).
Skip se: net NC; pin enable strap; manca valore per 002.
- Test: `tests/test_passive_rail_check.py``test_missing_decoupling_is_warning`, `test_cap_to_gnd_clears_decoupling`, `test_enable_strapped_to_rail_is_not_decoupling`, `test_nc_supply_net_is_skipped`, `test_ldo_vout_needs_cout`, `test_ldo_vout_100n_only_is_value_warning`, `test_vdd_100n_is_not_a_value_warning`, `test_ki_cad_voltage_prefix_is_power`, `test_fb_and_rn_prefixes`.
#### `PE-I2C-001` / `PE-I2C-002` — `passive_rail_check.py` `check_i2c_pullups`
Analizza: pull-up I2C (001 presenza; 002 valore vs banda).
Skip se: pin SDA è alias SPI; pull-up senza valore (002 non sizato); bus assente.
- Test: `tests/test_passive_rail_check.py``test_i2c_missing_pullup`, `test_i2c_pullup_present`, `test_i2c_pullup_to_3v3_digital_typed_as_signal`, `test_spi_pin_alias_sda_is_not_i2c`, `test_i2c_from_slash_alias_in_pin_name`, `test_i2c_4k7_pullup_is_in_nxp_wide_band`, `test_i2c_100ohm_pullup_is_too_stiff`, `test_i2c_100k_pullup_is_too_weak`, `test_i2c_pullup_without_value_is_not_sized`.
#### `PE-RST-001` / `PE-RST-002` — `passive_rail_check.py` `check_reset_pullups`
Analizza: reset/enable pull (001 presenza; 002 verso vs datasheet).
Skip se: GPIO guida il reset.
- Test: `tests/test_passive_rail_check.py``test_reset_no_finding_when_gpio_drives`, `test_reset_floating_is_warning`, `test_nrst_pulldown_is_warning`, `test_nrst_pullup_is_not_pulldown`.
#### `PE-BOM-001` / `PE-BOM-002` — `bom_match_check.py`
Analizza: MPN schema vs BOM (001 mismatch; 002 riga BOM orfana).
Skip se: mappa schematica vuota (PADS/EDIF senza properties).
- Test: `tests/test_bom_match_check.py``test_matching_mpns_produce_no_findings`, `test_mpn_mismatch_is_error_ps_bom_001`, `test_orphan_bom_ref_is_warning`, `test_mpn_case_and_whitespace_are_not_a_mismatch`, `test_empty_bom_mpn_with_schematic_mpn_is_not_a_mismatch`, `test_empty_schematic_map_skips_check`, `test_legacy_design_graph_without_source_fields_still_validates`, `test_build_graph_kicad_mpn_mismatch_surfaces`.
#### `PE-DNP-001` — `dnp_check.py`
Analizza: DNP vs enable floating.
Skip se: colonna DNP assente.
- Test: `tests/test_dnp_check.py``test_dnp_pull_leaves_enable_floating`, `test_fitted_pull_is_silent`, `test_no_dnp_column_skips_floating_enable`, `test_parse_bom_reads_dnp_and_skips_when_column_absent`.
#### LED corrente — `led_current_check.py` (nessun `PE-*`)
Analizza: I_f Ohm vs rating canale. Source `led_current_check`.
Skip se: niente `forward_current_*`; tensione rail sconosciuta (non si indovina).
- Test: `tests/test_led_current_check.py``test_over_current_is_error`, `test_proper_resistor_no_finding`, `test_unknown_rail_no_error`, `test_no_rating_skipped`, `test_parse_resistance`.
#### `PE-TH-001` / `PE-TH-002` / `PE-TH-003` — `thermal_check.py`
Analizza: P = I_load × drop; Tj = Ta + P·θJA (001 INFO senza θJA o Tj ok; 002 WARNING Tj alto — **non in catalogo `finding_engine`**; 003 I²R shunt).
Skip se: manca I_load (Iout_max non è carico).
- Test: `tests/test_thermal_check.py``test_ldo_without_theta_ja_is_info`, `test_iout_max_is_not_used_as_load`, `test_ldo_hot_tj_is_warning`, `test_shunt_over_rating_is_warning`, `test_led_resistor_within_rating_is_silent`.
#### `PE-PWR-001` (schema) — `power_margin_check.py`
Analizza: Σ IQ + I_load vs Iout regolatore; caduta su R serie. Source `power_margin_check`.
Skip se: manca I_load; IQ assente non è fail.
- Test: `tests/test_power_margin_check.py``test_load_over_iout_max_is_ps_pwr_001`, `test_missing_iq_is_not_guessed_into_margin_fail`, `test_series_r_ir_drop_uses_i_load_not_trace`, `test_no_series_r_does_not_invent_trace_drop`.
#### `PE-SEQ-001` — `sequencing_check.py`
Analizza: PG → EN da note datasheet.
Skip se: niente spec `power_sequence`.
- Test: `tests/test_sequencing_check.py``test_pg_not_tied_to_en_is_warning`, `test_pg_tied_to_en_is_silent`, `test_no_power_sequence_spec_skips_even_if_pg_open`.
#### `PE-FLT-001` / `PE-FLT-002` / `PE-FLT-003` — `filter_check.py`
Analizza: topologia filtro (001), pezzo (002), fc vs ADC / DCR ferrite (003).
Skip se: manca C; pull-up+decoupling non è filtro; DCR ferrite senza limite datasheet.
- Test: `tests/test_filter_check.py``test_rc_reports_fc_info_without_adc_rate`, `test_rc_vs_adc_rate_is_warning`, `test_pullup_plus_decoupling_is_not_a_filter`, `test_missing_c_value_does_not_invent_fc_warning`, `test_pi_and_t_need_l_and_c`, `test_ferrite_dcr_warns_only_with_datasheet_limit`.
#### `PE-XTAL-001` / `PE-XTAL-002` / `PE-XTAL-003` — `crystal_cl_check.py`
Analizza: CL vs C1/C2 (001 coppia; 002 serie/stray; 003 drive). Cstray solo se in specs.
Skip se: niente CL in specs.
- Test: `tests/test_crystal_cl_check.py``test_no_cl_in_specs_is_silent`, `test_series_above_cl_without_stray_warns`, `test_with_stray_mismatch_warns`, `test_simple_project_without_cl_silent`.
#### `PE-LF-001` / `PE-LF-002` / `PE-LF-003` — `lifecycle.py`
Analizza: Obsolete / NRND / RoHS non-compliant.
Skip se: catalogo assente.
- Test: `tests/test_lifecycle_check.py``test_obsolete_is_warning_and_uses_distributor_replacement`, `test_nrnd_is_info`, `test_explicit_rohs_non_compliant_is_warning`, `test_active_is_silent`, `test_rohs_not_applicable_is_not_a_fail`, `test_missing_catalog_and_missing_substitute_are_not_guessed`.
#### `PE-INT-001` — `internal_features_check.py`
Analizza: ESD/pull interni vs net.
Skip se: `internal_features` vuoto (non si indovina open-drain).
- Test: `tests/test_internal_features_check.py``test_listed_open_drain_without_pull_is_warning`, `test_listed_pin_with_pullup_is_silent`, `test_empty_features_does_not_guess_open_drain`.
#### `PE-ESR-001` — `hf_coverage_check.py`
Analizza: copertura HF/ESR (bulk vs 100 n).
Skip se: valore C sconosciuto.
- Test: `tests/test_hf_coverage_check.py``test_bulk_only_is_info_ps_esr_001`, `test_bulk_plus_100n_is_silent`, `test_unknown_cap_value_is_not_guessed`.
#### `PE-ERRATA-001` — `errata_check.py`
Analizza: errata pubblicata vs pull sul pin.
Skip se: MPN sconosciuto o entry senza URL.
- Test: `tests/test_errata_check.py``test_missing_errata_pullup_is_ps_errata_001`, `test_pullup_present_is_silent`, `test_unknown_mpn_and_url_less_entry_are_silent`.
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).
Test: `tests/test_interface_class_check.py``test_no_connector_emits_nothing`, `test_usbc_rd_vbus_gnd_certified`, `test_usbc_missing_cc2_is_error`, `test_usbc_wrong_rd_is_error`, `test_usbc_cc_to_controller_without_r_is_insufficient_not_error`, `test_usbc_unknown_r_value_is_insufficient`, `test_usb3_class_without_ss_pairs_is_error`, `test_usb3_with_ss_pairs_is_certified`, `test_missing_vbus_is_error`, `test_simple_project_usbc_not_false_error`, `test_hubaudio_like_usbc_usb2_and_poe_magjack`, `test_usbc_vbus_does_not_invent_usb_500ma`, `test_does_not_use_layout_geometry`, `test_rule_catalog_shared_not_si`.
#### Ethernet — solo se RJ45/MagJack
- **`PE-ETH-001`** classe 10/100 vs GbE. Skip/INSUFFICIENT se velocità assente (non si inventa GbE).
- **`PE-ETH-002`** TX/RX (10/100) o quattro MDI (GbE).
- **`PE-ETH-003`** magnetics se GbE.
- **`PE-ETH-004`** Bob Smith 75 Ω: REVIEW (spesso in MagJack).
Test: `tests/test_interface_class_check.py``test_ethernet_10_100_magjack_no_gbe_magnetics_error`, `test_gbe_without_magnetics_is_error`, `test_gbe_with_magnetics_is_certified`, `test_rj45_without_speed_is_insufficient_not_invented_gbe`.
#### PoE — solo con evidenza PoE (RJ45 nudo = skip)
- **`PE-POE-001`** evidenza PoE.
- **`PE-POE-002`** classe/tipo (802.3af/at/bt, Class n) — mai inventata.
- **`PE-POE-003`** magnetics/isolamento (MagJack o LAN transformer).
- **`PE-POE-004`** (2.62.1) tensione di isolamento solo da numero sourced. Skip senza V.
Test: `tests/test_interface_class_check.py``test_bare_rj45_does_not_invent_poe`, `test_poe_with_evidence_requires_magnetics_isolation`, `test_poe_evidence_on_bare_jack_is_isolation_error`, `test_hubaudio_bom_has_usbc_and_poe_rj45_not_ddr`, `test_run_pcb_checks_skips_absent_interfaces`; `tests/test_pcb_phase_b.py``test_poe_isolation_skips_without_voltage_fact`.
#### DDR — solo se DRAM sul grafo (2.62.0)
- **`PE-DDR-001`** classe DRAM.
- **`PE-DDR-002`** CK, DQS, ≥8 DQ.
- **`PE-DDR-003`** VTT/VREF solo se quei net esistono (altrimenti skip, non VTT inventato).
Test: `tests/test_hf_line_check.py``test_no_ddr_cpu_fpga_on_usb_only_graph`, `test_ddr_present_class_and_nets_skip_invented_vtt`, `test_rule_catalog_hf_ids`.
#### CPU bus parallelo — solo se D/A/controllo esistono (2.62.0)
- **`PE-CPU-001`** presenza bus parallelo.
- **`PE-CPU-002`** ≥8 D/DQ/AD.
- **`PE-CPU-003`** address o AD mux.
- **`PE-CPU-004`** CS/WE/OE (o FMC).
Test: `tests/test_hf_line_check.py``test_cpu_parallel_bus_only_when_data_addr_control_exist`.
#### FPGA — solo se FPGA sul grafo (2.62.0)
- **`PE-FPGA-001`** classe FPGA.
- **`PE-FPGA-002`** flash di config se c’è un flash IC (altrimenti skip).
- **`PE-FPGA-003`** VCCINT/VCCIO solo se net nominati.
Test: `tests/test_hf_line_check.py``test_fpga_only_when_present_config_flash_optional`.
#### USB-PD contratto — `usb_pd_check.py` (2.62.1)
- **`PE-PD-001`** controller PD sul grafo (FUSB302/TPS257/…). Non è USB2 Rd. PDO non inventati.
Test: `tests/test_pcb_phase_b.py``test_usb_pd_skips_without_pd_controller`.
### 1.5 Review AI per-IC + post-pass
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`).
- Review loop: `tests/test_native_review_loop.py` (5); `tests/test_validation_no_tool_recovery.py` `test_no_tool_calls_triggers_forced_submit_next_turn`; `tests/test_validation_concurrency.py` (3); `tests/test_validation_trace_log.py` (3); `tests/test_review_parse.py` (2); `tests/test_quote_verify.py` (5); `tests/test_finding_engine.py` (`test_llm_review_is_never_rule`, `test_review_and_risk_cannot_stay_error`, `test_insufficient_evidence_does_not_stay_error`); `tests/test_fase_b_checks.py` `test_llm_error_review_clamped_in_complete_finding`; `tests/test_review_fingerprint.py` (2); `tests/test_reprocess.py` (4); `tests/test_deepseek_provider.py` (`test_thinking_only_echoes_reasoning_content_on_follow_up` e roundtrip tool).
- Excerpt cross-IC: `tests/test_datasheet_excerpt_tool.py` (12); `tests/test_cache_breakpoint_limit.py` (3).
- **Normalize** (downgrade-only): `tests/test_normalize_findings.py` (14); `tests/test_periscope_postpass_rewrite.py` `test_emmaforo_uart_normalize_cannot_promote_warning`.
- **Dedupe** cross-IC: `tests/test_dedupe_findings.py` (11); `tests/test_periscope_postpass_rewrite.py` `test_emmaforo_uart_dedupe_collapses_both_endpoints`.
---
## 2. Esame PCB (`MODE=pcb`)
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.
- Test: `tests/test_kicad_pcb.py` (14); `tests/test_pcb_via_not_pad.py` (geometria via≠pad); `tests/test_pcb_software_bugs.py` `test_kicad10_name_only_nets_fill_index`.
### 2.2 Net / footprint — `pcb_net_match.py` + `pcb_checks._incomplete_layout_finding`
#### `PE-LAY-001`
Pad PCB su net diverso dallo schema (stesso pin).
Skip se: layout assente.
- Test: `tests/test_pcb_review.py``test_pad_net_mismatch_is_pe_lay_001`, `test_hierarchy_plus_real_mismatch_keeps_only_real_pe_lay_001`.
#### `PE-LAY-002`
Ref in schema, nessun footprint (tranne #PWR). Distingue “manca sulla PCB”.
- Test: `tests/test_pcb_review.py` `test_missing_footprint_is_pe_lay_002`; `tests/test_incomplete_pcb.py` `test_unplaced_ref_is_lay_002_not_invented_xy`.
#### `PE-LAY-003`
Stesso net, solo prefisso `/sheet/` KiCad (INFO, una volta).
- Test: `tests/test_pcb_review.py` `test_kicad_hierarchy_slash_is_one_sync_finding_not_per_pad`.
#### `PE-LAY-004` — `pcb_checks.py` (**non in catalogo `finding_engine`**)
Pad net senza rame (track **o** via **o** zone). Skip NC `unconnected-(…)`. INFO INSUFFICIENT, non FAIL inventato.
- Test: `tests/test_incomplete_pcb.py` `test_run_pcb_checks_on_partial_layout_does_not_invent_tracks`; `tests/test_pcb_software_bugs.py``test_lay004_zone_counts_as_copper`, `test_lay004_skips_unconnected_nc_and_matches_sheet_prefix`, `test_hubaudio_pcb_lay004_vs_drc_order_of_magnitude`.
### 2.3 Placement vs `layout_rules` — `placement_check.py`
Senza mm in `layout_rules`: skip. Niente 3 mm default. Distanza euclidea pad-pad **non** usata.
#### `PE-PLC-001`
Path rame (segmenti) vs `max_distance_mm`.
- Test: `tests/test_placement_check.py``test_crystal_load_cap_beyond_max_distance_mm_is_ps_plc_001`, `test_crystal_load_cap_within_max_distance_mm_is_silent`, `test_track_path_longer_than_max_distance_mm_is_ps_plc_001`; `tests/test_pcb_review.py` `test_close_decoupling_has_no_plc_001`; `tests/test_placement_check.py` `test_simple_project_without_pcb_has_no_ps_plc`.
#### `PE-PLC-002`
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).
#### `PE-PLC-003`
`same_layer` decoupling.
- Test: `tests/test_placement_check.py``test_same_layer_param_opposite_layers_is_ps_plc_003`, `test_same_layer_param_same_copper_is_silent`, `test_opposite_layers_without_same_layer_param_is_silent`, `test_opposite_layers_with_via_in_courtyard_is_silent`.
#### `PE-PLC-004`
Keepout: net estraneo nel courtyard. Non è DRC rame-pad.
- Test: `tests/test_placement_check.py``test_keepout_foreign_track_in_courtyard_is_ps_plc_004`, `test_ic_courtyard_pad_nets_are_not_keepout_violations`, `test_keepout_own_net_in_courtyard_is_silent`, `test_keepout_without_courtyard_is_silent`.
#### `PE-PLC-005` (**non in catalogo `finding_engine`**)
Proximity non misurabile perché manca la **traccia**.
- Test: `tests/test_placement_check.py` `test_unrouted_crystal_cap_is_insufficient_not_euclidean`.
Schema runner omette i check layout: `tests/test_placement_check.py` `test_schema_deterministic_runner_omits_layout_checks`.
### 2.4 SI — `si_check.py` + ImpedenceFinder
Gate: `layout_rules` kind + `net_class`/quote sul **bus citato**. Skip I2C/GPIO/EN/CC/strap RC. Bus: USB2, USB3, ETH MDI, RGMII, SGMII, DDR3 CLK/DQS/DQ/ADDR, HDMI, PCIe, LVDS; 2.62.0 aggiunge MIPI e clock HF (`bus_class`); XTAL/OSCIN fuori.
Z0: `impedance.py` / `impedance_traces.py`. CPWG → errore, non numero inventato. Senza stackup: skip misura.
Suite gate: `tests/test_si_check.py` `test_skip_i2c_gpio_cc_regn`, `test_i2c_not_checked_as_50_ohm`, `test_simple_project_without_pcb_has_no_ps_si_001`; `tests/test_hf_line_check.py` `test_bus_class_mipi_hf_clk_not_xtal`. Calcolatrice: `tests/test_impedance.py` (20); misura net: `tests/test_impedance_traces.py` (10).
#### `PE-SI-001` length_match / skew coppia
Skip senza mm datasheet.
- Test: `tests/test_si_check.py` `test_length_match_2_5mm_vs_1mm_is_fail`, `test_coverage_mdi_rgmii_ddr3_usb3_gated_not_cross_mapped`.
#### `PE-SI-002` Zavg/min/max vs finestra datasheet
Mai folklore 90 Ω.
- Test: `tests/test_si_check.py` `test_emmaforo_usb_avg_in_window_min_out_is_margin`, `test_coverage_mdi_rgmii_ddr3_usb3_gated_not_cross_mapped`.
#### `PE-SI-003` lunghezza max mm
- Test: `tests/test_si_check.py` `test_coverage_mdi_rgmii_ddr3_usb3_gated_not_cross_mapped`.
#### `PE-SI-004` spacing intra-coppia mm
Skip senza mm. Copertura indiretta nella stessa suite SI gated.
#### `PE-SI-005` piano di riferimento / layer
Skip senza FACT datasheet.
#### `PE-SI-006` conteggio via HS vs min/max datasheet
Skip senza min/max.
#### `PE-SI-008` return via accanto alla coppia (`return_path`)
Skip senza kind.
#### `PE-SI-009` serie R ohm sul net HS (non EN-RC)
- Test: `tests/test_si_check.py` `test_en_rc_series_resistor_does_not_paint_usb`.
#### `PE-SI-010` bus HS misurato, libreria senza FACT Z → INFO, non FAIL 90 Ω
- Test: `tests/test_si_check.py` `test_usb_without_library_z_is_insufficient_not_90ohm_fail`, `test_empty_net_class_impedance_does_not_paint_usb`; `tests/test_hf_line_check.py` `test_existing_si_usb_not_polluted_by_hf_geometry`.
Refresh extract SI vecchio: `tests/test_pcb_plan_closeout.py` `test_si_refresh_when_old_extract_has_only_decoupling`, `test_si_extract_needed_skips_board_ics_only_not_shared_library`.
### 2.5 Linee HF extra — `hf_line_check.py` (2.62.0 Fase A)
Track ≠ via ≠ pad ≠ zona. Stub = traccia dangling.
#### `PE-SI-007` stub vs mm datasheet
INFO se misura c’è e mm mancano (INSUFFICIENT). Skip: pad-pad, via, zone.
- Test: `tests/test_hf_line_check.py``test_no_layout_skips_hf_geometry`, `test_pad_to_pad_usb_has_no_stub`, `test_via_is_not_a_stub`, `test_zone_is_not_a_track_stub`, `test_dangling_track_stub_vs_datasheet_mm_is_fail`, `test_measured_stub_without_datasheet_mm_is_insufficient`.
#### `PE-SI-011` gap pour GND sotto coppia HF (REVIEW)
Skip senza stackup/zone.
- Test: `tests/test_hf_line_check.py` `test_split_under_pair_needs_stackup_and_zone`.
#### `PE-SI-012` terminazione BOM serie/parallelo FACT
Skip se manca il terminatore (non si inventa 50 Ω).
- Test: `tests/test_hf_line_check.py` `test_bom_termination_is_fact_missing_is_skip`.
Catalogo HF: `tests/test_hf_line_check.py` `test_rule_catalog_hf_ids`. DDR/CPU/FPGA: §1.4 (stesso `check_memory_fpga_classes` in `run_pcb_checks`).
### 2.6 Potenza / via / termico sul board — `pcb_power_thermal.py`
#### `PE-PWR-001` (PCB) — `check_pcb_power_traces`
Larghezza **traccia** × spessore rame vs I_load/Imax/I_abs. IPC-2221 solo con thickness **e** ΔT da Tjmax. Iout_max non è carico. Niente 500 mA USB.
Skip se: manca I datasheet (**skip vero**, non INFO INSUFFICIENT — 2.61.1).
- Test: `tests/test_pcb_review.py``test_power_trace_ipc2221_errors_when_load_exceeds_ampacity`, `test_power_trace_uses_parsed_width_and_thickness`, `test_power_trace_skips_without_i_load`, `test_power_trace_skips_iout_max_as_load`, `test_power_trace_uses_imax_when_i_load_absent`, `test_power_trace_uses_i_abs_when_i_load_absent`; `tests/test_interface_class_check.py` `test_usbc_vbus_does_not_invent_usb_500ma`.
#### `PE-VIA-001` — `check_pcb_via_current`
Se I c’è e sul net **zero via**: INFO conteggio. Se le via ci sono: silenzio — **nessuna tabella ampacity**. Via ≠ traccia.
Skip se: manca I.
- Test: `tests/test_pcb_software_bugs.py` `test_via_current_skips_without_i_load`.
#### `PE-VIA-002` (2.62.1)
I + conteggio/drill FACT. Tabella IPC via **non** applicata.
Skip se: manca I.
- Test: `tests/test_pcb_phase_b.py` `test_via_current_skips_without_i_does_not_invent_ipc`.
#### `PE-THM-001` — `check_pcb_thermal_copper`
P = I_load × drop; courtyard senza pour/via.
Skip se: P sconosciuta.
- Test: `tests/test_pcb_review.py` `test_thermal_copper_warns_without_pour_or_vias`.
#### `PE-THM-002` — `check_pcb_junction_temp`
Tj = Ta + P·θJA solo con P, θJA, area rame, conteggio via. Non è FEM.
Skip/INSUFFICIENT se manca un fattore.
- Test: `tests/test_pcb_plan_closeout.py``test_tj_when_theta_copper_vias_exist`, `test_tj_insufficient_without_theta`.
#### `PE-KEL-001`
Pin Kelvin/sense sullo stesso net di carico. Ignora CRS/IN±.
- Test: `tests/test_pcb_review.py` `test_kelvin_sense_pin_on_shared_net`; `tests/test_pcb_software_bugs.py` `test_kelvin_ignores_crs_and_in_plus`.
#### `PE-STCH-001`
Segnale sopra pour GND senza via GND nel bbox. Skip GPIO minuscoli.
- Test: `tests/test_pcb_review.py``test_gnd_stitch_skips_tiny_gpio`, `test_gnd_stitch_info_when_signal_has_pour_but_no_via`.
### 2.7 Derating / gerarchia / timing / PI
#### `PE-DRT-001` / `PE-DRT-002` / `PE-DRT-003` — `pcb_checks.check_pcb_derating` + `derating.py`
Vop vs Vrated: ERROR / MARGIN / PASS INFO. Niente % dielettrico inventato. Skip se manca Vop o Vr.
- Test: `tests/test_fase_b_checks.py` `test_derating_pass_margin_risk`; `tests/test_derating.py` (7, C_eff stima — non è un PE-* a parte).
#### `PE-HIER-001` — `hierarchy.py`
Pin IC senza net nominato.
- Test: `tests/test_fase_b_checks.py` `test_hierarchy_component_pin_net_block`.
#### `PE-TIM-001` / `PE-TIM-002` — `timing_check.py`
001 RC reset vs t_reset; 002 strap vs Vih/Vil.
Skip senza R/C e t_reset o Vih/Vil.
- 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.
- Test: `tests/test_fase_b_checks.py``test_esd_without_part_is_review_not_error`, `test_esd_skips_gpio_nc_unconnected_and_onboard_power`, `test_esd_one_finding_per_connector_ic_path`, `test_esd_skips_when_protection_part_on_net`.
#### `PE-ESD-002` (2.62.1) — `check_esd_distance`
Distanza **pad** TVSconnettore (via ≠ pad). INFO se no mm.
- Test: `tests/test_pcb_phase_b.py` `test_esd_distance_uses_pads_not_vias`.
#### `PE-RET-001` — `check_return_path`
Net HS sopra pour GND senza via GND nel bbox: REVIEW.
- Test: `tests/test_fase_b_checks.py` `test_return_path_hs_is_review`.
#### `PE-EMI-001` — `emi_check.py`
Solo se `layout_rules` cita choke/ferrite/shield. Non IEC 61000. Skip NC.
- Test: `tests/test_pcb_plan_closeout.py` `test_emi_only_with_datasheet_fact`; `tests/test_pcb_software_bugs.py` `test_emi_and_pi_skip_unconnected_nets`.
#### `PE-SPOF-001` — `spof_check.py`
Un regolatore o XTAL condiviso: REVIEW, mai ERROR.
- Test: `tests/test_pcb_plan_closeout.py` `test_spof_review_single_ldo`.
### 2.9 BOM ↔ PCB ↔ datasheet — `bom_pcb_check.py`
Skip se manca un lato (BOM, footprint, o numero datasheet).
#### `PE-BOM-010` famiglia package
- Test: `tests/test_pcb_plan_closeout.py` `test_package_family_mismatch_is_pe_bom_010`.
#### `PE-BOM-011` pin_count/pintable vs pad PCB
EP/thermal extra e via **non** sono pad. Via ≠ pad.
- Test: `tests/test_pcb_plan_closeout.py` `test_package_family_mismatch_is_pe_bom_010` (stesso test asserisce anche 011); `tests/test_pcb_software_bugs.py` `test_ep_split_pads_not_bom_011`; `tests/test_pcb_via_not_pad.py` (13 test geometria + `test_pcb_pipeline_reparses_stale_layout_graph`); `tests/test_incomplete_pcb.py` `test_partial_board_via_is_not_a_pad`.
#### `PE-BOM-012` Vop vs rating/abs-max legato al **pin**
- Test: `tests/test_pcb_plan_closeout.py` `test_abs_max_voltage_and_temp_and_current`; `tests/test_pcb_software_bugs.py` `test_vddcr_absmax_does_not_bind_3v3_io`.
#### `PE-BOM-013` Top vs abs-max T
- Test: `tests/test_pcb_plan_closeout.py` `test_abs_max_voltage_and_temp_and_current`.
#### `PE-BOM-014` I_load vs rating
Skip se manca I.
- Test: `tests/test_pcb_plan_closeout.py``test_abs_max_voltage_and_temp_and_current`, `test_inductor_current_rating`.
### 2.10 Resto Fase B (2.62.1)
#### `PE-STK-001` — `stackup_check.py`
Spessore rame board vs stackup fab sourced. Mai 1 oz default.
Skip se: niente stackup layout o niente FACT fab.
- Test: `tests/test_pcb_phase_b.py``test_stackup_skips_without_fab_spec`, `test_stackup_vs_fab_spec_is_review_not_invented_oz`.
#### `PE-ANT-001` / `PE-ANT-002` — `antenna_layout_check.py`
Keepout zona e matching L/C sul feed. Solo se antenna/net RF sul grafo. Niente 50 Ω inventati.
Skip se: antenna assente; matching manca e non c’è FACT (002 non emette).
- Test: `tests/test_pcb_phase_b.py``test_antenna_skips_when_absent`, `test_antenna_keepout_and_match_are_facts`.
#### `PE-PDN-001` — `pdn_check.py`
Z(f) solo con FACT in frequenza. Z0 SI **non** è PDN.
- Test: `tests/test_pcb_phase_b.py` `test_pdn_skips_without_zf`.
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.
Skip se: niente JSON libreria per lIC.
- Test: `tests/test_pcb_review.py``test_pcb_ai_finding_gets_action`, `test_parse_review_action_field_without_recommendation`, `test_layout_context_includes_via_counts`, `test_layout_context_includes_domain_and_group`, `test_pcb_ai_skips_without_library_extraction`, `test_library_constraints_fill_from_storage`, `test_inventory_lists_net_length_and_pair`; `tests/test_finding_engine.py` `test_pcb_review_ai_finding_action_without_recommendation`; `tests/test_pcb_software_bugs.py` `test_thinking_reasoning_content_echo_still_present`.
### 2.12 Tab antenna RF (non in `run_pcb_checks`)
Moduli: `antenna_rf.py`, `antenna_geometry.py`. Topologia matching + ricetta routing. No EM/VSWR/CPWG. Distinto da `PE-ANT-001/002`.
- Test: `tests/test_antenna_rf.py``test_verify_finds_matching_path_to_ant_footprint`, `test_verify_missing_matching_is_warning`, `test_design_recipe_needs_marker_without_ant`, `test_design_recipe_ready_with_ant_and_stackup`, `test_geometry_templates_produce_export`, `test_geometry_overflow_tiny_zone`, `test_design_recipe_meander_template`.
Placement/auto-place **non** è esame PCB: `tests/test_placement_pack.py`, `tests/test_placement_pipeline.py` (fuori da questo albero di analisi).
---
## Motore finding (condiviso)
`finding_engine.py`: FACT / REQUIREMENT / INFERENCE; ERROR solo RULE+MANDATORY; `decisions.json` non ri-naga.
- Test: `tests/test_finding_engine.py` (11); `tests/test_finding_schema.py` (7); `tests/test_fase_b_checks.py` `test_sort_findings_error_then_class`; `tests/test_review_workflow.py` (10 — stati finding UI, non check PE). CAD bridge: `tests/test_cad_bridge.py` (5).
---
## Test che esistono ma non sono analisi
`tests/` ha **114 file / ~730 test**. Oltre ai nodi sopra restano overlay nativi, upload, auth, billing seam, UI rewrite. Non sono check PE-*.
**Upload / ingest cartella** (non analisi): `tests/test_periscope_frontend_upload_rewrite.py` `test_upload_zone_is_src`; `tests/test_kicad_project.py` (picker cartella, `.history`, Safari stem); `tests/test_netlist_bundle.py` (zip, zip-slip); `tests/test_kicad_pcb.py` `test_upload_pcb_sets_has_pcb`, `test_upload_sch_as_pcb_is_rejected`; `tests/test_purple_parts_resolver.py` (upload BOM/LCSC); `tests/test_reopen_replace.py` `test_reopen_then_replace_bom_and_netlist`.
**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).
+138
View File
@@ -0,0 +1,138 @@
# Conformità coding — Periscope intero
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 dallordine 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.
## 8. Check deterministici PCB (`pcb_checks.py` + moduli `PE-*`)
**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**.
## 9. Check schematico (derating, LED, crystal, mux, rail, …)
**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.** Lautorità deterministica è nei check; lLLM 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`.
## 11. Estrazione datasheet (skills, `datasheet_extract.py`, `extraction.py`)
**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, limplementazione 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 lempty 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).
## 13. Pipeline orchestrazione (`pipeline.py`, `pcb_pipeline.py`, job)
**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.
## 15. HTTP — progetti, pipeline, report, impedance
**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/schematic/` | net, BOM, review IC, check su grafo |
| `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) |
| Skills/taxonomy/storage/Z0 vendor | sì |
| Dual tree src/dependency | no |
| Pipeline + extract ~1.3k + projects router | no |
| Auth/admin users | no (non toccare) |
| 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.
+37
View File
@@ -0,0 +1,37 @@
# Libreria componenti — porta prodotto
La libreria **non** vive dentro lesame. È una porta a sé: pagina `/library` + API `/api/library`. Lanalisi (preliminare datasheet, schematico, PCB) *usa* la libreria; non è lunico 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 nellanalisi, 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.
+83
View File
@@ -0,0 +1,83 @@
# Criteri Rust — Periscope
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 nellorchestrazione, non nellI/O HTTP, non nellLLM.
4. **Determinismo** — stessa input + config → stesso modello e stessi numeri. Niente hash random, niente ordine di hash map visibile nei finding, niente dipendenza dallLLM.
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.
## Sequenza obbligatoria (costituzione §§3336)
```text
implementazione corretta in Python
misura (wall + allocazioni, board reale: HubAudio / Emmaforo)
profiling (il hot path ha un nome di funzione)
scelta esplicita del pezzo in questo file
Rust con confine grosso
benchmark sulla stessa board
accetta / rifiuta sui numeri e sulla semantica (via resta via)
```
Finché la riga “Pezzo scelto” sotto è vuota: **nessun rustup**.
## Cosa non va in Rust
| Area | Perché |
| --- | --- |
| Pipeline, job, SSE, FastAPI | orchestrazione |
| Estrazione datasheet, skills, LLM | I/O + modello |
| Review AI, normalize, dedup | non deterministico in autorità |
| Project CRUD, storage, report JSON | I/O |
| Frontend | altro runtime |
| Auth / JWT / users | fuori perimetro; non toccare |
| FEM, OpenEMS, ampacity IPC via, DRC | non è Periscope |
| ImpedenceFinder venduto | già un core chiuso; non riscrivere “per Rust” |
| Un check `PE-*` isolato | il check è regola + evidenza; il costo è il parser/geometria a monte |
## Candidati *solo dopo misura* (nessuno scelto)
Questi sono i posti dove un profilo *potrebbe* mostrare un hot path. Non sono un piano di porting.
- Parser `.kicad_pcb` (`parsers_kicad_pcb.py`) se il wall su HubAudio è il parse, non i check.
- Predicate geometriche usate da stub / zone / courtyard (track graph, point-in-poly) se il walk segmenti domina.
- Walk grafo net→pin su BOM grandi, se misurato.
Non candidati: `run_pcb_checks` come god-object Rust; “tutto il core”; Z inventata più veloce.
## Pezzo scelto
Nessuno.
Nessuna misura registrata in questo file. Quindi: niente toolchain Rust in questa macro-fase.
## Come registrare una scelta (futuro)
Quando una misura esiste, aggiungere qui:
```text
Pezzo: <modulo / funzione>
Board: <HubAudio / Emmaforo / …>
Python wall: <s> alloc: <MB>
Hot path: <nome>
Ipotesi Rust: correttezza geometrica | memoria | parser | determinismo | confine
Benchmark dopo: <s> e test via≠pad ancora verdi
Decisione: accetta | rifiuta
```