MEGAINFRA-WEBAPP/scripts/migrate-delivery-notes-ocr.py

140 lines
5.3 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
One-off migration to add OCR pipeline fields to the delivery_notes collection
and to extend the data_import_batches batch_type select with delivery_notes.
Run once after deploying the OCR webhook code. Idempotent — re-running on a
collection that already has the field is a no-op.
The python bootstrap script (pb-bootstrap-schema.py) only creates collections
that don't exist; it can't add fields to an existing one. This script does.
Required: POCKETBASE_ADMIN_TOKEN (or email + password) in env / .env.local.
"""
import json
import os
import sys
import urllib.error
import urllib.request
PB = os.environ.get("NEXT_PUBLIC_POCKETBASE_URL", "https://pb.mega.infra.apphub.co.za")
EMAIL = os.environ.get("POCKETBASE_ADMIN_EMAIL", "admin@mega.co.za")
PASSWORD = os.environ.get("POCKETBASE_ADMIN_PASSWORD", "12345678")
def http(method, path, body=None, token=None):
url = f"{PB}{path}"
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, method=method)
if body is not None:
req.add_header("Content-Type", "application/json")
if token:
req.add_header("Authorization", token)
try:
with urllib.request.urlopen(req) as r:
txt = r.read().decode()
return r.status, json.loads(txt) if txt else None
except urllib.error.HTTPError as e:
return e.code, json.loads(e.read().decode() or "{}")
def login() -> str:
token = os.environ.get("POCKETBASE_ADMIN_TOKEN")
if token:
# Validate it; if it's expired, fall through to password auth.
code, _ = http("GET", "/api/collections/_superusers/auth-refresh", token=token)
if code == 200:
print("auth ok (existing token)")
return token
print("existing token rejected, falling back to email/password…")
code, auth = http(
"POST",
"/api/collections/_superusers/auth-with-password",
{"identity": EMAIL, "password": PASSWORD},
)
if code != 200:
sys.exit(f"AUTH FAILED: {code} {auth}")
print(f"auth ok, admin id={auth['record']['id']}")
return auth["token"]
def get_collection(token: str, name: str):
code, resp = http("GET", f"/api/collections/{name}", token=token)
if code != 200:
sys.exit(f"GET collection {name} failed: {code} {resp}")
return resp
def patch_collection(token: str, collection_id: str, patch: dict):
code, resp = http("PATCH", f"/api/collections/{collection_id}", patch, token)
if code != 200:
sys.exit(f"PATCH collection {collection_id} failed: {code} {resp}")
return resp
def field(name, type_, **kw):
f = {"name": name, "type": type_}
f.update(kw)
return f
def ensure_field(existing_fields: list, want: dict) -> bool:
"""Return True if the field was added."""
name = want["name"]
if any(f.get("name") == name for f in existing_fields):
print(f" • {name:25s} already exists — skipping")
return False
return True
def migrate_delivery_notes(token: str):
print("\n→ delivery_notes")
coll = get_collection(token, "delivery_notes")
fields = list(coll.get("fields", []))
additions = []
if ensure_field(fields, field("processing_status", "select", required=False,
values=["pending", "ocr-complete", "auto-verified",
"needs-review", "manual-verified", "failed"],
maxSelect=1)):
additions.append(field("processing_status", "select", required=False,
values=["pending", "ocr-complete", "auto-verified",
"needs-review", "manual-verified", "failed"],
maxSelect=1))
if ensure_field(fields, field("ocr_confidence", "number", required=False)):
additions.append(field("ocr_confidence", "number", required=False))
if ensure_field(fields, field("ocr_completed_at", "date", required=False)):
additions.append(field("ocr_completed_at", "date", required=False))
if ensure_field(fields, field("received_by_name", "text", required=False)):
additions.append(field("received_by_name", "text", required=False))
if additions:
patch_collection(token, coll["id"], {"fields": fields + additions})
for a in additions:
print(f" ✓ added {a['name']}")
else:
print(" • no changes needed")
def migrate_data_import_batches(token: str):
print("\n→ data_import_batches")
coll = get_collection(token, "data_import_batches")
fields = list(coll.get("fields", []))
# Find the batch_type field; extend its `values` to include delivery_notes.
bt = next((f for f in fields if f.get("name") == "batch_type"), None)
if not bt:
sys.exit("batch_type field not found on data_import_batches")
if "delivery_notes" in (bt.get("values") or []):
print(" • batch_type already includes delivery_notes — skipping")
return
bt["values"] = list(bt.get("values") or []) + ["delivery_notes"]
patch_collection(token, coll["id"], {"fields": fields})
print(" ✓ added delivery_notes to batch_type select")
if __name__ == "__main__":
token = login()
migrate_delivery_notes(token)
migrate_data_import_batches(token)
print("\nDone.")