Pinscope open-source core
Agentic schematic validation: datasheet extraction via Claude Console Skills, netlist/BOM design graph, per-IC direct datasheet review with page citations, capacitor derating, Next.js report UI. Extracted from the Pinscope cloud codebase. Auth and billing live in the private gateway repo behind stable seams (billing_hook.py, adapter files listed in CLAUDE.md).
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""One-time migration: copy per-project datasheets into library/datasheets/.
|
||||
|
||||
Scans all users/*/projects/*/uploads/datasheets/*.pdf and copies each to
|
||||
library/datasheets/{filename} if it doesn't already exist there.
|
||||
|
||||
Works with both LocalStorageBackend and GCSStorageBackend depending on
|
||||
whether GCS_BUCKET is set.
|
||||
|
||||
Usage:
|
||||
# Dry run (default) — shows what would be copied
|
||||
python -m scripts.migrate_datasheets_to_library
|
||||
|
||||
# Actually copy
|
||||
python -m scripts.migrate_datasheets_to_library --apply
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
def get_storage():
|
||||
from backend.config import settings
|
||||
|
||||
if settings.gcs_bucket:
|
||||
from backend.services.storage_gcs import GCSStorageBackend
|
||||
return GCSStorageBackend(settings.gcs_bucket)
|
||||
else:
|
||||
from backend.services.storage import LocalStorageBackend
|
||||
return LocalStorageBackend(settings.data_dir)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Migrate per-project datasheets to library/datasheets/")
|
||||
parser.add_argument("--apply", action="store_true", help="Actually copy files (default is dry run)")
|
||||
args = parser.parse_args()
|
||||
|
||||
storage = get_storage()
|
||||
print(f"Storage backend: {type(storage).__name__}")
|
||||
|
||||
from backend.services.datasheet_store import resolve_datasheet, store_datasheet_bytes
|
||||
|
||||
# Find all per-project datasheet PDFs
|
||||
all_keys = storage.list_recursive("users/")
|
||||
datasheet_keys = [k for k in all_keys if "/uploads/datasheets/" in k and k.endswith(".pdf")]
|
||||
|
||||
print(f"Found {len(datasheet_keys)} per-project datasheet(s)")
|
||||
|
||||
copied = 0
|
||||
skipped = 0
|
||||
|
||||
for key in datasheet_keys:
|
||||
filename = key.rsplit("/", 1)[-1]
|
||||
mpn = filename.removesuffix(".pdf")
|
||||
|
||||
# Skip if already in library (ref-based or legacy flat file)
|
||||
if resolve_datasheet(storage, mpn) is not None:
|
||||
print(f" SKIP {filename} (already in library)")
|
||||
skipped += 1
|
||||
continue
|
||||
lib_key = f"library/datasheets/{filename}"
|
||||
if storage.exists(lib_key):
|
||||
print(f" SKIP {filename} (legacy flat file exists)")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
if args.apply:
|
||||
data = storage.read_bytes(key)
|
||||
bk = store_datasheet_bytes(storage, data, mpn)
|
||||
print(f" STORE {key} -> {bk}")
|
||||
copied += 1
|
||||
else:
|
||||
print(f" WOULD STORE {key} -> blob + ref")
|
||||
copied += 1
|
||||
|
||||
print()
|
||||
if args.apply:
|
||||
print(f"Done: {copied} stored, {skipped} skipped")
|
||||
else:
|
||||
print(f"Dry run: {copied} would be stored, {skipped} already in library")
|
||||
if copied > 0:
|
||||
print("Run with --apply to execute")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user