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:
@@ -27,10 +27,10 @@ from typing import Any, Literal
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.config import settings
|
||||
from backend.pinscopex.parsers import parse_bom
|
||||
from backend.pinscopex.resolve_passives import resolve_mpn
|
||||
from backend.pinscopex.taxonomy import SIMPLE_TYPES, type_for_ref
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.periscopex.parsers import parse_bom
|
||||
from backend.periscopex.resolve_passives import resolve_mpn
|
||||
from backend.periscopex.taxonomy import SIMPLE_TYPES, type_for_ref
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
from backend.services import projects as proj_svc
|
||||
from backend.services.billing_hook import get_billing
|
||||
from backend.services.llm.pricing import CACHE_RATES, PRICING
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Automatic datasheet lookup — LCSC, manufacturer URLs, optional DigiKey.
|
||||
|
||||
DeepSeek-adapted Pinscope still needs the actual PDF. The original wizard
|
||||
DeepSeek-adapted Periscope still needs the actual PDF. The original wizard
|
||||
only auto-fetched via DigiKey, which requires paid API keys and often
|
||||
fails when the manufacturer CDN blocks the download.
|
||||
|
||||
@@ -39,7 +39,7 @@ _PDF_MAGIC = b"%PDF-"
|
||||
_MIN_PDF_SIZE = 5_000
|
||||
_UA = (
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Pinscope/2.8"
|
||||
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Periscope/2.8"
|
||||
)
|
||||
_LCSC_BASE = "https://wmsc.lcsc.com/ftps/wm"
|
||||
|
||||
@@ -154,7 +154,7 @@ def find_local_pdf(pdf_dir: Path, mpn: str) -> Path | None:
|
||||
``ESP32-S31-WROOM-3`` matches ``ESP32-S31-WROOM-3-N16R16V.pdf`` and the
|
||||
reverse — packing / flash-size suffixes, not sibling dies (CH340 vs CH340E).
|
||||
"""
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
|
||||
if not mpn or not pdf_dir.is_dir():
|
||||
return None
|
||||
|
||||
@@ -16,7 +16,7 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
from backend.services.storage import StorageBackend
|
||||
|
||||
BLOB_PREFIX = "library/datasheets/blobs/"
|
||||
|
||||
@@ -25,7 +25,7 @@ import time
|
||||
from datetime import datetime, timezone
|
||||
from typing import Awaitable, Callable
|
||||
|
||||
from backend.pinscopex.models import Finding
|
||||
from backend.periscopex.models import Finding
|
||||
from backend.services.api_logs import ApiLogger
|
||||
from backend.services.llm import Message, TextBlock
|
||||
from backend.services.llm.factory import call_with_fallback
|
||||
|
||||
+34
-34
@@ -174,7 +174,7 @@ def _render_report_email(
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 20px; font-weight: 700; color: #ffffff; letter-spacing: -0.025em;">
|
||||
Pinscope
|
||||
Periscope
|
||||
</td>
|
||||
<td align="right" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.05em;">
|
||||
Report Ready
|
||||
@@ -247,7 +247,7 @@ def _render_report_email(
|
||||
<tr><td style="background-color: #f9fafb; padding: 20px 32px; border-top: 1px solid #e5e7eb;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr><td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;">
|
||||
Pinscope · Agentic schematic validation
|
||||
Periscope · Agentic schematic validation
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
@@ -292,7 +292,7 @@ def _render_pipeline_started_email(
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 20px; font-weight: 700; color: #ffffff; letter-spacing: -0.025em;">
|
||||
Pinscope
|
||||
Periscope
|
||||
</td>
|
||||
<td align="right" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.05em;">
|
||||
Pipeline Started
|
||||
@@ -398,7 +398,7 @@ def _render_pipeline_started_email(
|
||||
<tr><td style="background-color: #f9fafb; padding: 20px 32px; border-top: 1px solid #e5e7eb;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr><td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;">
|
||||
Pinscope · Agentic schematic validation
|
||||
Periscope · Agentic schematic validation
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
@@ -448,7 +448,7 @@ def _build_report_message(
|
||||
) -> MIMEMultipart:
|
||||
"""Build the report-ready email message."""
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = f"Pinscope <{settings.email_sender}>"
|
||||
msg["From"] = f"Periscope <{settings.email_sender}>"
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = f"Report ready: {project_name}"
|
||||
|
||||
@@ -460,7 +460,7 @@ def _build_report_message(
|
||||
infos = summary.get("INFO", 0)
|
||||
text_body = (
|
||||
f"Hi {recipient_name},\n\n"
|
||||
f"Your Pinscope validation report for \"{project_name}\" is ready.\n\n"
|
||||
f"Your Periscope validation report for \"{project_name}\" is ready.\n\n"
|
||||
f"Summary: {total} findings — {errors} errors, {warnings} warnings, {infos} info\n\n"
|
||||
f"View the report: {report_url}\n"
|
||||
)
|
||||
@@ -485,7 +485,7 @@ def _build_paused_message(
|
||||
credits_needed_low: float,
|
||||
) -> MIMEMultipart:
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = f"Pinscope <{settings.email_sender}>"
|
||||
msg["From"] = f"Periscope <{settings.email_sender}>"
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = f"Paused: {project_name} is waiting for credits"
|
||||
|
||||
@@ -495,7 +495,7 @@ def _build_paused_message(
|
||||
|
||||
text_body = (
|
||||
f"Hi {recipient_name},\n\n"
|
||||
f"Your Pinscope run for \"{project_name}\" paused because you're low on credits.\n\n"
|
||||
f"Your Periscope run for \"{project_name}\" paused because you're low on credits.\n\n"
|
||||
f"{last_line}\n{stage_line}\n\n"
|
||||
f"Current balance: {balance:.2f} credits\n"
|
||||
f"Credits needed to finish (est): {credits_needed_low:.2f}+\n\n"
|
||||
@@ -510,13 +510,13 @@ def _build_topup_failed_message(
|
||||
amount_usd: float, reason: str,
|
||||
) -> MIMEMultipart:
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = f"Pinscope <{settings.email_sender}>"
|
||||
msg["From"] = f"Periscope <{settings.email_sender}>"
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = "Pinscope: auto top-up failed"
|
||||
msg["Subject"] = "Periscope: auto top-up failed"
|
||||
manage_url = f"{settings.email_frontend_url}/credits"
|
||||
text_body = (
|
||||
f"Hi {recipient_name},\n\n"
|
||||
f"We tried to auto top-up your Pinscope balance with "
|
||||
f"We tried to auto top-up your Periscope balance with "
|
||||
f"${amount_usd:.2f} but the charge failed.\n\n"
|
||||
f"Reason: {reason}\n\n"
|
||||
f"Auto top-up has been disabled until you update your payment method. "
|
||||
@@ -549,13 +549,13 @@ def _build_low_balance_message(
|
||||
to_email: str, recipient_name: str, balance: float, threshold: float,
|
||||
) -> MIMEMultipart:
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = f"Pinscope <{settings.email_sender}>"
|
||||
msg["From"] = f"Periscope <{settings.email_sender}>"
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = "Pinscope: low credit balance"
|
||||
msg["Subject"] = "Periscope: low credit balance"
|
||||
credits_url = f"{settings.email_frontend_url}/credits"
|
||||
text_body = (
|
||||
f"Hi {recipient_name},\n\n"
|
||||
f"Your Pinscope credit balance has dropped to "
|
||||
f"Your Periscope credit balance has dropped to "
|
||||
f"{balance:.2f} credits (below your threshold of {threshold:.2f}).\n\n"
|
||||
f"Top up here so your pipelines don't pause mid-run: {credits_url}\n"
|
||||
)
|
||||
@@ -691,10 +691,10 @@ async def send_test_email(to_email: str) -> dict:
|
||||
|
||||
result["step"] = "send"
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = f"Pinscope <{settings.email_sender}>"
|
||||
msg["From"] = f"Periscope <{settings.email_sender}>"
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = "Pinscope email test"
|
||||
msg.attach(MIMEText(f"Test email from Pinscope. Sender: {settings.email_sender}. Creds: {cred_type}", "plain"))
|
||||
msg["Subject"] = "Periscope email test"
|
||||
msg.attach(MIMEText(f"Test email from Periscope. Sender: {settings.email_sender}. Creds: {cred_type}", "plain"))
|
||||
|
||||
import asyncio as _asyncio
|
||||
raw = base64.urlsafe_b64encode(msg.as_bytes()).decode("ascii")
|
||||
@@ -741,7 +741,7 @@ async def send_pipeline_started_email(
|
||||
|
||||
# Build message
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = f"Pinscope <{settings.email_sender}>"
|
||||
msg["From"] = f"Periscope <{settings.email_sender}>"
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = f"Pipeline started: {project_name} ({num_components} components)"
|
||||
|
||||
@@ -866,7 +866,7 @@ def _render_feedback_email(
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 20px; font-weight: 700; color: #ffffff; letter-spacing: -0.025em;">
|
||||
Pinscope
|
||||
Periscope
|
||||
</td>
|
||||
<td align="right" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.05em;">
|
||||
Feedback Received
|
||||
@@ -953,7 +953,7 @@ def _render_feedback_email(
|
||||
<tr><td style="background-color: #f9fafb; padding: 20px 32px; border-top: 1px solid #e5e7eb;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr><td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;">
|
||||
Pinscope · Agentic schematic validation
|
||||
Periscope · Agentic schematic validation
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
@@ -1006,7 +1006,7 @@ async def send_feedback_received_email(
|
||||
to_email = settings.email_admin_notify
|
||||
subject_ctx = project_name or "general"
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = f"Pinscope <{settings.email_sender}>"
|
||||
msg["From"] = f"Periscope <{settings.email_sender}>"
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = f"Feedback ({type_label}): {subject_ctx}"
|
||||
|
||||
@@ -1095,7 +1095,7 @@ def _render_feedback_reply_email(
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 20px; font-weight: 700; color: #ffffff; letter-spacing: -0.025em;">
|
||||
Pinscope
|
||||
Periscope
|
||||
</td>
|
||||
<td align="right" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.05em;">
|
||||
New Reply
|
||||
@@ -1113,7 +1113,7 @@ def _render_feedback_reply_email(
|
||||
Hi {_esc(recipient_first_name)},
|
||||
</td></tr>
|
||||
<tr><td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 15px; color: #374151; padding-bottom: 18px;">
|
||||
The Pinscope team just replied to your feedback.
|
||||
The Periscope team just replied to your feedback.
|
||||
</td></tr>
|
||||
|
||||
{context_line}
|
||||
@@ -1124,7 +1124,7 @@ def _render_feedback_reply_email(
|
||||
<tr><td style="padding: 18px 22px;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr><td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 11px; font-weight: 600; color: #047857; text-transform: uppercase; letter-spacing: 0.05em; padding-bottom: 10px;">
|
||||
Pinscope team
|
||||
Periscope team
|
||||
</td></tr>
|
||||
<tr><td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 15px; color: #064e3b; line-height: 1.55; white-space: pre-wrap;">
|
||||
{_esc(reply_text)}
|
||||
@@ -1155,12 +1155,12 @@ def _render_feedback_reply_email(
|
||||
<!--[if mso]>
|
||||
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" href="{feedback_url}" style="height:48px;v-text-anchor:middle;width:240px;" arcsize="14%" fillcolor="#3b82f6" stroke="f">
|
||||
<w:anchorlock/>
|
||||
<center style="color:#ffffff;font-family:sans-serif;font-size:15px;font-weight:bold;">View in Pinscope →</center>
|
||||
<center style="color:#ffffff;font-family:sans-serif;font-size:15px;font-weight:bold;">View in Periscope →</center>
|
||||
</v:roundrect>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<a href="{feedback_url}" target="_blank" style="display: inline-block; background-color: #3b82f6; color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 15px; font-weight: 600; text-decoration: none; padding: 12px 32px; border-radius: 8px; letter-spacing: -0.01em;">
|
||||
View in Pinscope →
|
||||
View in Periscope →
|
||||
</a>
|
||||
<!--<![endif]-->
|
||||
</td></tr>
|
||||
@@ -1170,7 +1170,7 @@ def _render_feedback_reply_email(
|
||||
Thank you so much for taking the time to share your feedback — we truly value it.
|
||||
</td></tr>
|
||||
<tr><td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 15px; color: #374151; padding-top: 6px;">
|
||||
— The Pinscope team
|
||||
— The Periscope team
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
@@ -1180,7 +1180,7 @@ def _render_feedback_reply_email(
|
||||
<tr><td style="background-color: #f9fafb; padding: 20px 32px; border-top: 1px solid #e5e7eb;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr><td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;">
|
||||
Pinscope · Agentic schematic validation
|
||||
Periscope · Agentic schematic validation
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
@@ -1203,7 +1203,7 @@ async def send_feedback_reply_email(
|
||||
finding_designator: str | None = None,
|
||||
finding_mpn: str | None = None,
|
||||
) -> None:
|
||||
"""Notify the original submitter that the Pinscope team replied. Fire-and-forget."""
|
||||
"""Notify the original submitter that the Periscope team replied. Fire-and-forget."""
|
||||
if not settings.use_email:
|
||||
return
|
||||
|
||||
@@ -1229,15 +1229,15 @@ async def send_feedback_reply_email(
|
||||
first_name = full_name.split()[0] if full_name else "there"
|
||||
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = f"Pinscope <{settings.email_sender}>"
|
||||
msg["From"] = f"Periscope <{settings.email_sender}>"
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = "The Pinscope team replied to your feedback"
|
||||
msg["Subject"] = "The Periscope team replied to your feedback"
|
||||
|
||||
# Plain text fallback
|
||||
text_lines = [
|
||||
f"Hi {first_name},",
|
||||
"",
|
||||
"The Pinscope team just replied to your feedback.",
|
||||
"The Periscope team just replied to your feedback.",
|
||||
"",
|
||||
"— Reply —",
|
||||
reply_text,
|
||||
@@ -1245,10 +1245,10 @@ async def send_feedback_reply_email(
|
||||
"— Your original message —",
|
||||
original_message,
|
||||
"",
|
||||
f"View in Pinscope: {settings.email_frontend_url}/feedback",
|
||||
f"View in Periscope: {settings.email_frontend_url}/feedback",
|
||||
"",
|
||||
"Thank you so much for taking the time to share your feedback — we truly value it.",
|
||||
"— The Pinscope team",
|
||||
"— The Periscope team",
|
||||
]
|
||||
msg.attach(MIMEText("\n".join(text_lines), "plain"))
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
from backend.periscopex.models import (
|
||||
CapacitorSpecs,
|
||||
ComponentConstraints,
|
||||
ComponentModel,
|
||||
@@ -27,7 +27,7 @@ from backend.pinscopex.models import (
|
||||
NetType,
|
||||
SimpleComponentSpecs,
|
||||
)
|
||||
from backend.pinscopex.taxonomy import (
|
||||
from backend.periscopex.taxonomy import (
|
||||
TAXONOMY_DIR,
|
||||
add_subtype,
|
||||
format_for_prompt,
|
||||
@@ -399,13 +399,13 @@ def _coerce_abs_max(raw: object) -> list[dict]:
|
||||
|
||||
|
||||
def _coerce_layout_rules(raw: object) -> list[dict]:
|
||||
from backend.pinscopex.layout_rules import validate_layout_rules
|
||||
from backend.periscopex.layout_rules import validate_layout_rules
|
||||
rows, _errors = validate_layout_rules(raw if isinstance(raw, list) else [])
|
||||
return rows
|
||||
|
||||
|
||||
def _coerce_internal_features(raw: object):
|
||||
from backend.pinscopex.models import InternalFeatures
|
||||
from backend.periscopex.models import InternalFeatures
|
||||
if not isinstance(raw, dict):
|
||||
return None
|
||||
try:
|
||||
@@ -1088,7 +1088,7 @@ async def auto_resolve_specs(
|
||||
|
||||
# Convert passive SimpleComponentSpecs to typed models
|
||||
if component_type == "passive":
|
||||
from backend.pinscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
from backend.periscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
typed = simple_to_typed_passive_specs(specs)
|
||||
return ComponentModel(mpn=mpn, specs=typed)
|
||||
|
||||
@@ -1260,7 +1260,7 @@ async def resolve_from_value(
|
||||
)
|
||||
|
||||
if component_type == "passive":
|
||||
from backend.pinscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
from backend.periscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
typed = simple_to_typed_passive_specs(specs)
|
||||
return ComponentModel(mpn=mpn, specs=typed)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ def load_skill_validator(skill_name: str):
|
||||
if not path.is_file():
|
||||
return None
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
f"pinscope_skill_{skill_name.replace('-', '_')}_validate", path,
|
||||
f"periscope_skill_{skill_name.replace('-', '_')}_validate", path,
|
||||
)
|
||||
if spec is None or spec.loader is None:
|
||||
return None
|
||||
|
||||
@@ -15,7 +15,7 @@ import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.pdf_text import (
|
||||
from backend.periscopex.pdf_text import (
|
||||
extract_pdf_document_text,
|
||||
fitz_page_text,
|
||||
page_is_sparse,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Pinscope local JWT helpers (HS256)."""
|
||||
"""Periscope local JWT helpers (HS256)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -21,7 +21,7 @@ def issue_token(user_id: str, email: str) -> str:
|
||||
payload = {
|
||||
"sub": user_id,
|
||||
"email": email,
|
||||
"iss": "pinscope-local",
|
||||
"iss": "periscope-local",
|
||||
"iat": now,
|
||||
"exp": now + timedelta(days=TOKEN_TTL_DAYS),
|
||||
}
|
||||
@@ -37,7 +37,7 @@ def decode_token(token: str) -> dict[str, Any] | None:
|
||||
token,
|
||||
secret,
|
||||
algorithms=[ALGORITHM],
|
||||
issuer="pinscope-local",
|
||||
issuer="periscope-local",
|
||||
options={"verify_aud": False},
|
||||
leeway=10,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Local Pinscope user directory (self-host auth, no Clerk).
|
||||
"""Local Periscope user directory (self-host auth, no Clerk).
|
||||
|
||||
Users live under ``data/auth/users/{user_id}.json`` with an email index.
|
||||
Passwords use stdlib ``hashlib.scrypt``.
|
||||
|
||||
@@ -23,7 +23,7 @@ from datetime import datetime, timezone
|
||||
from typing import Awaitable, Callable
|
||||
|
||||
from backend.config import settings
|
||||
from backend.pinscopex.models import Finding
|
||||
from backend.periscopex.models import Finding
|
||||
from backend.services.api_logs import ApiLogger
|
||||
from backend.services.llm import Message, TextBlock
|
||||
from backend.services.llm.factory import call_with_fallback
|
||||
|
||||
@@ -8,8 +8,8 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from backend.pinscopex.models import ComponentModel, SimpleComponentSpecs
|
||||
from backend.pinscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
from backend.periscopex.models import ComponentModel, SimpleComponentSpecs
|
||||
from backend.periscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
|
||||
_CAP = re.compile(
|
||||
r"(?P<num>\d+(?:\.\d+)?)\s*(?P<mul>[pnuμµmk])?\s*[fF]\b",
|
||||
|
||||
@@ -9,8 +9,8 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from backend.pinscopex.models import ComponentModel, SimpleComponentSpecs
|
||||
from backend.pinscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
from backend.periscopex.models import ComponentModel, SimpleComponentSpecs
|
||||
from backend.periscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
from backend.services.passive_from_distributor import _spice
|
||||
|
||||
_SIZE = r"(?:0201|0402|0603|0805|1206|1210|1812|2010|2512)"
|
||||
|
||||
@@ -4,8 +4,8 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from backend.pinscopex.models import ComponentModel, SimpleComponentSpecs
|
||||
from backend.pinscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
from backend.periscopex.models import ComponentModel, SimpleComponentSpecs
|
||||
from backend.periscopex.resolve_passives import simple_to_typed_passive_specs
|
||||
from backend.services.passive_from_distributor import _spice
|
||||
|
||||
_PLACEHOLDER = re.compile(
|
||||
|
||||
@@ -33,15 +33,15 @@ from pathlib import Path
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
|
||||
from backend.pinscopex.models import ComponentType
|
||||
from backend.pinscopex.utils import natural_sort_key, safe_mpn
|
||||
from backend.pinscopex.bom_summary import build_bom_summary
|
||||
from backend.pinscopex.derating import build_derating_table
|
||||
from backend.pinscopex.validate import _load_datasheets
|
||||
from backend.pinscopex.graph import build_graph
|
||||
from backend.pinscopex.parsers import parse_bom, parse_netlist_any
|
||||
from backend.pinscopex.resolve_passives import SkippedItem, load_patterns, resolve_mpn
|
||||
from backend.pinscopex.taxonomy import SIMPLE_TYPES, type_for_ref
|
||||
from backend.periscopex.models import ComponentType
|
||||
from backend.periscopex.utils import natural_sort_key, safe_mpn
|
||||
from backend.periscopex.bom_summary import build_bom_summary
|
||||
from backend.periscopex.derating import build_derating_table
|
||||
from backend.periscopex.validate import _load_datasheets
|
||||
from backend.periscopex.graph import build_graph
|
||||
from backend.periscopex.parsers import parse_bom, parse_netlist_any
|
||||
from backend.periscopex.resolve_passives import SkippedItem, load_patterns, resolve_mpn
|
||||
from backend.periscopex.taxonomy import SIMPLE_TYPES, type_for_ref
|
||||
|
||||
from backend.config import settings
|
||||
from backend.services import admin_settings as settings_svc
|
||||
@@ -196,7 +196,7 @@ def _cancel_gate_check(ctx: PipelineContext) -> None:
|
||||
class PipelineWorkspace:
|
||||
"""Downloads project files from storage to a temp dir for pipeline execution.
|
||||
|
||||
The pinscopex core library operates on local paths. This context manager
|
||||
The periscopex core library operates on local paths. This context manager
|
||||
downloads inputs at enter, provides local paths, and uploads results at exit.
|
||||
"""
|
||||
|
||||
@@ -264,7 +264,7 @@ class PipelineWorkspace:
|
||||
self._upload_file("bom_summary.json")
|
||||
self._upload_file("derating.json")
|
||||
self._upload_file("report.json")
|
||||
self._upload_file("pinscope-findings.json")
|
||||
self._upload_file("periscope-findings.json")
|
||||
self._upload_file("review_fingerprints.json")
|
||||
self._upload_file("api_logs.jsonl")
|
||||
|
||||
@@ -763,7 +763,7 @@ async def _stage_ic_extraction(ctx: PipelineContext) -> None:
|
||||
|
||||
# Pre-categorize: workspace cache, library cache, or needs extraction
|
||||
from backend.config import settings as app_settings
|
||||
from backend.pinscopex.layout_rules import needs_layout_rules_refresh
|
||||
from backend.periscopex.layout_rules import needs_layout_rules_refresh
|
||||
|
||||
layout_scan_ver = app_settings.get_default_model_version()
|
||||
_ic_cache: dict[str, tuple] = {}
|
||||
@@ -863,7 +863,7 @@ async def _stage_ic_extraction(ctx: PipelineContext) -> None:
|
||||
extracted_key = f"{ctx.ws.prefix}/extracted/{safe}.json"
|
||||
ctx.storage.upload_from_local(json_path, extracted_key)
|
||||
try:
|
||||
from backend.pinscopex.library_gate import should_promote_extraction
|
||||
from backend.periscopex.library_gate import should_promote_extraction
|
||||
|
||||
payload = json.loads(json_path.read_text(encoding="utf-8"))
|
||||
ok, reason = should_promote_extraction(payload)
|
||||
@@ -1545,7 +1545,7 @@ def _write_layout_graph(ws: PipelineWorkspace, project_id: str) -> None:
|
||||
if not pcb.is_file():
|
||||
return
|
||||
try:
|
||||
from backend.pinscopex.parsers_kicad_pcb import parse_kicad_pcb
|
||||
from backend.periscopex.parsers_kicad_pcb import parse_kicad_pcb
|
||||
|
||||
layout = parse_kicad_pcb(pcb)
|
||||
out = ws.local_path("layout_graph.json")
|
||||
@@ -1561,8 +1561,8 @@ def _write_layout_graph(ws: PipelineWorkspace, project_id: str) -> None:
|
||||
def _write_functional_groups(ws: PipelineWorkspace, graph) -> None:
|
||||
"""Layout F1: topology domains/groups (no mm). Fail-soft."""
|
||||
try:
|
||||
from backend.pinscopex.functional_groups import build_functional_groups
|
||||
from backend.pinscopex.validate import _build_constraints_map, _load_datasheets
|
||||
from backend.periscopex.functional_groups import build_functional_groups
|
||||
from backend.periscopex.validate import _build_constraints_map, _load_datasheets
|
||||
|
||||
extracted_dir = ws.local_path("extracted")
|
||||
cmap = {}
|
||||
@@ -1584,8 +1584,8 @@ def _write_impedance_nets(ws: PipelineWorkspace, graph) -> None:
|
||||
if not path.is_file():
|
||||
return
|
||||
try:
|
||||
from backend.pinscopex.impedance_traces import analyze_where_needed
|
||||
from backend.pinscopex.models import LayoutGraph
|
||||
from backend.periscopex.impedance_traces import analyze_where_needed
|
||||
from backend.periscopex.models import LayoutGraph
|
||||
|
||||
layout = LayoutGraph.model_validate_json(path.read_text())
|
||||
report = analyze_where_needed(layout, graph)
|
||||
@@ -1792,11 +1792,11 @@ async def _stage_validation(ctx: PipelineContext) -> None:
|
||||
fp_path = ctx.ws.local_path("review_fingerprints.json")
|
||||
current_fp: dict[str, str] = {}
|
||||
try:
|
||||
from backend.pinscopex.review_fingerprint import (
|
||||
from backend.periscopex.review_fingerprint import (
|
||||
graph_ic_fingerprints,
|
||||
skip_unchanged_ics,
|
||||
)
|
||||
from backend.pinscopex.validate import _build_constraints_map, _load_datasheets
|
||||
from backend.periscopex.validate import _build_constraints_map, _load_datasheets
|
||||
|
||||
cmap = _build_constraints_map(_load_datasheets(extracted_dir))
|
||||
current_fp = graph_ic_fingerprints(ctx.graph, cmap)
|
||||
|
||||
@@ -11,10 +11,10 @@ from __future__ import annotations
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.functional_groups import build_placement_plan
|
||||
from backend.pinscopex.graph import build_graph
|
||||
from backend.pinscopex.models import ComponentConstraints, DesignGraph, LayoutGraph
|
||||
from backend.pinscopex.placement_pack import build_placement_pack
|
||||
from backend.periscopex.functional_groups import build_placement_plan
|
||||
from backend.periscopex.graph import build_graph
|
||||
from backend.periscopex.models import ComponentConstraints, DesignGraph, LayoutGraph
|
||||
from backend.periscopex.placement_pack import build_placement_pack
|
||||
from backend.services import projects as proj_svc
|
||||
from backend.services.pipeline import PipelineWorkspace, broker
|
||||
from backend.services.storage import StorageBackend
|
||||
@@ -218,7 +218,7 @@ def _load_layout(ws: PipelineWorkspace) -> LayoutGraph | None:
|
||||
if not pcb.is_file():
|
||||
return None
|
||||
try:
|
||||
from backend.pinscopex.parsers_kicad_pcb import parse_kicad_pcb
|
||||
from backend.periscopex.parsers_kicad_pcb import parse_kicad_pcb
|
||||
|
||||
layout = parse_kicad_pcb(pcb)
|
||||
cached.write_text(layout.model_dump_json(indent=2) + "\n")
|
||||
|
||||
@@ -11,7 +11,7 @@ Each project lives at users/{user_id}/projects/{id}/ with:
|
||||
models/ — cached component specs
|
||||
design_graph.json — graph output
|
||||
report.json — validation report
|
||||
pinscope-findings.json — KiCad cad-bridge (plugin pan-and-zoom)
|
||||
periscope-findings.json — KiCad cad-bridge (plugin pan-and-zoom)
|
||||
|
||||
Library (global, shared across users):
|
||||
library/extracted/{mpn}.json
|
||||
@@ -31,9 +31,9 @@ from typing import Any
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import AliasChoices, BaseModel, Field
|
||||
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
from backend.services.storage import StaleGeneration, StorageBackend
|
||||
|
||||
|
||||
@@ -120,9 +120,13 @@ class ProjectMeta(BaseModel):
|
||||
pause_reason: str | None = None
|
||||
completed_review_refs: list[str] = [] # IC refs already reviewed (persists across pauses)
|
||||
|
||||
# Pinscope app version that generated the project's report.
|
||||
# Periscope app version that generated the project's report.
|
||||
# Stamped on the first /start transition and preserved thereafter.
|
||||
pinscope_version: str | None = None
|
||||
# Accept legacy pinscope_version from project.json written before the rebrand.
|
||||
periscope_version: str | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("periscope_version", "pinscope_version"),
|
||||
)
|
||||
|
||||
# Worker bookkeeping (set by the API on enqueue, read by /events SSE
|
||||
# and by the stale-running sweeper).
|
||||
@@ -166,7 +170,7 @@ def completed_review_refs_for_retry(
|
||||
for ref in (report.get("review_errors") or {}):
|
||||
if ref:
|
||||
failed.add(str(ref))
|
||||
from backend.pinscopex.utils import natural_sort_key
|
||||
from backend.periscopex.utils import natural_sort_key
|
||||
kept = [r for r in (meta.completed_review_refs or []) if r and r not in failed]
|
||||
return sorted(kept, key=natural_sort_key)
|
||||
|
||||
@@ -484,7 +488,7 @@ def clear_project_extractions(
|
||||
"bom_summary.json",
|
||||
"derating.json",
|
||||
"report.json",
|
||||
"pinscope-findings.json",
|
||||
"periscope-findings.json",
|
||||
"review_fingerprints.json",
|
||||
"api_logs.jsonl",
|
||||
"graph_voltage_updates.json",
|
||||
@@ -519,7 +523,7 @@ def reopen_project(
|
||||
"bom_summary.json",
|
||||
"derating.json",
|
||||
"report.json",
|
||||
"pinscope-findings.json",
|
||||
"periscope-findings.json",
|
||||
"review_fingerprints.json",
|
||||
"api_logs.jsonl",
|
||||
"graph_voltage_updates.json",
|
||||
@@ -936,7 +940,7 @@ def library_has_datasheet(
|
||||
return key
|
||||
# 3. Pattern-based fallback for passives
|
||||
if patterns:
|
||||
from backend.pinscopex.resolve_passives import resolve_mpn
|
||||
from backend.periscopex.resolve_passives import resolve_mpn
|
||||
|
||||
match = resolve_mpn(mpn, patterns)
|
||||
if match is not None:
|
||||
@@ -1121,11 +1125,11 @@ def list_library_patterns(storage: StorageBackend) -> list[str]:
|
||||
def load_library_patterns(storage: StorageBackend):
|
||||
"""Load and parse all passive patterns from the library.
|
||||
|
||||
For local backend, delegates to pinscopex. For GCS, downloads to temp first.
|
||||
For local backend, delegates to periscopex. For GCS, downloads to temp first.
|
||||
This function is only used by the library/check endpoint — during pipeline
|
||||
execution, patterns are loaded from the workspace temp directory.
|
||||
"""
|
||||
from backend.pinscopex.resolve_passives import load_patterns
|
||||
from backend.periscopex.resolve_passives import load_patterns
|
||||
|
||||
from backend.services.storage import LocalStorageBackend
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from typing import Awaitable, Callable
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from backend.pinscopex.models import (
|
||||
from backend.periscopex.models import (
|
||||
ComponentConstraints,
|
||||
ComponentType,
|
||||
DesignGraph,
|
||||
@@ -30,7 +30,7 @@ from backend.pinscopex.models import (
|
||||
NetType,
|
||||
ValidationReport,
|
||||
)
|
||||
from backend.pinscopex.validate import (
|
||||
from backend.periscopex.validate import (
|
||||
SYSTEM_PROMPT,
|
||||
_MAX_REVIEW_TURNS,
|
||||
ReviewResult,
|
||||
@@ -41,30 +41,30 @@ from backend.pinscopex.validate import (
|
||||
build_component_context,
|
||||
_parse_review,
|
||||
)
|
||||
from backend.pinscopex.quote_verify import verify_finding_citations
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.pinscopex.pin_mux_check import check_pin_mux_feasibility
|
||||
from backend.pinscopex.led_current_check import check_led_current
|
||||
from backend.pinscopex.passive_rail_check import (
|
||||
from backend.periscopex.quote_verify import verify_finding_citations
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
from backend.periscopex.pin_mux_check import check_pin_mux_feasibility
|
||||
from backend.periscopex.led_current_check import check_led_current
|
||||
from backend.periscopex.passive_rail_check import (
|
||||
check_i2c_pullups,
|
||||
check_reset_pullups,
|
||||
check_supply_decoupling,
|
||||
)
|
||||
from backend.pinscopex.bom_match_check import check_bom_schematic_match
|
||||
from backend.pinscopex.hf_coverage_check import check_hf_decoupling_coverage
|
||||
from backend.pinscopex.cad_bridge import annotate_findings_cad, build_cad_bridge, write_cad_bridge
|
||||
from backend.pinscopex.filter_check import check_filters
|
||||
from backend.pinscopex.thermal_check import check_thermal
|
||||
from backend.pinscopex.power_margin_check import check_power_margin
|
||||
from backend.pinscopex.sequencing_check import check_power_sequencing
|
||||
from backend.pinscopex.dnp_check import check_dnp_enables
|
||||
from backend.pinscopex.lifecycle import check_lifecycle, load_lifecycle_dir
|
||||
from backend.pinscopex.errata_check import check_errata
|
||||
from backend.pinscopex.internal_features_check import check_internal_features
|
||||
from backend.pinscopex.placement_check import check_placement
|
||||
from backend.pinscopex.si_check import check_si
|
||||
from backend.pinscopex.crystal_cl_check import check_crystal_cl
|
||||
from backend.pinscopex.nc_pin_check import check_nc_pins
|
||||
from backend.periscopex.bom_match_check import check_bom_schematic_match
|
||||
from backend.periscopex.hf_coverage_check import check_hf_decoupling_coverage
|
||||
from backend.periscopex.cad_bridge import annotate_findings_cad, build_cad_bridge, write_cad_bridge
|
||||
from backend.periscopex.filter_check import check_filters
|
||||
from backend.periscopex.thermal_check import check_thermal
|
||||
from backend.periscopex.power_margin_check import check_power_margin
|
||||
from backend.periscopex.sequencing_check import check_power_sequencing
|
||||
from backend.periscopex.dnp_check import check_dnp_enables
|
||||
from backend.periscopex.lifecycle import check_lifecycle, load_lifecycle_dir
|
||||
from backend.periscopex.errata_check import check_errata
|
||||
from backend.periscopex.internal_features_check import check_internal_features
|
||||
from backend.periscopex.placement_check import check_placement
|
||||
from backend.periscopex.si_check import check_si
|
||||
from backend.periscopex.crystal_cl_check import check_crystal_cl
|
||||
from backend.periscopex.nc_pin_check import check_nc_pins
|
||||
|
||||
TRACE_VERSION = 1
|
||||
|
||||
@@ -139,14 +139,14 @@ def _assistant_text(blocks) -> str:
|
||||
except Exception:
|
||||
log.exception("trace: assistant_text extraction failed")
|
||||
return "\n".join(parts)
|
||||
from backend.pinscopex.validation_tools import (
|
||||
from backend.periscopex.validation_tools import (
|
||||
ALL_TOOLS,
|
||||
SUBMIT_REVIEW_SCHEMA,
|
||||
ConstraintsMap,
|
||||
ExcerptState,
|
||||
execute_tool,
|
||||
)
|
||||
from backend.pinscopex.utils import safe_mpn
|
||||
from backend.periscopex.utils import safe_mpn
|
||||
|
||||
from backend.config import settings
|
||||
from backend.services.api_logs import ApiLogger
|
||||
@@ -639,7 +639,7 @@ def _find_pdf(
|
||||
then tries to download from the library.
|
||||
"""
|
||||
from backend.services.datasheet_finder import find_local_pdf
|
||||
from backend.pinscopex.utils import safe_mpn as _safe
|
||||
from backend.periscopex.utils import safe_mpn as _safe
|
||||
|
||||
mpn = (mpn or "").strip()
|
||||
if not mpn:
|
||||
@@ -836,7 +836,7 @@ async def validate_design_async(
|
||||
try:
|
||||
prefix_id = (project_prefix or "").rstrip("/").rsplit("/", 1)[-1]
|
||||
bridge = build_cad_bridge(report, prefix_id or report.project)
|
||||
write_cad_bridge(existing_path.with_name("pinscope-findings.json"), bridge)
|
||||
write_cad_bridge(existing_path.with_name("periscope-findings.json"), bridge)
|
||||
except Exception:
|
||||
log.exception("cad bridge write failed")
|
||||
return report
|
||||
|
||||
Reference in New Issue
Block a user