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).
146 lines
4.2 KiB
Python
146 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Clear the deprecated `rules` field from all extracted component JSON files in GCS.
|
|
|
|
Since the validation pipeline now reads datasheets directly instead of relying on
|
|
pre-extracted rules, the `rules` list in every ComponentConstraints JSON is stale
|
|
and should be emptied.
|
|
|
|
Scans:
|
|
library/extracted/*.json — shared library IC extractions
|
|
users/*/projects/*/extracted/*.json — per-project IC extractions
|
|
|
|
Sets `rules` to [] on any file where it is non-empty.
|
|
|
|
Works with both LocalStorageBackend and GCSStorageBackend depending on
|
|
whether GCS_BUCKET is set.
|
|
|
|
Usage:
|
|
# Dry run (default) — shows what would be changed
|
|
python -m scripts.clear_rules_from_extractions
|
|
|
|
# Actually apply changes
|
|
python -m scripts.clear_rules_from_extractions --apply
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
|
|
|
|
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 find_extraction_keys(storage) -> list[str]:
|
|
"""Return all extracted component JSON keys across library and per-project paths."""
|
|
keys: list[str] = []
|
|
|
|
# Shared library extractions
|
|
for key in storage.list_recursive("library/extracted/"):
|
|
if key.endswith(".json"):
|
|
keys.append(key)
|
|
|
|
# Per-project extractions: users/{uid}/projects/{pid}/extracted/*.json
|
|
# We can't know all user IDs in advance, so list from the top.
|
|
try:
|
|
user_prefixes = [
|
|
k for k in storage.list_prefix("users/")
|
|
if not k.endswith(".json")
|
|
]
|
|
except Exception:
|
|
user_prefixes = []
|
|
|
|
for user_prefix in user_prefixes:
|
|
projects_prefix = user_prefix.rstrip("/") + "/projects/"
|
|
try:
|
|
project_prefixes = [
|
|
k for k in storage.list_prefix(projects_prefix)
|
|
if not k.endswith(".json")
|
|
]
|
|
except Exception:
|
|
continue
|
|
|
|
for project_prefix in project_prefixes:
|
|
extracted_prefix = project_prefix.rstrip("/") + "/extracted/"
|
|
for key in storage.list_recursive(extracted_prefix):
|
|
if key.endswith(".json"):
|
|
keys.append(key)
|
|
|
|
return sorted(set(keys))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Clear deprecated 'rules' field from extracted component JSON files"
|
|
)
|
|
parser.add_argument(
|
|
"--apply",
|
|
action="store_true",
|
|
help="Actually write changes (default is dry run)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
storage = get_storage()
|
|
print(f"Storage backend: {type(storage).__name__}")
|
|
|
|
keys = find_extraction_keys(storage)
|
|
print(f"Found {len(keys)} extracted JSON file(s) to inspect\n")
|
|
|
|
changed = 0
|
|
skipped = 0
|
|
errors = 0
|
|
|
|
for key in keys:
|
|
try:
|
|
data = storage.read_json(key)
|
|
except Exception as exc:
|
|
print(f" ERROR reading {key}: {exc}")
|
|
errors += 1
|
|
continue
|
|
|
|
rules = data.get("rules")
|
|
if not rules:
|
|
# Already empty or missing — nothing to do
|
|
skipped += 1
|
|
continue
|
|
|
|
rule_count = len(rules)
|
|
data["rules"] = []
|
|
|
|
if args.apply:
|
|
try:
|
|
storage.write_json(key, data)
|
|
print(f" CLEARED {key} ({rule_count} rule(s) removed)")
|
|
except Exception as exc:
|
|
print(f" ERROR writing {key}: {exc}")
|
|
errors += 1
|
|
continue
|
|
else:
|
|
print(f" WOULD CLEAR {key} ({rule_count} rule(s))")
|
|
|
|
changed += 1
|
|
|
|
print()
|
|
if args.apply:
|
|
print(
|
|
f"Done: {changed} file(s) updated, {skipped} already empty, {errors} error(s)"
|
|
)
|
|
else:
|
|
print(
|
|
f"Dry run: {changed} file(s) would be updated, {skipped} already empty, {errors} error(s)"
|
|
)
|
|
if changed > 0:
|
|
print("Run with --apply to execute")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|