Split native Periscope (periscope/src) from inherited PinScope (periscope/dependency).

Keep validate.py and the finding engine as-is. AGPL LICENSE stays at the repo root.
Docker overlays dependency then src. Do not delete the inherited tree.
This commit is contained in:
2026-09-20 14:31:42 +02:00
parent 0a75e5971a
commit c362ef8a56
384 changed files with 742 additions and 433 deletions
+8 -2
View File
@@ -10,8 +10,14 @@ import pytest
# Ensure the repo root is on sys.path so `import backend.*` works whether
# pytest is invoked from the repo root or a subdirectory.
REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
for p in (
REPO_ROOT / "periscope" / "dependency",
REPO_ROOT / "periscope" / "src",
REPO_ROOT,
):
s = str(p)
if p.is_dir() and s not in sys.path:
sys.path.insert(0, s)
from backend.vendor_path import ensure_impedancefinder
+9
View File
@@ -0,0 +1,9 @@
"""Shared fixture paths after the periscope/src vs periscope/dependency split."""
from __future__ import annotations
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SIMPLE_PROJECT = REPO_ROOT / "periscope" / "dependency" / "simple_project"
TAXONOMY = REPO_ROOT / "periscope" / "dependency" / "taxonomy"
+1 -4
View File
@@ -2,7 +2,7 @@
from __future__ import annotations
from pathlib import Path
from tests.paths import SIMPLE_PROJECT
import pytest
@@ -14,9 +14,6 @@ from backend.services.cost_estimator import (
)
SIMPLE_PROJECT = Path(__file__).resolve().parents[1] / "simple_project"
def _seed_project(storage, user_id: str, project_id: str) -> None:
"""Create a minimal project with the simple_project BOM uploaded."""
proj_svc.create_project(storage, user_id, "Fixture")
+3 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from backend.periscopex.crystal_cl_check import check_crystal_cl
from backend.periscopex.models import (
Component,
@@ -92,6 +94,6 @@ def test_with_stray_mismatch_warns():
def test_simple_project_without_cl_silent():
from pathlib import Path
from backend.periscopex.models import DesignGraph
path = Path(__file__).resolve().parents[1] / "simple_project" / "design_graph.json"
path = SIMPLE_PROJECT / "design_graph.json"
g = DesignGraph.model_validate_json(path.read_text())
assert check_crystal_cl(g) == []
+3 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
import json
from pathlib import Path
@@ -19,7 +21,7 @@ from backend.periscopex.validation_tools import (
from backend.services.llm.types import PdfBlock
from backend.services.validation import _signal_neighbors
GRAPH = Path(__file__).resolve().parent.parent / "simple_project" / "design_graph.json"
GRAPH = SIMPLE_PROJECT / "design_graph.json"
IC_MPNS = {
"U1": "SPX3819M5-L-3-3/TR",
"U2": "CH340E",
+4 -2
View File
@@ -8,6 +8,8 @@ Unverified quotes are citation misses, not hits.
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from pathlib import Path
from backend.periscopex.eval_report import (
@@ -80,7 +82,7 @@ def test_citation_rate_ignores_deterministic_and_counts_unverified():
def test_simple_project_eval_matches_committed_golden():
root = Path(__file__).resolve().parents[1] / "simple_project"
root = SIMPLE_PROJECT
scores = eval_simple_project(root)
assert scores.graph_ok, scores.graph_errors
assert scores.precision == 1.0
@@ -94,7 +96,7 @@ def test_simple_project_eval_rejects_truncated_graph(tmp_path: Path):
import json
import shutil
src = Path(__file__).resolve().parents[1] / "simple_project"
src = SIMPLE_PROJECT
dest = tmp_path / "simple_project"
shutil.copytree(src, dest)
g = json.loads((dest / "design_graph.json").read_text())
+3 -1
View File
@@ -2,13 +2,15 @@
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
import json
from pathlib import Path
from backend.periscopex.functional_groups import build_functional_groups
from backend.periscopex.models import DesignGraph
SIMPLE = Path(__file__).resolve().parents[1] / "simple_project"
SIMPLE = SIMPLE_PROJECT
def _graph() -> DesignGraph:
+3 -2
View File
@@ -1,3 +1,4 @@
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from backend.services.passive_from_distributor import specs_from_distributor
@@ -91,7 +92,7 @@ def test_auto_resolve_skips_llm_when_catalog_parses(monkeypatch):
raise AssertionError("LLM must not run")
monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom)
tax = Path(__file__).resolve().parents[1] / "taxonomy"
tax = TAXONOMY
model = asyncio.run(
auto_resolve_specs(
mpn="CL21B225KPFNNNE",
@@ -115,7 +116,7 @@ def test_auto_resolve_use_llm_false_on_ferrite(monkeypatch):
raise AssertionError("LLM must not run")
monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom)
tax = Path(__file__).resolve().parents[1] / "taxonomy"
tax = TAXONOMY
model = asyncio.run(
auto_resolve_specs(
mpn="BLM18PG121SN1D",
+3 -2
View File
@@ -1,3 +1,4 @@
from tests.paths import SIMPLE_PROJECT, TAXONOMY
import asyncio
from pathlib import Path
@@ -57,7 +58,7 @@ def test_resolve_from_value_skips_llm_when_parseable(monkeypatch):
raise AssertionError("LLM must not run")
monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom)
tax = Path(__file__).resolve().parents[1] / "taxonomy"
tax = TAXONOMY
model = asyncio.run(
resolve_from_value(
mpn="18pF",
@@ -76,7 +77,7 @@ def test_resolve_from_value_placeholder_no_llm(monkeypatch):
raise AssertionError("LLM must not run")
monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom)
tax = Path(__file__).resolve().parents[1] / "taxonomy"
tax = TAXONOMY
with pytest.raises(ValueError, match="Placeholder"):
asyncio.run(
resolve_from_value(
+3 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from pathlib import Path
from backend.periscopex.functional_groups import FunctionalGroupsReport, PlacementDomain, PlacementIcGroup
@@ -26,7 +28,7 @@ from backend.periscopex.pcb_net_match import check_pcb_net_match
from backend.periscopex.pcb_review import build_pcb_layout_context
from backend.services.projects import ProjectMeta, STATUS_QUEUED, STATUS_RUNNING
SIMPLE = Path(__file__).resolve().parents[1] / "simple_project"
SIMPLE = SIMPLE_PROJECT
def _graph() -> DesignGraph:
+4 -2
View File
@@ -13,6 +13,8 @@ Locks in:
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from backend.periscopex.models import (
Component,
ComponentConstraints,
@@ -184,7 +186,7 @@ def test_simple_project_uart0_nets_are_feasible_on_mspm0_pins():
from backend.periscopex.models import DesignGraph
graph = DesignGraph.model_validate_json(
(Path(__file__).resolve().parents[1] / "simple_project" / "design_graph.json").read_text()
(SIMPLE_PROJECT / "design_graph.json").read_text()
)
cmap = {
"MSPM0G3507SPTR": _constraints(
@@ -206,7 +208,7 @@ def test_simple_project_uart0_swapped_on_mspm0_is_error():
from backend.periscopex.models import DesignGraph
graph = DesignGraph.model_validate_json(
(Path(__file__).resolve().parents[1] / "simple_project" / "design_graph.json").read_text()
(SIMPLE_PROJECT / "design_graph.json").read_text()
)
cmap = {
"MSPM0G3507SPTR": _constraints(
+3 -1
View File
@@ -6,6 +6,8 @@ Against: no PCB; same copper; same_layer unset; via in courtyard.
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from pathlib import Path
from backend.periscopex.eval_report import eval_simple_project
@@ -21,7 +23,7 @@ from backend.periscopex.models import (
)
from backend.periscopex.placement_check import _in_poly, check_placement
SIMPLE = Path(__file__).resolve().parents[1] / "simple_project"
SIMPLE = SIMPLE_PROJECT
def _graph() -> DesignGraph:
+3 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from pathlib import Path
from backend.periscopex.functional_groups import (
@@ -18,7 +20,7 @@ from backend.periscopex.models import (
)
from backend.periscopex.placement_pack import build_placement_pack
SIMPLE = Path(__file__).resolve().parents[1] / "simple_project"
SIMPLE = SIMPLE_PROJECT
def _graph() -> DesignGraph:
+3 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from pathlib import Path
import pytest
@@ -14,7 +16,7 @@ from backend.periscopex.models import DesignGraph
ROOT = Path(__file__).resolve().parents[1]
SIMPLE = ROOT / "simple_project"
SIMPLE = SIMPLE_PROJECT
@pytest.fixture
+3 -1
View File
@@ -7,6 +7,8 @@ I2C/GPIO/CC are not 50/90 Ω. ImpedenceFinder numbers vs layout_rules only.
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from pathlib import Path
from backend.periscopex.eval_report import eval_simple_project
@@ -26,7 +28,7 @@ from backend.periscopex.models import (
)
from backend.periscopex.si_check import bus_class, check_si, partner_net, skip_si_net
SIMPLE = Path(__file__).resolve().parents[1] / "simple_project"
SIMPLE = SIMPLE_PROJECT
def _graph() -> DesignGraph:
+3 -1
View File
@@ -9,6 +9,8 @@ so it survives future prompt regressions.
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
import json
from pathlib import Path
@@ -20,7 +22,7 @@ from backend.services import validation as val
from backend.services.llm.types import Completion, ToolCall, Usage
from backend.services.storage import LocalStorageBackend
GRAPH = Path(__file__).resolve().parent.parent / "simple_project" / "design_graph.json"
GRAPH = SIMPLE_PROJECT / "design_graph.json"
IC_MPNS = {
"U1": "SPX3819M5-L-3-3/TR",
"U2": "CH340E",
+3 -1
View File
@@ -8,6 +8,8 @@ and that trace failures never break the review/report.
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
import json
import re
from pathlib import Path
@@ -20,7 +22,7 @@ from backend.services import validation as val
from backend.services.llm.types import Completion, ToolCall, Usage
from backend.services.storage import LocalStorageBackend
GRAPH = Path(__file__).resolve().parent.parent / "simple_project" / "design_graph.json"
GRAPH = SIMPLE_PROJECT / "design_graph.json"
IC_MPNS = {
"U1": "SPX3819M5-L-3-3/TR",
"U2": "CH340E",