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:
2026-09-13 20:02:04 +02:00
co-authored by Cursor
parent 556306bf4d
commit 8d2b85600f
177 changed files with 869 additions and 766 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ from fastapi.responses import JSONResponse
from pydantic import BaseModel
from backend.config import settings
from backend.pinscopex.utils import safe_mpn
from backend.periscopex.utils import safe_mpn
from backend.routers.deps import get_storage
from backend.services import admin_settings as settings_svc
from backend.services.billing_hook import get_billing
+1 -1
View File
@@ -1,4 +1,4 @@
"""Local Pinscope auth endpoints (register / login / me)."""
"""Local Periscope auth endpoints (register / login / me)."""
from __future__ import annotations
+4 -4
View File
@@ -41,10 +41,10 @@ class ContactResponse(BaseModel):
def _build_contact_message(data: ContactRequest) -> MIMEMultipart:
"""Build the contact form email."""
msg = MIMEMultipart("alternative")
msg["From"] = f"Pinscope <{settings.email_sender}>"
msg["From"] = f"Periscope <{settings.email_sender}>"
msg["To"] = settings.contact_recipient
msg["Reply-To"] = data.email
msg["Subject"] = f"[Pinscope Contact] {data.subject or 'New message'} from {data.name}"
msg["Subject"] = f"[Periscope Contact] {data.subject or 'New message'} from {data.name}"
# Plain text
lines = [
@@ -55,7 +55,7 @@ def _build_contact_message(data: ContactRequest) -> MIMEMultipart:
lines.append(f"Company: {data.company}")
if data.subject:
lines.append(f"Subject: {data.subject}")
lines += ["", data.message, "", "— Sent from the Pinscope contact form"]
lines += ["", data.message, "", "— Sent from the Periscope contact form"]
msg.attach(MIMEText("\n".join(lines), "plain"))
# HTML
@@ -94,7 +94,7 @@ def _build_contact_message(data: ContactRequest) -> MIMEMultipart:
{rows}
</table>
<div style="margin-top: 16px; padding: 16px; background: #f9fafb; border-radius: 8px; font-size: 14px; line-height: 1.6; white-space: pre-wrap;">{message}</div>
<p style="margin-top: 24px; font-size: 12px; color: #888;">Sent from the Pinscope contact form</p>
<p style="margin-top: 24px; font-size: 12px; color: #888;">Sent from the Periscope contact form</p>
</div>"""
msg.attach(MIMEText(html_body, "html"))
+5 -5
View File
@@ -10,7 +10,7 @@ from typing import Literal
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel
from backend.pinscopex.impedance import (
from backend.periscopex.impedance import (
GeometryError,
TraceGeometry,
coupled_diff_z,
@@ -21,13 +21,13 @@ from backend.pinscopex.impedance import (
stackup_targets,
stripline_z0,
)
from backend.pinscopex.impedance_traces import (
from backend.periscopex.impedance_traces import (
NET_WALK_PITCH_MM,
analyze_specified_nets,
)
from backend.pinscopex.antenna_rf import build_antenna_report, build_design_recipe
from backend.pinscopex.models import DesignGraph, LayoutGraph
from backend.pinscopex.parsers_kicad_pcb import parse_kicad_pcb
from backend.periscopex.antenna_rf import build_antenna_report, build_design_recipe
from backend.periscopex.models import DesignGraph, LayoutGraph
from backend.periscopex.parsers_kicad_pcb import parse_kicad_pcb
from backend.routers.deps import get_storage, resolve_or_404
from backend.services import projects as proj_svc
+4 -4
View File
@@ -84,7 +84,7 @@ async def start(project_id: str, request: Request):
# Idempotent enqueue: only one ``draft|complete|error|cancelled`` ->
# ``queued`` transition can win. Concurrent /start clicks => 409.
from backend._version import PINSCOPE_VERSION
from backend._version import PERISCOPE_VERSION
try:
proj_svc.transition_status(
storage, owner_user_id, project_id,
@@ -92,7 +92,7 @@ async def start(project_id: str, request: Request):
to_status=proj_svc.STATUS_QUEUED,
cancel_requested=False,
execution_name=None,
pinscope_version=PINSCOPE_VERSION,
periscope_version=PERISCOPE_VERSION,
)
except proj_svc.StatusConflict:
raise HTTPException(409, "Pipeline already running or queued")
@@ -199,7 +199,7 @@ async def reprocess(project_id: str, request: Request, req: ReprocessRequest | N
(default) skips ICs that already produced a review; ``all`` re-reviews
every IC.
"""
from backend._version import PINSCOPE_VERSION
from backend._version import PERISCOPE_VERSION
storage = get_storage(request)
owner_user_id, meta = await resolve_or_404(request, project_id)
@@ -241,7 +241,7 @@ async def reprocess(project_id: str, request: Request, req: ReprocessRequest | N
pause_checkpoint=None,
pause_reason=None,
completed_review_refs=keep_refs,
pinscope_version=PINSCOPE_VERSION,
periscope_version=PERISCOPE_VERSION,
)
except proj_svc.StatusConflict:
raise HTTPException(409, "Pipeline already running or queued")
+11 -11
View File
@@ -11,7 +11,7 @@ from pydantic import BaseModel
MAX_UPLOAD_BYTES = 30 * 1024 * 1024 # 30 MB
from backend.config import settings
from backend.pinscopex.utils import safe_mpn
from backend.periscopex.utils import safe_mpn
from backend.routers.deps import get_storage, get_user_id, resolve_or_404
from backend.services import projects as proj_svc
@@ -63,7 +63,7 @@ async def check_library(req: LibraryCheckRequest, request: Request):
passive_resolved: list[str] = []
if req.passive_mpns:
from backend.pinscopex.resolve_passives import resolve_mpn
from backend.periscopex.resolve_passives import resolve_mpn
passive_resolved = [
mpn for mpn in req.passive_mpns
@@ -256,7 +256,7 @@ async def get_netlist_subdesigns(project_id: str, request: Request):
``selected`` list (None = "include everything") so the wizard can render
the picker pre-populated.
"""
from backend.pinscopex.parsers_edif import list_edif_subdesigns
from backend.periscopex.parsers_edif import list_edif_subdesigns
import tempfile, os
storage = get_storage(request)
@@ -360,7 +360,7 @@ async def upload_bom(
import os
import tempfile
from backend.pinscopex.parsers import parse_bom
from backend.periscopex.parsers import parse_bom
try:
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
@@ -420,7 +420,7 @@ async def upload_bom(
# simple → datasheet upload). Mirrors the bucket logic in
# services/pipeline.py:_stage_bom_parse so the field is correct after
# either path runs.
from backend.pinscopex.taxonomy import SIMPLE_TYPES, type_for_ref
from backend.periscopex.taxonomy import SIMPLE_TYPES, type_for_ref
ic_mpns: list[str] = []
passive_mpns: list[str] = []
@@ -511,9 +511,9 @@ async def upload_netlist(
raise HTTPException(404, "Project not found")
user_id = result[0] # owner_user_id for storage paths
from backend.pinscopex.netlist_bundle import materialize_netlist_upload
from backend.pinscopex.parsers import parse_netlist_any, validate_netlist
from backend.pinscopex.parsers_edif import list_edif_subdesigns
from backend.periscopex.netlist_bundle import materialize_netlist_upload
from backend.periscopex.parsers import parse_netlist_any, validate_netlist
from backend.periscopex.parsers_edif import list_edif_subdesigns
import tempfile
blobs: list[tuple[str, bytes]] = []
@@ -616,7 +616,7 @@ async def upload_pcb(project_id: str, file: UploadFile, request: Request):
if len(data) > MAX_UPLOAD_BYTES:
raise HTTPException(413, f"File too large (max {MAX_UPLOAD_BYTES // 1024 // 1024} MB)")
import tempfile, os
from backend.pinscopex.parsers_kicad_pcb import parse_kicad_pcb
from backend.periscopex.parsers_kicad_pcb import parse_kicad_pcb
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".kicad_pcb")
try:
@@ -647,7 +647,7 @@ def _build_designator_pins(
(natural sort on refs and on pin numbers) so the wizard's dropdowns
look identical regardless of netlist format.
"""
from backend.pinscopex.utils import natural_sort_key
from backend.periscopex.utils import natural_sort_key
by_ref: dict[str, dict[str, str]] = {ref: {} for ref in parts}
for net_name, pins in nets.items():
@@ -1079,7 +1079,7 @@ async def lcsc_resolve_passive(
# Catalog miss (ferrite, odd text): LLM path, charged if the logger has tokens.
# Download taxonomy to a temp dir so auto_resolve_specs can read/write it.
# Mirrors the PipelineWorkspace pattern: pinscopex operates on local paths.
# Mirrors the PipelineWorkspace pattern: periscopex operates on local paths.
api_logger = ApiLogger()
with tempfile.TemporaryDirectory() as tmpdir:
tax_dir = Path(tmpdir) / "taxonomy"
+4 -4
View File
@@ -11,15 +11,15 @@ from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import JSONResponse, Response
from pydantic import BaseModel
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,
eco_csv,
sign_report,
)
from backend.pinscopex.utils import safe_mpn
from backend.periscopex.utils import safe_mpn
from backend.routers.deps import get_storage, get_user_id, resolve_or_404
from backend.services import projects as proj_svc
@@ -51,7 +51,7 @@ async def get_cad_bridge(project_id: str, request: Request):
storage = get_storage(request)
owner_user_id, _ = await resolve_or_404(request, project_id)
prefix = proj_svc.project_prefix(owner_user_id, project_id)
key = f"{prefix}/pinscope-findings.json"
key = f"{prefix}/periscope-findings.json"
if not storage.exists(key):
raise HTTPException(404, "CAD bridge not found — run the pipeline first")
return JSONResponse(storage.read_json(key))