Rebrand Pinscope to Periscope across product and codebase.
Rename the core package to periscopex, update UI/docs/Docker/deploy defaults to periscope.michelebigi.it, and keep legacy version/storage key aliases so existing projects keep working. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.antenna_rf import build_antenna_report, build_design_recipe
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.antenna_rf import build_antenna_report, build_design_recipe
|
||||
from backend.periscopex.models import (
|
||||
CapacitorSpecs,
|
||||
Component,
|
||||
ComponentType,
|
||||
@@ -187,7 +187,7 @@ def test_design_recipe_ready_with_ant_and_stackup():
|
||||
|
||||
|
||||
def test_geometry_templates_produce_export():
|
||||
from backend.pinscopex.antenna_geometry import build_geometry
|
||||
from backend.periscopex.antenna_geometry import build_geometry
|
||||
|
||||
for tmpl in ("ifa", "meander", "stub"):
|
||||
geo = build_geometry(tmpl, f0_mhz=2440.0, w_mm=0.4, er=4.5)
|
||||
@@ -201,7 +201,7 @@ def test_geometry_templates_produce_export():
|
||||
|
||||
|
||||
def test_geometry_overflow_tiny_zone():
|
||||
from backend.pinscopex.antenna_geometry import build_geometry
|
||||
from backend.periscopex.antenna_geometry import build_geometry
|
||||
|
||||
geo = build_geometry(
|
||||
"ifa",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""BOM vs schematic MPN/value match.
|
||||
|
||||
Favor: identical MPNs silent; real mismatch is ERROR PS-BOM-001 with
|
||||
Favor: identical MPNs silent; real mismatch is ERROR PE-BOM-001 with
|
||||
designator; orphan BOM line is WARNING.
|
||||
Against: case/whitespace-only MPN is not a mismatch; empty schematic map
|
||||
skips the check (PADS path); BOM-empty + schematic MPN is fill, not ERROR.
|
||||
@@ -8,8 +8,8 @@ skips the check (PADS path); BOM-empty + schematic MPN is fill, not ERROR.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.bom_match_check import check_bom_schematic_match
|
||||
from backend.pinscopex.models import DesignGraph
|
||||
from backend.periscopex.bom_match_check import check_bom_schematic_match
|
||||
from backend.periscopex.models import DesignGraph
|
||||
|
||||
|
||||
def test_matching_mpns_produce_no_findings():
|
||||
@@ -24,7 +24,7 @@ def test_mpn_mismatch_is_error_ps_bom_001():
|
||||
findings = check_bom_schematic_match(sch, bom)
|
||||
assert len(findings) == 1
|
||||
f = findings[0]
|
||||
assert f.rule_id == "PS-BOM-001"
|
||||
assert f.rule_id == "PE-BOM-001"
|
||||
assert f.source == "bom_match"
|
||||
assert f.status == "ERROR"
|
||||
assert f.designator == "U1"
|
||||
@@ -40,7 +40,7 @@ def test_orphan_bom_ref_is_warning():
|
||||
}
|
||||
findings = check_bom_schematic_match(sch, bom)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-BOM-002"
|
||||
assert findings[0].rule_id == "PE-BOM-002"
|
||||
assert findings[0].status == "WARNING"
|
||||
assert findings[0].designator == "R99"
|
||||
|
||||
@@ -75,7 +75,7 @@ def test_legacy_design_graph_without_source_fields_still_validates():
|
||||
|
||||
|
||||
def test_build_graph_kicad_mpn_mismatch_surfaces(tmp_path):
|
||||
from backend.pinscopex.graph import build_graph
|
||||
from backend.periscopex.graph import build_graph
|
||||
|
||||
net = tmp_path / "net.xml"
|
||||
net.write_text(
|
||||
@@ -103,5 +103,5 @@ def test_build_graph_kicad_mpn_mismatch_surfaces(tmp_path):
|
||||
)
|
||||
findings = check_bom_schematic_match(g.schematic_fields, g.bom_fields)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-BOM-001"
|
||||
assert findings[0].rule_id == "PE-BOM-001"
|
||||
assert findings[0].designator == "U1"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.parsers import parse_bom
|
||||
from backend.periscopex.parsers import parse_bom
|
||||
|
||||
|
||||
def test_parse_bom_uses_value_for_ic_when_mpn_column_empty(tmp_path: Path):
|
||||
|
||||
@@ -4,9 +4,9 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.cad_bridge import annotate_findings_cad, build_cad_bridge
|
||||
from backend.pinscopex.models import CadIndexEntry, Finding, ValidationReport
|
||||
from backend.pinscopex.parsers_kicad import kicad_part_fields, parse_kicad
|
||||
from backend.periscopex.cad_bridge import annotate_findings_cad, build_cad_bridge
|
||||
from backend.periscopex.models import CadIndexEntry, Finding, ValidationReport
|
||||
from backend.periscopex.parsers_kicad import kicad_part_fields, parse_kicad
|
||||
from plugins.kicad.focus import find_bridge_file, focus_target, load_bridge
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ def test_bridge_exports_version_and_strips_pin_prefix():
|
||||
report = ValidationReport(
|
||||
project="p", timestamp="t",
|
||||
findings=[_f(
|
||||
finding_id="U3-001", rule_id="PS-MUX-001",
|
||||
finding_id="U3-001", rule_id="PE-MUX-001",
|
||||
pins=["U3.12"], cad_sheet="power.kicad_sch",
|
||||
cad_uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
net="UART5_TX",
|
||||
@@ -42,7 +42,7 @@ def test_bridge_exports_version_and_strips_pin_prefix():
|
||||
def test_missing_uuid_stays_empty_and_pcb_rule_targets_board():
|
||||
report = ValidationReport(
|
||||
project="p", timestamp="t",
|
||||
findings=[_f(rule_id="PS-PLC-001", pins=["1"], status="ERROR")],
|
||||
findings=[_f(rule_id="PE-PLC-001", pins=["1"], status="ERROR")],
|
||||
summary={"total": 1},
|
||||
)
|
||||
row = build_cad_bridge(report, "x")["findings"][0]
|
||||
@@ -92,15 +92,15 @@ def test_kicad_sch_fields_include_uuid_and_child_sheet(tmp_path: Path):
|
||||
|
||||
|
||||
def test_plugin_finds_bridge_and_pcb_target(tmp_path: Path):
|
||||
(tmp_path / "pinscope-findings.json").write_text(
|
||||
(tmp_path / "periscope-findings.json").write_text(
|
||||
'{"version":1,"project_id":"p","findings":[]}\n'
|
||||
)
|
||||
nested = tmp_path / "board"
|
||||
nested.mkdir()
|
||||
found = find_bridge_file(nested / "x.kicad_pcb")
|
||||
assert found == tmp_path / "pinscope-findings.json"
|
||||
assert found == tmp_path / "periscope-findings.json"
|
||||
assert load_bridge(found)["version"] == 1
|
||||
t = focus_target({"ref": "U1", "rule_id": "PS-PLC-001", "uuid": "x", "sheet": ""})
|
||||
t = focus_target({"ref": "U1", "rule_id": "PE-PLC-001", "uuid": "x", "sheet": ""})
|
||||
assert t["kind"] == "pcb" and t["ref"] == "U1"
|
||||
missing = tmp_path / "nowhere"
|
||||
missing.mkdir()
|
||||
|
||||
@@ -74,7 +74,7 @@ def test_estimator_counts_library_cache_hits(storage, tmp_path):
|
||||
if not ic_items:
|
||||
pytest.skip("fixture has no uncached IC")
|
||||
target_mpn = ic_items[0].identifier
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
|
||||
storage.write_json(
|
||||
f"library/extracted/{safe_mpn(target_mpn)}.json",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.crystal_cl_check import check_crystal_cl
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.crystal_cl_check import check_crystal_cl
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentType,
|
||||
DesignGraph,
|
||||
@@ -79,19 +79,19 @@ def test_series_above_cl_without_stray_warns():
|
||||
# 100p || 100p = 50p > CL 18p * 1.25
|
||||
g = _xtal_graph(cl_f=18e-12, c1="100p", c2="100p")
|
||||
findings = check_crystal_cl(g)
|
||||
assert any(f.rule_id == "PS-XTAL-002" for f in findings)
|
||||
assert any(f.rule_id == "PE-XTAL-002" for f in findings)
|
||||
|
||||
|
||||
def test_with_stray_mismatch_warns():
|
||||
# 18p||18p = 9p + 2p stray = 11p vs CL 18p → below 0.75*18
|
||||
g = _xtal_graph(cl_f=18e-12, c1="18p", c2="18p", stray=2e-12)
|
||||
findings = check_crystal_cl(g)
|
||||
assert any(f.rule_id == "PS-XTAL-002" for f in findings)
|
||||
assert any(f.rule_id == "PE-XTAL-002" for f in findings)
|
||||
|
||||
|
||||
def test_simple_project_without_cl_silent():
|
||||
from pathlib import Path
|
||||
from backend.pinscopex.models import DesignGraph
|
||||
from backend.periscopex.models import DesignGraph
|
||||
path = Path(__file__).resolve().parents[1] / "simple_project" / "design_graph.json"
|
||||
g = DesignGraph.model_validate_json(path.read_text())
|
||||
assert check_crystal_cl(g) == []
|
||||
|
||||
@@ -8,9 +8,9 @@ from pathlib import Path
|
||||
import pytest
|
||||
from pypdf import PdfWriter
|
||||
|
||||
from backend.pinscopex.models import DesignGraph
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.pinscopex.validation_tools import (
|
||||
from backend.periscopex.models import DesignGraph
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
from backend.periscopex.validation_tools import (
|
||||
EXCERPT_TOPICS,
|
||||
ExcerptState,
|
||||
execute_tool,
|
||||
|
||||
@@ -15,7 +15,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from backend.pinscopex.models import Finding
|
||||
from backend.periscopex.models import Finding
|
||||
from backend.services.dedupe_findings import (
|
||||
SUBMIT_DEDUPED_SCHEMA,
|
||||
_build_deduped,
|
||||
@@ -78,13 +78,13 @@ def test_dedupe_passthrough_keeps_cad_fields():
|
||||
reference="ref 1",
|
||||
net="USB_D+",
|
||||
pins=["U2.1"],
|
||||
rule_id="PS-USB-001",
|
||||
rule_id="PE-USB-001",
|
||||
)
|
||||
]
|
||||
groups = [{"member_indices": [1], "change_rationale": "passthrough"}]
|
||||
built = _build_deduped(groups, originals)
|
||||
assert built is not None
|
||||
assert built[0].rule_id == "PS-USB-001"
|
||||
assert built[0].rule_id == "PE-USB-001"
|
||||
assert built[0].net == "USB_D+"
|
||||
assert built[0].pins == ["U2.1"]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Pinscope LLM routing is DeepSeek only.
|
||||
"""Periscope LLM routing is DeepSeek only.
|
||||
|
||||
Favor: every pipeline stage uses DeepSeek even if PROVIDER_* is set to
|
||||
anthropic; model_for_stage stays on deepseek-flash.
|
||||
|
||||
@@ -7,11 +7,11 @@ C0G is not treated as X7R.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.derating import (
|
||||
from backend.periscopex.derating import (
|
||||
build_derating_table,
|
||||
dc_bias_remaining,
|
||||
)
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
CapacitorSpecs,
|
||||
Component,
|
||||
ComponentType,
|
||||
|
||||
@@ -4,8 +4,8 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.dnp_check import check_dnp_enables
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.dnp_check import check_dnp_enables
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
@@ -16,7 +16,7 @@ from backend.pinscopex.models import (
|
||||
PinConnection,
|
||||
ResistorSpecs,
|
||||
)
|
||||
from backend.pinscopex.parsers import parse_bom
|
||||
from backend.periscopex.parsers import parse_bom
|
||||
|
||||
|
||||
def _graph(components, nets, bom_fields=None):
|
||||
@@ -78,7 +78,7 @@ def test_dnp_pull_leaves_enable_floating():
|
||||
)
|
||||
findings = check_dnp_enables(g, _cons())
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-DNP-001"
|
||||
assert findings[0].rule_id == "PE-DNP-001"
|
||||
assert findings[0].status == "ERROR"
|
||||
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.pinscopex.parsers import (
|
||||
from backend.periscopex.parsers import (
|
||||
detect_netlist_format,
|
||||
parse_netlist_any,
|
||||
validate_netlist,
|
||||
)
|
||||
from backend.pinscopex.parsers_edif import (
|
||||
from backend.periscopex.parsers_edif import (
|
||||
list_edif_subdesigns,
|
||||
parse_edif_netlist,
|
||||
)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
"""Errata catalog — workaround on the graph, no scraping.
|
||||
|
||||
Favor: known MPN with a pull-up workaround missing on the net → PS-ERRATA-001.
|
||||
Favor: known MPN with a pull-up workaround missing on the net → PE-ERRATA-001.
|
||||
Against: MPN not in catalog is silent (even TI-looking); workaround pull-up
|
||||
present is silent; catalog entry without url is skipped.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.errata_check import check_errata
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.errata_check import check_errata
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
@@ -76,7 +76,7 @@ CATALOG = {
|
||||
def test_missing_errata_pullup_is_ps_errata_001():
|
||||
findings = check_errata(_graph(False), _cons(), CATALOG)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-ERRATA-001"
|
||||
assert findings[0].rule_id == "PE-ERRATA-001"
|
||||
assert findings[0].status == "WARNING"
|
||||
assert findings[0].source == "errata_check"
|
||||
assert "ti.com/lit/er" in findings[0].reference
|
||||
|
||||
+10
-10
@@ -10,13 +10,13 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.eval_report import (
|
||||
from backend.periscopex.eval_report import (
|
||||
citation_hit_rate,
|
||||
eval_simple_project,
|
||||
finding_key,
|
||||
score_report,
|
||||
)
|
||||
from backend.pinscopex.models import Finding
|
||||
from backend.periscopex.models import Finding
|
||||
|
||||
|
||||
def _f(**kwargs) -> Finding:
|
||||
@@ -26,7 +26,7 @@ def _f(**kwargs) -> Finding:
|
||||
|
||||
|
||||
def test_perfect_match_is_precision_and_recall_one():
|
||||
f = _f(rule_id="PS-I2C-001", designator="U3", net="/I2C0.SDA")
|
||||
f = _f(rule_id="PE-I2C-001", designator="U3", net="/I2C0.SDA")
|
||||
scores = score_report([f], {finding_key(f)})
|
||||
assert scores.precision == 1.0
|
||||
assert scores.recall == 1.0
|
||||
@@ -36,18 +36,18 @@ def test_perfect_match_is_precision_and_recall_one():
|
||||
|
||||
|
||||
def test_extra_finding_drops_precision_not_recall():
|
||||
gold = _f(rule_id="PS-I2C-001", designator="U3", net="/I2C0.SDA")
|
||||
extra = _f(rule_id="PS-BOM-001", designator="U1", net="")
|
||||
gold = _f(rule_id="PE-I2C-001", designator="U3", net="/I2C0.SDA")
|
||||
extra = _f(rule_id="PE-BOM-001", designator="U1", net="")
|
||||
scores = score_report([gold, extra], {finding_key(gold)})
|
||||
assert scores.recall == 1.0
|
||||
assert scores.precision == 0.5
|
||||
assert scores.extra_keys == ["PS-BOM-001|U1|"]
|
||||
assert scores.extra_keys == ["PE-BOM-001|U1|"]
|
||||
|
||||
|
||||
def test_missing_golden_key_drops_recall():
|
||||
gold_a = "PS-I2C-001|U3|/I2C0.SDA"
|
||||
gold_b = "PS-I2C-001|U3|/I2C0.SCL"
|
||||
produced = [_f(rule_id="PS-I2C-001", designator="U3", net="/I2C0.SDA")]
|
||||
gold_a = "PE-I2C-001|U3|/I2C0.SDA"
|
||||
gold_b = "PE-I2C-001|U3|/I2C0.SCL"
|
||||
produced = [_f(rule_id="PE-I2C-001", designator="U3", net="/I2C0.SDA")]
|
||||
scores = score_report([produced[0]], {gold_a, gold_b})
|
||||
assert scores.precision == 1.0
|
||||
assert scores.recall == 0.5
|
||||
@@ -57,7 +57,7 @@ def test_missing_golden_key_drops_recall():
|
||||
def test_citation_rate_ignores_deterministic_and_counts_unverified():
|
||||
det = _f(
|
||||
source="i2c_pullup_check",
|
||||
rule_id="PS-I2C-001",
|
||||
rule_id="PE-I2C-001",
|
||||
source_quote="ignored because deterministic",
|
||||
why="no pull-up",
|
||||
)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.filter_check import check_filters
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.filter_check import check_filters
|
||||
from backend.periscopex.models import (
|
||||
CapacitorSpecs,
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
@@ -90,7 +90,7 @@ def test_rc_reports_fc_info_without_adc_rate():
|
||||
)
|
||||
findings = check_filters(g)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-FLT-001"
|
||||
assert findings[0].rule_id == "PE-FLT-001"
|
||||
assert findings[0].status == "INFO"
|
||||
assert findings[0].source == "filter_check"
|
||||
|
||||
@@ -110,7 +110,7 @@ def test_rc_vs_adc_rate_is_warning():
|
||||
)
|
||||
findings = check_filters(g)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-FLT-002"
|
||||
assert findings[0].rule_id == "PE-FLT-002"
|
||||
assert findings[0].status == "WARNING"
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ def test_missing_c_value_does_not_invent_fc_warning():
|
||||
)
|
||||
findings = check_filters(g)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-FLT-001"
|
||||
assert findings[0].rule_id == "PE-FLT-001"
|
||||
assert findings[0].status == "INFO"
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ def test_pi_and_t_need_l_and_c():
|
||||
},
|
||||
)
|
||||
pi = check_filters(g_pi)
|
||||
assert len(pi) == 1 and pi[0].rule_id == "PS-FLT-001" and "π" in pi[0].finding
|
||||
assert len(pi) == 1 and pi[0].rule_id == "PE-FLT-001" and "π" in pi[0].finding
|
||||
|
||||
g_t = _graph(
|
||||
{
|
||||
@@ -212,7 +212,7 @@ def test_ferrite_dcr_warns_only_with_datasheet_limit():
|
||||
"GND": (NetType.GROUND, [("U1", "2"), ("C1", "2")]),
|
||||
},
|
||||
)
|
||||
assert not any(f.rule_id == "PS-FLT-003" for f in check_filters(g, cons))
|
||||
assert not any(f.rule_id == "PE-FLT-003" for f in check_filters(g, cons))
|
||||
|
||||
g2 = _graph(
|
||||
{
|
||||
@@ -226,5 +226,5 @@ def test_ferrite_dcr_warns_only_with_datasheet_limit():
|
||||
"GND": (NetType.GROUND, [("U1", "2"), ("C1", "2")]),
|
||||
},
|
||||
)
|
||||
dcr = [f for f in check_filters(g2, cons) if f.rule_id == "PS-FLT-003"]
|
||||
dcr = [f for f in check_filters(g2, cons) if f.rule_id == "PE-FLT-003"]
|
||||
assert len(dcr) == 1 and dcr[0].status == "WARNING"
|
||||
|
||||
@@ -13,7 +13,7 @@ import json
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from backend.pinscopex.models import Finding
|
||||
from backend.periscopex.models import Finding
|
||||
|
||||
|
||||
def test_legacy_json_without_new_fields_still_validates():
|
||||
@@ -42,7 +42,7 @@ def test_new_fields_round_trip_json():
|
||||
status="WARNING",
|
||||
net="VIN",
|
||||
pins=["U1.1", "C1.1"],
|
||||
rule_id="PS-DEC-001",
|
||||
rule_id="PE-DEC-001",
|
||||
cad_sheet="power.kicad_sch",
|
||||
cad_uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
variant="DNP",
|
||||
@@ -51,7 +51,7 @@ def test_new_fields_round_trip_json():
|
||||
again = Finding.model_validate(dumped)
|
||||
assert again.net == "VIN"
|
||||
assert again.pins == ["U1.1", "C1.1"]
|
||||
assert again.rule_id == "PS-DEC-001"
|
||||
assert again.rule_id == "PE-DEC-001"
|
||||
assert again.cad_sheet == "power.kicad_sch"
|
||||
assert again.cad_uuid == "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
|
||||
assert again.variant == "DNP"
|
||||
|
||||
@@ -5,8 +5,8 @@ from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.functional_groups import build_functional_groups
|
||||
from backend.pinscopex.models import DesignGraph
|
||||
from backend.periscopex.functional_groups import build_functional_groups
|
||||
from backend.periscopex.models import DesignGraph
|
||||
|
||||
SIMPLE = Path(__file__).resolve().parents[1] / "simple_project"
|
||||
|
||||
@@ -69,7 +69,7 @@ def test_simple_project_splits_5v_and_3v3_domains():
|
||||
|
||||
def test_ldo_power_satellites_stay_on_primary_rail():
|
||||
"""LDO input-rail caps must not appear as primary-rail satellites."""
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
CapacitorSpecs,
|
||||
Component,
|
||||
ComponentType,
|
||||
@@ -203,7 +203,7 @@ def test_ldo_power_satellites_stay_on_primary_rail():
|
||||
|
||||
def test_multi_rail_board_does_not_collapse_to_one_domain():
|
||||
"""Charger→LDO→MCU must not become a single domain via shared POWER nets."""
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentType,
|
||||
DesignGraph,
|
||||
@@ -279,7 +279,7 @@ def test_multi_rail_board_does_not_collapse_to_one_domain():
|
||||
|
||||
|
||||
def test_build_placement_plan_alias():
|
||||
from backend.pinscopex.functional_groups import build_placement_plan
|
||||
from backend.periscopex.functional_groups import build_placement_plan
|
||||
report = build_placement_plan(_graph())
|
||||
assert report.objective == "routing"
|
||||
assert {g.ref for g in report.groups} >= {"U1", "U2", "U3"}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""HF coverage INFO when bulk C exists without a 100 nF-class ceramic."""
|
||||
|
||||
from backend.pinscopex.hf_coverage_check import check_hf_decoupling_coverage
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.hf_coverage_check import check_hf_decoupling_coverage
|
||||
from backend.periscopex.models import (
|
||||
CapacitorSpecs,
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
@@ -62,7 +62,7 @@ def test_bulk_only_is_info_ps_esr_001():
|
||||
)
|
||||
findings = check_hf_decoupling_coverage(g, _cmap())
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-ESR-001"
|
||||
assert findings[0].rule_id == "PE-ESR-001"
|
||||
assert findings[0].status == "INFO"
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""D1 impedance — ImpedenceFinder closed forms, no second formula set.
|
||||
|
||||
Favor: Pinscope Z0 equals vendored ImpedenceFinder bit-for-bit; classic
|
||||
Favor: Periscope Z0 equals vendored ImpedenceFinder bit-for-bit; classic
|
||||
3 mm / 1.6 mm FR4 is ~50 Ω; solve_width round-trips.
|
||||
Against: h<=0 invents nothing; CPWG stays unimplemented; stripline t=0
|
||||
raises; calculator emits no findings. OpenEMS is not imported.
|
||||
@@ -11,7 +11,7 @@ from __future__ import annotations
|
||||
import pytest
|
||||
|
||||
from impedancefinder import zsolver as ifz
|
||||
from backend.pinscopex.impedance import (
|
||||
from backend.periscopex.impedance import (
|
||||
GeometryError,
|
||||
TraceGeometry,
|
||||
coupled_diff_z,
|
||||
@@ -105,8 +105,8 @@ def test_stackup_rejects_non_positive_h():
|
||||
|
||||
def test_kicad_dru_is_advice_not_a_finding():
|
||||
dru = export_kicad_dru(stackup_targets(h=0.20, er=4.5, t=0.035, s=0.20))
|
||||
assert "(rule PINSCOPE_50OHM" in dru
|
||||
assert "PS-Z" not in dru
|
||||
assert "(rule PERISCOPE_50OHM" in dru
|
||||
assert "PE-Z" not in dru
|
||||
|
||||
|
||||
def _impedance_client():
|
||||
@@ -163,5 +163,5 @@ def test_api_stackup_returns_dru_not_findings():
|
||||
assert res.status_code == 200
|
||||
body = res.json()
|
||||
assert body["targets"]["microstrip_50"]["z0"] == pytest.approx(50.0, rel=0.02)
|
||||
assert "(rule PINSCOPE_50OHM" in body["kicad_dru"]
|
||||
assert "(rule PERISCOPE_50OHM" in body["kicad_dru"]
|
||||
assert "findings" not in body
|
||||
|
||||
@@ -10,12 +10,12 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.pinscopex.impedance import GeometryError
|
||||
from backend.pinscopex.impedance_traces import (
|
||||
from backend.periscopex.impedance import GeometryError
|
||||
from backend.periscopex.impedance_traces import (
|
||||
analyze_specified_nets,
|
||||
analyze_where_needed,
|
||||
)
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
DesignGraph,
|
||||
LayoutDielectric,
|
||||
LayoutGraph,
|
||||
@@ -25,7 +25,7 @@ from backend.pinscopex.models import (
|
||||
Net,
|
||||
NetType,
|
||||
)
|
||||
from backend.pinscopex.parsers_kicad_pcb import parse_kicad_pcb
|
||||
from backend.periscopex.parsers_kicad_pcb import parse_kicad_pcb
|
||||
from backend.vendor_path import ensure_impedancefinder
|
||||
|
||||
ensure_impedancefinder()
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
"""Internal features (block-diagram extraction) — open-drain pull-up.
|
||||
|
||||
Favor: pin listed in pullup_pins with no resistor to a rail → PS-INT-001.
|
||||
Favor: pin listed in pullup_pins with no resistor to a rail → PE-INT-001.
|
||||
Against: empty internal_features is silent; listed pin with a pull-up is
|
||||
silent; a pin not in pullup_pins is not guessed as open-drain.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.internal_features_check import check_internal_features
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.internal_features_check import check_internal_features
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
@@ -69,7 +69,7 @@ def test_listed_open_drain_without_pull_is_warning():
|
||||
feats = InternalFeatures(pullup_pins=["SDA"])
|
||||
findings = check_internal_features(_graph(False), _cons(feats))
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-INT-001"
|
||||
assert findings[0].rule_id == "PE-INT-001"
|
||||
assert findings[0].status == "WARNING"
|
||||
|
||||
|
||||
|
||||
+13
-13
@@ -1,6 +1,6 @@
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.parsers import detect_netlist_format, parse_netlist_any, validate_netlist
|
||||
from backend.periscopex.parsers import detect_netlist_format, parse_netlist_any, validate_netlist
|
||||
|
||||
|
||||
XML = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -93,14 +93,14 @@ def test_parse_kicad_sexp_and_mpn_fields(tmp_path: Path):
|
||||
parts, nets, fmt = parse_netlist_any(p)
|
||||
assert fmt == "kicad_sexp"
|
||||
assert ("U1", "1") in nets["3V3"]
|
||||
from backend.pinscopex.parsers_kicad import kicad_part_fields
|
||||
from backend.periscopex.parsers_kicad import kicad_part_fields
|
||||
fields = kicad_part_fields(p)
|
||||
assert fields["U1"]["mpn"] == "MSPM0G3507SPTR"
|
||||
assert fields["U1"]["lcsc"] == "C12345"
|
||||
|
||||
|
||||
def test_kicad_mpn_fills_empty_bom(tmp_path: Path):
|
||||
from backend.pinscopex.graph import build_graph
|
||||
from backend.periscopex.graph import build_graph
|
||||
|
||||
net = tmp_path / "net.xml"
|
||||
net.write_text(XML)
|
||||
@@ -180,7 +180,7 @@ def _sch(*body: str) -> str:
|
||||
|
||||
def test_power_symbols_same_name_merge_without_wires(tmp_path: Path):
|
||||
"""KiCad power flags are global: two GND symbols share one net even if islands."""
|
||||
from backend.pinscopex.parsers import parse_netlist_any
|
||||
from backend.periscopex.parsers import parse_netlist_any
|
||||
|
||||
p = tmp_path / "power.kicad_sch"
|
||||
# R1 and R2 far apart, each with GND on pin 1, no wires between them.
|
||||
@@ -200,7 +200,7 @@ def test_power_symbols_same_name_merge_without_wires(tmp_path: Path):
|
||||
|
||||
|
||||
def test_local_labels_same_name_merge_on_same_sheet(tmp_path: Path):
|
||||
from backend.pinscopex.parsers import parse_netlist_any
|
||||
from backend.periscopex.parsers import parse_netlist_any
|
||||
|
||||
p = tmp_path / "local.kicad_sch"
|
||||
p.write_text(_sch(
|
||||
@@ -219,7 +219,7 @@ def test_local_labels_same_name_merge_on_same_sheet(tmp_path: Path):
|
||||
|
||||
|
||||
def test_pin_on_mid_wire_segment_connects(tmp_path: Path):
|
||||
from backend.pinscopex.parsers import parse_netlist_any
|
||||
from backend.periscopex.parsers import parse_netlist_any
|
||||
|
||||
p = tmp_path / "midwire.kicad_sch"
|
||||
# Horizontal wire from (-10,3.81) to (10,3.81); R1 pin1 at (0,3.81) sits mid-segment.
|
||||
@@ -237,7 +237,7 @@ def test_pin_on_mid_wire_segment_connects(tmp_path: Path):
|
||||
|
||||
|
||||
def test_parse_single_sheet_kicad_sch(tmp_path: Path):
|
||||
from backend.pinscopex.parsers import parse_netlist_any
|
||||
from backend.periscopex.parsers import parse_netlist_any
|
||||
|
||||
p = tmp_path / "one.kicad_sch"
|
||||
p.write_text(_sch(
|
||||
@@ -253,7 +253,7 @@ def test_parse_single_sheet_kicad_sch(tmp_path: Path):
|
||||
|
||||
|
||||
def test_hierarchical_global_gnd_merges_across_sheets(tmp_path: Path):
|
||||
from backend.pinscopex.parsers import parse_netlist_any
|
||||
from backend.periscopex.parsers import parse_netlist_any
|
||||
|
||||
child = tmp_path / "child.kicad_sch"
|
||||
child.write_text(_sch(
|
||||
@@ -283,7 +283,7 @@ def test_hierarchical_global_gnd_merges_across_sheets(tmp_path: Path):
|
||||
|
||||
|
||||
def test_hierarchical_label_connects_through_sheet_pin(tmp_path: Path):
|
||||
from backend.pinscopex.parsers import parse_netlist_any
|
||||
from backend.periscopex.parsers import parse_netlist_any
|
||||
|
||||
child = tmp_path / "analog.kicad_sch"
|
||||
child.write_text(_sch(
|
||||
@@ -313,7 +313,7 @@ def test_hierarchical_label_connects_through_sheet_pin(tmp_path: Path):
|
||||
|
||||
|
||||
def test_local_labels_same_name_do_not_merge_across_sheets(tmp_path: Path):
|
||||
from backend.pinscopex.parsers import parse_netlist_any
|
||||
from backend.periscopex.parsers import parse_netlist_any
|
||||
|
||||
child = tmp_path / "child.kicad_sch"
|
||||
child.write_text(_sch(
|
||||
@@ -345,7 +345,7 @@ def test_local_labels_same_name_do_not_merge_across_sheets(tmp_path: Path):
|
||||
|
||||
def test_sheetfile_parent_traversal_is_rejected(tmp_path: Path):
|
||||
import pytest
|
||||
from backend.pinscopex.parsers_kicad import parse_kicad
|
||||
from backend.periscopex.parsers_kicad import parse_kicad
|
||||
|
||||
root = tmp_path / "root.kicad_sch"
|
||||
root.write_text(_sch(
|
||||
@@ -366,7 +366,7 @@ def test_sheetfile_parent_traversal_is_rejected(tmp_path: Path):
|
||||
|
||||
def test_missing_child_sheet_raises(tmp_path: Path):
|
||||
import pytest
|
||||
from backend.pinscopex.parsers_kicad import parse_kicad
|
||||
from backend.periscopex.parsers_kicad import parse_kicad
|
||||
|
||||
root = tmp_path / "root.kicad_sch"
|
||||
root.write_text(_sch(
|
||||
@@ -387,7 +387,7 @@ def test_missing_child_sheet_raises(tmp_path: Path):
|
||||
|
||||
def test_cyclic_sheet_include_is_rejected(tmp_path: Path):
|
||||
import pytest
|
||||
from backend.pinscopex.parsers_kicad import parse_kicad
|
||||
from backend.periscopex.parsers_kicad import parse_kicad
|
||||
|
||||
child = tmp_path / "child.kicad_sch"
|
||||
child.write_text(_sch(
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.pinscopex.parsers_kicad_pcb import parse_kicad_pcb
|
||||
from backend.periscopex.parsers_kicad_pcb import parse_kicad_pcb
|
||||
|
||||
_PCB = """(kicad_pcb (version 20240108) (generator pcbnew)
|
||||
(net 0 "")
|
||||
@@ -106,7 +106,7 @@ _PCB_V10 = """(kicad_pcb (version 20260206) (generator pcbnew)
|
||||
|
||||
|
||||
def test_kicad10_pad_net_string_form(tmp_path: Path):
|
||||
from backend.pinscopex.parsers_kicad_pcb import nets_from_pcb
|
||||
from backend.periscopex.parsers_kicad_pcb import nets_from_pcb
|
||||
|
||||
p = tmp_path / "v10.kicad_pcb"
|
||||
p.write_text(_PCB_V10)
|
||||
@@ -120,7 +120,7 @@ def test_kicad10_pad_net_string_form(tmp_path: Path):
|
||||
|
||||
def test_build_graph_prefers_pcb_nets_over_sch(tmp_path: Path):
|
||||
"""Board pad nets win when sch geometry would swap rails."""
|
||||
from backend.pinscopex.graph import build_graph
|
||||
from backend.periscopex.graph import build_graph
|
||||
|
||||
sch = tmp_path / "netlist.kicad_sch"
|
||||
# Minimal sch: only needs to parse as kicad_sch with some parts.
|
||||
|
||||
@@ -6,7 +6,7 @@ Against: unknown kind rejected; non-numeric distance becomes null.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.layout_rules import validate_layout_rules
|
||||
from backend.periscopex.layout_rules import validate_layout_rules
|
||||
|
||||
|
||||
def test_numeric_max_distance_mm_is_kept():
|
||||
@@ -34,7 +34,7 @@ def test_empty_list_is_explicit_skip():
|
||||
|
||||
|
||||
def test_needs_refresh_when_empty_and_old_version():
|
||||
from backend.pinscopex.layout_rules import needs_layout_rules_refresh
|
||||
from backend.periscopex.layout_rules import needs_layout_rules_refresh
|
||||
|
||||
assert needs_layout_rules_refresh(
|
||||
{"model_version": "1.9.0", "layout_rules": []},
|
||||
|
||||
@@ -9,7 +9,7 @@ Locks in:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentType,
|
||||
DesignGraph,
|
||||
@@ -19,7 +19,7 @@ from backend.pinscopex.models import (
|
||||
SimpleComponentSpecs,
|
||||
PinConnection,
|
||||
)
|
||||
from backend.pinscopex.led_current_check import check_led_current, _parse_resistance
|
||||
from backend.periscopex.led_current_check import check_led_current, _parse_resistance
|
||||
|
||||
|
||||
def _led(values, pins, subtype="discrete.led.rgb"):
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
"""Lifecycle from distributor payload.
|
||||
|
||||
Favor: DigiKey Obsolete → PS-LF-001 WARNING and uses ProductSubstitutions;
|
||||
NRND → PS-LF-002 INFO; explicit RoHS Non-Compliant → PS-LF-003.
|
||||
Favor: DigiKey Obsolete → PE-LF-001 WARNING and uses ProductSubstitutions;
|
||||
NRND → PE-LF-002 INFO; explicit RoHS Non-Compliant → PE-LF-003.
|
||||
Against: Active is silent; RoHS Not Applicable is not a fail; missing
|
||||
catalog row is silent; no substitution key means no invented replacement.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.lifecycle import (
|
||||
from backend.periscopex.lifecycle import (
|
||||
check_lifecycle,
|
||||
parse_distributor_product,
|
||||
)
|
||||
from backend.pinscopex.models import Component, ComponentType, DesignGraph, Net, NetType, PinConnection
|
||||
from backend.periscopex.models import Component, ComponentType, DesignGraph, Net, NetType, PinConnection
|
||||
|
||||
|
||||
def _graph(mpn="PARTX"):
|
||||
@@ -44,7 +44,7 @@ def test_obsolete_is_warning_and_uses_distributor_replacement():
|
||||
assert rec.replacement == "ABC-B"
|
||||
findings = check_lifecycle(_graph("ABC"), {"ABC": rec})
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-LF-001"
|
||||
assert findings[0].rule_id == "PE-LF-001"
|
||||
assert findings[0].status == "WARNING"
|
||||
assert findings[0].source == "lifecycle_check"
|
||||
assert "ABC-B" in (findings[0].recommendation or "")
|
||||
@@ -55,7 +55,7 @@ def test_nrnd_is_info():
|
||||
assert rec.lifecycle == "nrnd"
|
||||
findings = check_lifecycle(_graph("N1"), {"N1": rec})
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-LF-002"
|
||||
assert findings[0].rule_id == "PE-LF-002"
|
||||
assert findings[0].status == "INFO"
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_explicit_rohs_non_compliant_is_warning():
|
||||
assert rec.rohs_compliant is False
|
||||
findings = check_lifecycle(_graph("R1"), {"R1": rec})
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-LF-003"
|
||||
assert findings[0].rule_id == "PE-LF-003"
|
||||
assert findings[0].status == "WARNING"
|
||||
|
||||
|
||||
@@ -93,6 +93,6 @@ def test_missing_catalog_and_missing_substitute_are_not_guessed():
|
||||
assert rec.replacement is None
|
||||
findings = check_lifecycle(_graph("EOLX"), {"EOLX": rec})
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-LF-001"
|
||||
assert findings[0].rule_id == "PE-LF-001"
|
||||
assert "ABC-B" not in (findings[0].recommendation or "")
|
||||
assert rec.replacement is None
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Local Pinscope auth — users, JWT, email lookup (no FastAPI required)."""
|
||||
"""Local Periscope auth — users, JWT, email lookup (no FastAPI required)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
@@ -12,7 +12,7 @@ from backend.pinscopex.models import (
|
||||
Pin,
|
||||
PinConnection,
|
||||
)
|
||||
from backend.pinscopex.nc_pin_check import check_nc_pins
|
||||
from backend.periscopex.nc_pin_check import check_nc_pins
|
||||
|
||||
|
||||
def test_nc_pin_on_active_net_warns():
|
||||
@@ -56,7 +56,7 @@ def test_nc_pin_on_active_net_warns():
|
||||
}
|
||||
findings = check_nc_pins(g, cmap)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-NC-001"
|
||||
assert findings[0].rule_id == "PE-NC-001"
|
||||
assert findings[0].net == "SIG"
|
||||
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.pinscopex.netlist_bundle import (
|
||||
from backend.periscopex.netlist_bundle import (
|
||||
find_kicad_pcb,
|
||||
materialize_netlist_upload,
|
||||
sniff_netlist_kind,
|
||||
)
|
||||
from backend.pinscopex.parsers import parse_netlist_any, validate_netlist
|
||||
from backend.periscopex.parsers import parse_netlist_any, validate_netlist
|
||||
|
||||
_LIB_R = """
|
||||
(lib_symbols
|
||||
@@ -86,7 +86,7 @@ def test_sniff_rejects_pcb_as_netlist():
|
||||
|
||||
|
||||
def test_root_alone_missing_child_explains_multi_file(tmp_path: Path):
|
||||
from backend.pinscopex.parsers_kicad import parse_kicad
|
||||
from backend.periscopex.parsers_kicad import parse_kicad
|
||||
|
||||
root, _child = _root_with_child()
|
||||
parsed = materialize_netlist_upload(
|
||||
@@ -198,7 +198,7 @@ def test_zip_pipeline_workspace_reparses_hierarchy(tmp_path: Path):
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from backend.main import app
|
||||
from backend.pinscopex.parsers import parse_netlist_any
|
||||
from backend.periscopex.parsers import parse_netlist_any
|
||||
from backend.services.storage import LocalStorageBackend
|
||||
|
||||
app.state.storage = LocalStorageBackend(tmp_path)
|
||||
|
||||
@@ -15,7 +15,7 @@ exact-string matching on section markers, so:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.parsers import parse_netlist
|
||||
from backend.periscopex.parsers import parse_netlist
|
||||
|
||||
|
||||
EASYEDA_PRO_NETLIST = """\
|
||||
|
||||
@@ -19,7 +19,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from backend.pinscopex.models import Finding
|
||||
from backend.periscopex.models import Finding
|
||||
from backend.services.normalize_findings import (
|
||||
SUBMIT_NORMALIZED_SCHEMA,
|
||||
_build_normalized,
|
||||
@@ -63,7 +63,7 @@ def test_normalize_passthrough_keeps_cad_fields():
|
||||
reference="",
|
||||
net="UART5_TX",
|
||||
pins=["U3.54"],
|
||||
rule_id="PS-MUX-001",
|
||||
rule_id="PE-MUX-001",
|
||||
cad_sheet="mcu.kicad_sch",
|
||||
)
|
||||
]
|
||||
@@ -74,7 +74,7 @@ def test_normalize_passthrough_keeps_cad_fields():
|
||||
built = _build_normalized(raw_findings, [], originals)
|
||||
assert built is not None
|
||||
kept, _ = built
|
||||
assert kept[0].rule_id == "PS-MUX-001"
|
||||
assert kept[0].rule_id == "PE-MUX-001"
|
||||
assert kept[0].net == "UART5_TX"
|
||||
assert kept[0].pins == ["U3.54"]
|
||||
assert kept[0].cad_sheet == "mcu.kicad_sch"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.graph import _infer_net_properties
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.graph import _infer_net_properties
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
@@ -13,7 +13,7 @@ from backend.pinscopex.models import (
|
||||
Pin,
|
||||
PinConnection,
|
||||
)
|
||||
from backend.pinscopex.passive_rail_check import (
|
||||
from backend.periscopex.passive_rail_check import (
|
||||
check_i2c_pullups,
|
||||
check_reset_pullups,
|
||||
check_supply_decoupling,
|
||||
@@ -104,7 +104,7 @@ def test_i2c_missing_pullup():
|
||||
findings = check_i2c_pullups(g, cons)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].source == "i2c_pullup_check"
|
||||
assert findings[0].rule_id == "PS-I2C-001"
|
||||
assert findings[0].rule_id == "PE-I2C-001"
|
||||
assert findings[0].net == "I2C_SDA"
|
||||
|
||||
|
||||
@@ -250,8 +250,8 @@ def test_nc_supply_net_is_skipped():
|
||||
|
||||
|
||||
def test_fb_and_rn_prefixes():
|
||||
from backend.pinscopex.graph import _classify_component
|
||||
from backend.pinscopex.models import ComponentType
|
||||
from backend.periscopex.graph import _classify_component
|
||||
from backend.periscopex.models import ComponentType
|
||||
|
||||
assert _classify_component("FB1", "") == ComponentType.INDUCTOR
|
||||
assert _classify_component("RN4", "") == ComponentType.RESISTOR
|
||||
@@ -261,7 +261,7 @@ def test_fb_and_rn_prefixes():
|
||||
def _res(ref, pins, value="4.7k", ohms=None):
|
||||
specs = None
|
||||
if ohms is not None:
|
||||
from backend.pinscopex.models import ResistorSpecs
|
||||
from backend.periscopex.models import ResistorSpecs
|
||||
specs = ResistorSpecs(value_ohms=ohms, value_formatted=f"{ohms}")
|
||||
return Component(
|
||||
reference=ref, value=value, footprint="",
|
||||
@@ -272,7 +272,7 @@ def _res(ref, pins, value="4.7k", ohms=None):
|
||||
def _cap(ref, pins, value="100n", farads=None):
|
||||
specs = None
|
||||
if farads is not None:
|
||||
from backend.pinscopex.models import CapacitorSpecs
|
||||
from backend.periscopex.models import CapacitorSpecs
|
||||
specs = CapacitorSpecs(value_farads=farads, value_formatted=value)
|
||||
return Component(
|
||||
reference=ref, value=value, footprint="",
|
||||
@@ -313,7 +313,7 @@ def test_i2c_100ohm_pullup_is_too_stiff():
|
||||
)
|
||||
findings = check_i2c_pullups(g, _cmap_i2c())
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-I2C-002"
|
||||
assert findings[0].rule_id == "PE-I2C-002"
|
||||
assert findings[0].status == "WARNING"
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ def test_i2c_100k_pullup_is_too_weak():
|
||||
},
|
||||
)
|
||||
findings = check_i2c_pullups(g, _cmap_i2c())
|
||||
assert [f.rule_id for f in findings] == ["PS-I2C-002"]
|
||||
assert [f.rule_id for f in findings] == ["PE-I2C-002"]
|
||||
|
||||
|
||||
def test_i2c_pullup_without_value_is_not_sized():
|
||||
@@ -359,7 +359,7 @@ def test_nrst_pulldown_is_warning():
|
||||
},
|
||||
)
|
||||
findings = check_reset_pullups(g, cons)
|
||||
assert any(f.rule_id == "PS-RST-002" for f in findings)
|
||||
assert any(f.rule_id == "PE-RST-002" for f in findings)
|
||||
|
||||
|
||||
def test_nrst_pullup_is_not_pulldown():
|
||||
@@ -410,7 +410,7 @@ def test_ldo_vout_needs_cout():
|
||||
},
|
||||
)
|
||||
findings = check_supply_decoupling(g, cons)
|
||||
assert any(f.net == "VOUT" and f.rule_id == "PS-DEC-001" for f in findings)
|
||||
assert any(f.net == "VOUT" and f.rule_id == "PE-DEC-001" for f in findings)
|
||||
assert not any(f.net == "VIN" for f in findings)
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ def test_ldo_vout_100n_only_is_value_warning():
|
||||
)
|
||||
findings = check_supply_decoupling(g, cons)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-DEC-002"
|
||||
assert findings[0].rule_id == "PE-DEC-002"
|
||||
assert findings[0].status == "WARNING"
|
||||
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ def test_sparse_page_is_flagged(tmp_path: Path):
|
||||
|
||||
|
||||
def test_one_table_markdown_from_extract_rows():
|
||||
from backend.pinscopex.pdf_text import _one_table_markdown
|
||||
from backend.periscopex.pdf_text import _one_table_markdown
|
||||
|
||||
class _Table:
|
||||
def to_markdown(self):
|
||||
|
||||
@@ -13,7 +13,7 @@ Locks in:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
@@ -25,8 +25,8 @@ from backend.pinscopex.models import (
|
||||
PinConnection,
|
||||
ValidationReport,
|
||||
)
|
||||
from backend.pinscopex.pin_function_tokens import normalize_functions, parse_net_token
|
||||
from backend.pinscopex.pin_mux_check import check_pin_mux_feasibility
|
||||
from backend.periscopex.pin_function_tokens import normalize_functions, parse_net_token
|
||||
from backend.periscopex.pin_mux_check import check_pin_mux_feasibility
|
||||
|
||||
|
||||
def _constraints(mpn, pintable):
|
||||
@@ -69,7 +69,7 @@ def test_real_defect_uart5_swapped_is_error():
|
||||
assert {f.designator for f in findings} == {"U3"}
|
||||
tx = next(f for f in findings if "MCU-UART5-TX" in f.finding)
|
||||
assert "cannot be muxed as UART5_TX" in tx.finding
|
||||
assert tx.rule_id == "PS-MUX-001"
|
||||
assert tx.rule_id == "PE-MUX-001"
|
||||
assert tx.net == "MCU-UART5-TX"
|
||||
assert tx.pins == ["U3.54"]
|
||||
|
||||
@@ -181,7 +181,7 @@ def test_spi_controller_peripheral_names_are_synonyms():
|
||||
def test_simple_project_uart0_nets_are_feasible_on_mspm0_pins():
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.models import DesignGraph
|
||||
from backend.periscopex.models import DesignGraph
|
||||
|
||||
graph = DesignGraph.model_validate_json(
|
||||
(Path(__file__).resolve().parents[1] / "simple_project" / "design_graph.json").read_text()
|
||||
@@ -203,7 +203,7 @@ def test_simple_project_uart0_nets_are_feasible_on_mspm0_pins():
|
||||
def test_simple_project_uart0_swapped_on_mspm0_is_error():
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.models import DesignGraph
|
||||
from backend.periscopex.models import DesignGraph
|
||||
|
||||
graph = DesignGraph.model_validate_json(
|
||||
(Path(__file__).resolve().parents[1] / "simple_project" / "design_graph.json").read_text()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""G2 placement vs simple_project — no invented millimetre boards.
|
||||
|
||||
Favor: real U1 + C4 on +3V3; same_layer True + opposite copper → PS-PLC-003.
|
||||
Favor: real U1 + C4 on +3V3; same_layer True + opposite copper → PE-PLC-003.
|
||||
Against: no PCB; same copper; same_layer unset; via in courtyard.
|
||||
"""
|
||||
|
||||
@@ -8,8 +8,8 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.eval_report import eval_simple_project
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.eval_report import eval_simple_project
|
||||
from backend.periscopex.models import (
|
||||
ComponentConstraints,
|
||||
DesignGraph,
|
||||
LayoutFootprint,
|
||||
@@ -19,7 +19,7 @@ from backend.pinscopex.models import (
|
||||
LayoutVia,
|
||||
Pin,
|
||||
)
|
||||
from backend.pinscopex.placement_check import _in_poly, check_placement
|
||||
from backend.periscopex.placement_check import _in_poly, check_placement
|
||||
|
||||
SIMPLE = Path(__file__).resolve().parents[1] / "simple_project"
|
||||
|
||||
@@ -41,7 +41,7 @@ def test_simple_project_has_ldo_and_3v3_caps():
|
||||
def test_simple_project_without_pcb_has_no_ps_plc():
|
||||
findings = check_placement(_graph(), {}, None)
|
||||
assert findings == []
|
||||
assert all(not (f.rule_id or "").startswith("PS-PLC-") for f in findings)
|
||||
assert all(not (f.rule_id or "").startswith("PE-PLC-") for f in findings)
|
||||
|
||||
|
||||
def test_simple_project_eval_has_no_placement_keys():
|
||||
@@ -49,7 +49,7 @@ def test_simple_project_eval_has_no_placement_keys():
|
||||
assert scores.finding_count == 3
|
||||
assert scores.precision == 1.0
|
||||
assert scores.recall == 1.0
|
||||
assert not any(k.startswith("PS-PLC-") for k in scores.extra_keys)
|
||||
assert not any(k.startswith("PE-PLC-") for k in scores.extra_keys)
|
||||
|
||||
|
||||
def test_via_count_is_calculated_from_courtyard_and_min_parameter():
|
||||
@@ -105,7 +105,7 @@ def test_same_layer_param_opposite_layers_is_ps_plc_003():
|
||||
_ldo_cons(same_layer=True),
|
||||
_u1_c4_layout(ic_layer="F.Cu", cap_layer="B.Cu"),
|
||||
)
|
||||
plc = [f for f in findings if f.rule_id == "PS-PLC-003"]
|
||||
plc = [f for f in findings if f.rule_id == "PE-PLC-003"]
|
||||
assert len(plc) == 1
|
||||
assert plc[0].status == "WARNING"
|
||||
assert plc[0].net == "+3V3"
|
||||
@@ -140,7 +140,7 @@ def test_opposite_layers_with_via_in_courtyard_is_silent():
|
||||
courtyard=courtyard,
|
||||
),
|
||||
)
|
||||
assert all(f.rule_id != "PS-PLC-003" for f in findings)
|
||||
assert all(f.rule_id != "PE-PLC-003" for f in findings)
|
||||
|
||||
|
||||
def test_simple_project_has_crystal_load_caps():
|
||||
@@ -189,7 +189,7 @@ def test_crystal_load_cap_beyond_max_distance_mm_is_ps_plc_001():
|
||||
_xtal_cons(max_distance_mm=limit),
|
||||
_x1_c9_layout(cap_x=10.0),
|
||||
)
|
||||
plc = [f for f in findings if f.rule_id == "PS-PLC-001"]
|
||||
plc = [f for f in findings if f.rule_id == "PE-PLC-001"]
|
||||
assert len(plc) == 1
|
||||
assert plc[0].designator == "X1"
|
||||
assert plc[0].net == "/HFXIN"
|
||||
@@ -215,7 +215,7 @@ def test_track_path_longer_than_max_distance_mm_is_ps_plc_001():
|
||||
_xtal_cons(max_distance_mm=limit),
|
||||
_x1_c9_layout(cap_x=1.0, segments=segs),
|
||||
)
|
||||
plc = [f for f in findings if f.rule_id == "PS-PLC-001"]
|
||||
plc = [f for f in findings if f.rule_id == "PE-PLC-001"]
|
||||
assert len(plc) == 1
|
||||
assert plc[0].net == "/HFXIN"
|
||||
|
||||
@@ -258,7 +258,7 @@ def test_keepout_foreign_track_in_courtyard_is_ps_plc_004():
|
||||
_xtal_keepout_cons(),
|
||||
_x1_keepout_layout(net="GND"),
|
||||
)
|
||||
plc = [f for f in findings if f.rule_id == "PS-PLC-004"]
|
||||
plc = [f for f in findings if f.rule_id == "PE-PLC-004"]
|
||||
assert len(plc) == 1
|
||||
assert plc[0].designator == "X1"
|
||||
assert plc[0].net == "GND"
|
||||
|
||||
@@ -4,19 +4,19 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.functional_groups import (
|
||||
from backend.periscopex.functional_groups import (
|
||||
PlacementIcGroup,
|
||||
PlacementSatellite,
|
||||
FunctionalGroupsReport,
|
||||
build_functional_groups,
|
||||
)
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
DesignGraph,
|
||||
LayoutFootprint,
|
||||
LayoutGraph,
|
||||
LayoutPad,
|
||||
)
|
||||
from backend.pinscopex.placement_pack import build_placement_pack
|
||||
from backend.periscopex.placement_pack import build_placement_pack
|
||||
|
||||
SIMPLE = Path(__file__).resolve().parents[1] / "simple_project"
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.pinscopex.functional_groups import (
|
||||
from backend.periscopex.functional_groups import (
|
||||
FunctionalGroupsReport,
|
||||
build_placement_plan,
|
||||
)
|
||||
from backend.pinscopex.models import DesignGraph
|
||||
from backend.periscopex.models import DesignGraph
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
@@ -14,7 +14,7 @@ from backend.pinscopex.models import (
|
||||
ResistorSpecs,
|
||||
SimpleComponentSpecs,
|
||||
)
|
||||
from backend.pinscopex.power_margin_check import check_power_margin
|
||||
from backend.periscopex.power_margin_check import check_power_margin
|
||||
|
||||
|
||||
def _graph(components, nets):
|
||||
@@ -74,7 +74,7 @@ def test_load_over_iout_max_is_ps_pwr_001():
|
||||
},
|
||||
)
|
||||
findings = check_power_margin(g, _cons())
|
||||
assert any(f.rule_id == "PS-PWR-001" and f.designator == "U1" for f in findings)
|
||||
assert any(f.rule_id == "PE-PWR-001" and f.designator == "U1" for f in findings)
|
||||
|
||||
|
||||
def test_missing_iq_is_not_guessed_into_margin_fail():
|
||||
@@ -109,7 +109,7 @@ def test_series_r_ir_drop_uses_i_load_not_trace():
|
||||
},
|
||||
)
|
||||
findings = check_power_margin(g, _cons())
|
||||
assert any(f.designator == "R1" and f.rule_id == "PS-PWR-001" for f in findings)
|
||||
assert any(f.designator == "R1" and f.rule_id == "PE-PWR-001" for f in findings)
|
||||
|
||||
|
||||
def test_no_series_r_does_not_invent_trace_drop():
|
||||
|
||||
@@ -450,7 +450,7 @@ async def test_lcsc_resolve_passive_success_and_cached(tmp_path, monkeypatch):
|
||||
"""First call parses the LCSC description without the LLM. Second call
|
||||
short-circuits with cached=True and does not invoke auto_resolve_specs."""
|
||||
from backend.config import settings
|
||||
from backend.pinscopex.models import CapacitorSpecs, ComponentModel
|
||||
from backend.periscopex.models import CapacitorSpecs, ComponentModel
|
||||
from backend.services import purple_parts
|
||||
from backend.services import extraction as extraction_svc
|
||||
|
||||
@@ -539,7 +539,7 @@ async def test_lcsc_resolve_passive_success_and_cached(tmp_path, monkeypatch):
|
||||
assert call_count["n"] == 0
|
||||
|
||||
# Library copy should exist for cross-project reuse.
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
safe = safe_mpn("CL21A106KAYNNNE")
|
||||
assert storage.exists(f"library/passives/{safe}.json")
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.models import Finding
|
||||
from backend.pinscopex.quote_verify import (
|
||||
from backend.periscopex.models import Finding
|
||||
from backend.periscopex.quote_verify import (
|
||||
locate_quote,
|
||||
quote_in_text,
|
||||
verify_finding_citations,
|
||||
)
|
||||
from backend.pinscopex.validate import _parse_review
|
||||
from backend.periscopex.validate import _parse_review
|
||||
from backend.services.llm.pdf_ingest import make_text_pdf
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentType,
|
||||
DesignGraph,
|
||||
@@ -6,7 +6,7 @@ from backend.pinscopex.models import (
|
||||
NetType,
|
||||
PinConnection,
|
||||
)
|
||||
from backend.pinscopex.review_fingerprint import (
|
||||
from backend.periscopex.review_fingerprint import (
|
||||
graph_ic_fingerprints,
|
||||
skip_unchanged_ics,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""submit_review parsing — ERROR without a datasheet quote is demoted."""
|
||||
|
||||
from backend.pinscopex.validate import _parse_review
|
||||
from backend.periscopex.validate import _parse_review
|
||||
|
||||
|
||||
def test_error_without_quote_becomes_unverified_warning():
|
||||
|
||||
@@ -10,8 +10,8 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.pinscopex.models import Finding
|
||||
from backend.pinscopex.review_workflow import (
|
||||
from backend.periscopex.models import Finding
|
||||
from backend.periscopex.review_workflow import (
|
||||
ReviewError,
|
||||
apply_review_state,
|
||||
build_eco,
|
||||
@@ -30,7 +30,7 @@ def _finding(**kwargs):
|
||||
why="no 100nF on VDD",
|
||||
status="ERROR",
|
||||
recommendation="add 100nF",
|
||||
rule_id="PS-DEC-001",
|
||||
rule_id="PE-DEC-001",
|
||||
)
|
||||
defaults.update(kwargs)
|
||||
return Finding(**defaults)
|
||||
@@ -65,7 +65,7 @@ def test_eco_includes_accepted_not_false_positive():
|
||||
states = apply_review_state(states, "U2-001", state="false_positive", reason="ok in app", user_id="a")
|
||||
eco = build_eco(findings, states)
|
||||
assert [row["finding_id"] for row in eco] == ["U1-001"]
|
||||
assert eco[0]["rule_id"] == "PS-DEC-001"
|
||||
assert eco[0]["rule_id"] == "PE-DEC-001"
|
||||
assert eco[0]["ref"] == "U1"
|
||||
assert "100nF" in eco[0]["after"]
|
||||
csv = eco_csv(eco)
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.library_gate import pintable_checksum, should_promote_extraction
|
||||
from backend.pinscopex.models import Component, ComponentType, DesignGraph, Net, NetType, PinConnection
|
||||
from backend.pinscopex.validation_tools import shortest_path
|
||||
from backend.periscopex.library_gate import pintable_checksum, should_promote_extraction
|
||||
from backend.periscopex.models import Component, ComponentType, DesignGraph, Net, NetType, PinConnection
|
||||
from backend.periscopex.validation_tools import shortest_path
|
||||
from backend.services.api_logs import cache_stats_by_stage
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
@@ -13,7 +13,7 @@ from backend.pinscopex.models import (
|
||||
PinConnection,
|
||||
SimpleComponentSpecs,
|
||||
)
|
||||
from backend.pinscopex.sequencing_check import check_power_sequencing
|
||||
from backend.periscopex.sequencing_check import check_power_sequencing
|
||||
|
||||
|
||||
def _graph(components, nets):
|
||||
@@ -79,7 +79,7 @@ def _dual(pg_net, en_net, sequence=True):
|
||||
def test_pg_not_tied_to_en_is_warning():
|
||||
findings = check_power_sequencing(_dual("PGOOD", "EN_1V8"), _cons())
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-SEQ-001"
|
||||
assert findings[0].rule_id == "PE-SEQ-001"
|
||||
assert findings[0].status == "WARNING"
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
"""G1 SI vs simple_project — no invented millimetres or USBPHY boards.
|
||||
|
||||
Favor: real /USB.D+ and /USB.D- pair by suffix; eval stays 3 keys.
|
||||
Against: no .kicad_pcb → no PS-SI-001; 3W is not invented.
|
||||
Against: no .kicad_pcb → no PE-SI-001; 3W is not invented.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.eval_report import eval_simple_project
|
||||
from backend.pinscopex.models import DesignGraph
|
||||
from backend.pinscopex.si_check import check_si, partner_net
|
||||
from backend.periscopex.eval_report import eval_simple_project
|
||||
from backend.periscopex.models import DesignGraph
|
||||
from backend.periscopex.si_check import check_si, partner_net
|
||||
|
||||
SIMPLE = Path(__file__).resolve().parents[1] / "simple_project"
|
||||
|
||||
@@ -33,7 +33,7 @@ def test_simple_project_usb_dp_dm_are_a_named_pair():
|
||||
def test_simple_project_without_pcb_has_no_ps_si_001():
|
||||
findings = check_si(_graph(), {}, None)
|
||||
assert findings == []
|
||||
assert all(f.rule_id != "PS-3W-001" for f in findings)
|
||||
assert all(f.rule_id != "PE-3W-001" for f in findings)
|
||||
|
||||
|
||||
def test_simple_project_eval_has_no_si_keys():
|
||||
@@ -42,6 +42,6 @@ def test_simple_project_eval_has_no_si_keys():
|
||||
assert scores.precision == 1.0
|
||||
assert scores.recall == 1.0
|
||||
assert not any(
|
||||
k.startswith("PS-SI-") or k.startswith("PS-3W-")
|
||||
k.startswith("PE-SI-") or k.startswith("PE-3W-")
|
||||
for k in scores.extra_keys
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
@@ -14,7 +14,7 @@ from backend.pinscopex.models import (
|
||||
ResistorSpecs,
|
||||
SimpleComponentSpecs,
|
||||
)
|
||||
from backend.pinscopex.thermal_check import check_thermal
|
||||
from backend.periscopex.thermal_check import check_thermal
|
||||
|
||||
|
||||
def _graph(components, nets):
|
||||
@@ -63,7 +63,7 @@ def test_ldo_without_theta_ja_is_info():
|
||||
)
|
||||
findings = check_thermal(g, _cons())
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-TH-001"
|
||||
assert findings[0].rule_id == "PE-TH-001"
|
||||
assert findings[0].status == "INFO"
|
||||
assert "theta_ja" in findings[0].finding.lower() or "theta_ja" in findings[0].why.lower()
|
||||
|
||||
@@ -92,7 +92,7 @@ def test_ldo_hot_tj_is_warning():
|
||||
)
|
||||
findings = check_thermal(g, _cons())
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-TH-002"
|
||||
assert findings[0].rule_id == "PE-TH-002"
|
||||
assert findings[0].status == "WARNING"
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ def test_shunt_over_rating_is_warning():
|
||||
)
|
||||
findings = check_thermal(g)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule_id == "PS-TH-003"
|
||||
assert findings[0].rule_id == "PE-TH-003"
|
||||
assert findings[0].status == "WARNING"
|
||||
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ import pytest
|
||||
from pypdf import PdfWriter
|
||||
|
||||
from backend.config import settings
|
||||
from backend.pinscopex.models import Component, ComponentType, DesignGraph, Finding
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.pinscopex.validate import ReviewResult
|
||||
from backend.periscopex.models import Component, ComponentType, DesignGraph, Finding
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
from backend.periscopex.validate import ReviewResult
|
||||
from backend.services import validation as val
|
||||
from backend.services.api_logs import ApiLogger
|
||||
from backend.services.storage import LocalStorageBackend
|
||||
|
||||
@@ -8,7 +8,7 @@ import json
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
|
||||
@@ -15,7 +15,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
from pypdf import PdfWriter
|
||||
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
from backend.services import validation as val
|
||||
from backend.services.llm.types import Completion, ToolCall, Usage
|
||||
from backend.services.storage import LocalStorageBackend
|
||||
@@ -148,8 +148,9 @@ async def test_no_tool_calls_triggers_forced_submit_next_turn(workspace, monkeyp
|
||||
# All three ICs should have recovered: each had a no-tool-call turn 0,
|
||||
# then submit_review under forced tool_choice on turn 1.
|
||||
report = json.loads(workspace["report"].read_text())
|
||||
assert report["summary"]["total"] == 3
|
||||
assert report["summary"]["INFO"] == 3
|
||||
review = [f for f in report["findings"] if not f.get("rule_id")]
|
||||
assert len(review) == 3
|
||||
assert sum(1 for f in review if f.get("status") == "INFO") == 3
|
||||
|
||||
# Verify the recovery actually forced submit_review on turn 1 for each IC.
|
||||
by_ic_turn = {(ic, n): tc for ic, n, tc in seen_tool_choices}
|
||||
|
||||
@@ -15,7 +15,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
from pypdf import PdfWriter
|
||||
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
from backend.services import validation as val
|
||||
from backend.services.llm.types import Completion, ToolCall, Usage
|
||||
from backend.services.storage import LocalStorageBackend
|
||||
@@ -191,8 +191,9 @@ async def test_trace_written_per_ic_with_schema(workspace, monkeypatch):
|
||||
assert isinstance(t["duration_ms"], int)
|
||||
|
||||
report = json.loads(workspace["report"].read_text())
|
||||
# 3 ICs x 1 finding each
|
||||
assert report["summary"]["total"] == 3
|
||||
# 3 ICs x 1 LLM finding each (deterministic checks may add more)
|
||||
review = [f for f in report["findings"] if not f.get("rule_id")]
|
||||
assert len(review) == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -214,7 +215,8 @@ async def test_trace_write_failure_does_not_break_review(workspace, monkeypatch)
|
||||
await _run(workspace, monkeypatch, before_ic)
|
||||
|
||||
report = json.loads(workspace["report"].read_text())
|
||||
assert report["summary"]["total"] == 3
|
||||
review = [f for f in report["findings"] if not f.get("rule_id")]
|
||||
assert len(review) == 3
|
||||
assert not (workspace["data"] / PREFIX / "review_traces").exists()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user