WFDASH-WEBAPP/scripts/smoke/cleanup-junk-data.py
SJCoetz 7df7a8850f feat(item-history): add getItemHistory service with 5 PB queries
Co-authored-by: Orca <help@stably.ai>
2026-07-29 14:51:58 +02:00

147 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""
WFDash PB data cleanup — removes junk placeholder/smoke-test records,
fixes two quote status anomalies, and clears {0,0} (null-island)
coordinates so the map doesn't render ocean pins.
Safe: checks for references before deleting. DRY-RUN by default; pass
--apply to actually mutate.
"""
import sys, json, urllib.request, urllib.error
PB = "https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io"
ADMIN_EMAIL = "david.els1@outlook.com"
ADMIN_PASS = "1234567890"
APPLY = "--apply" in sys.argv
def req(method, path, body=None, token=None):
url = f"{PB}{path}"
data = json.dumps(body).encode() if body is not None else None
r = urllib.request.Request(url, data=data, method=method)
r.add_header("Content-Type", "application/json")
if token:
r.add_header("Authorization", token)
try:
with urllib.request.urlopen(r) as resp:
return json.loads(resp.read()) if resp.status != 204 else {}
except urllib.error.HTTPError as e:
return {"__error": e.code, "__body": e.read().decode()[:300]}
# 1. Auth
auth = req("POST", "/api/collections/_superusers/auth-with-password",
{"identity": ADMIN_EMAIL, "password": ADMIN_PASS})
token = auth.get("token", "")
if not token:
print("AUTH FAILED:", auth); sys.exit(1)
print(f"[auth] admin token ok ({len(token)} chars)")
def list_all(collection, filter_str="", per_page=500):
out = []
page = 1
while True:
f = f"&filter={urllib.parse.quote(filter_str)}" if filter_str else ""
r = req("GET", f"/api/collections/{collection}/records?page={page}&perPage={per_page}{f}", token=token)
if "__error" in r:
print(f" [warn] list {collection} failed: {r['__error']}"); break
items = r.get("items", [])
out.extend(items)
if page >= r.get("totalPages", 1): break
page += 1
return out
import urllib.parse
# 2. Find junk records
print("\n=== Finding junk records ===")
companies = list_all("companies")
customers = list_all("customers")
junk_company_ids = [c["id"] for c in companies
if c.get("company","").startswith("SELECT/CREATE") or c.get("company","").startswith("SMOKE-CO")]
junk_customer_ids = [c["id"] for c in customers
if c.get("customer","").startswith("SELECT/CREATE")]
print(f" junk companies: {junk_company_ids}")
print(f" junk customers: {junk_customer_ids}")
# 3. Check references before deleting
print("\n=== Checking references ===")
# Quotes that reference junk customers or companies
quotes = list_all("quotes")
ref_customers = set(junk_customer_ids)
ref_companies = set(junk_company_ids)
quotes_blocked = [q for q in quotes if q.get("customer") in ref_customers or q.get("company") in ref_companies]
print(f" quotes referencing junk customer/company: {len(quotes_blocked)}")
if quotes_blocked:
for q in quotes_blocked[:5]:
print(f" {q.get('quote_number')} (customer={q.get('customer')}, company={q.get('company')})")
# Orders referencing junk
orders = list_all("orders")
orders_blocked = [o for o in orders if o.get("customer") in ref_customers or o.get("company") in ref_companies]
print(f" orders referencing junk customer/company: {len(orders_blocked)}")
# Customers referencing junk company
cust_blocked = [c for c in customers if c.get("company") in ref_companies and c["id"] not in junk_customer_ids]
print(f" non-junk customers referencing junk company: {len(cust_blocked)}")
# 4. Delete junk (customers first, then companies — avoids dangling customer→company ref)
print("\n=== Deleting junk ===")
deleted = 0
for cid in junk_customer_ids:
if quotes_blocked or orders_blocked or cust_blocked:
# only block if THIS customer is referenced
this_ref = any(q.get("customer")==cid for q in quotes_blocked) or any(o.get("customer")==cid for o in orders_blocked)
if this_ref:
print(f" SKIP customer {cid} — referenced by quotes/orders"); continue
if APPLY:
r = req("DELETE", f"/api/collections/customers/records/{cid}", token=token)
if "__error" in r: print(f" FAIL customer {cid}: {r['__error']} {r['__body']}")
else: print(f" DELETED customer {cid}"); deleted += 1
else:
print(f" [dry-run] would delete customer {cid}"); deleted += 1
for cid in junk_company_ids:
# check no remaining references
still_ref = any(q.get("company")==cid for q in quotes) or any(o.get("company")==cid for o in orders) or any(c.get("company")==cid for c in customers if c["id"] not in junk_customer_ids)
if still_ref:
print(f" SKIP company {cid} — still referenced"); continue
if APPLY:
r = req("DELETE", f"/api/collections/companies/records/{cid}", token=token)
if "__error" in r: print(f" FAIL company {cid}: {r['__error']} {r['__body']}")
else: print(f" DELETED company {cid}"); deleted += 1
else:
print(f" [dry-run] would delete company {cid}"); deleted += 1
# 5. Fix quote status anomalies
print("\n=== Fixing quote status anomalies ===")
fixed = 0
for q in quotes:
qid = q["id"]; qnum = q.get("quote_number",""); st = q.get("status",""); cb = q.get("claimed_by","")
if st == "":
print(f" {qnum}: empty status -> 'new'")
if APPLY:
req("PATCH", f"/api/collections/quotes/records/{qid}", {"status":"new"}, token=token)
fixed += 1
elif st == "claimed" and not cb:
print(f" {qnum}: claimed w/o claimer -> 'new'")
if APPLY:
req("PATCH", f"/api/collections/quotes/records/{qid}", {"status":"new","claimed_by":""}, token=token)
fixed += 1
# 6. Clear {0,0} coordinates on companies (null-island)
print("\n=== Clearing {0,0} coordinates ===")
cleared = 0
for c in companies:
co = c.get("coordinates")
if isinstance(co, dict) and co.get("lon")==0 and co.get("lat")==0:
if APPLY:
req("PATCH", f"/api/collections/companies/records/{c['id']}", {"coordinates":None}, token=token)
print(f" {c.get('company','?')}: cleared {{0,0}}")
cleared += 1
print(f"\n=== Summary ({'APPLIED' if APPLY else 'DRY-RUN'}) ===")
print(f" deleted: {deleted}")
print(f" quotes fixed: {fixed}")
print(f" coordinates cleared: {cleared}")
if not APPLY:
print("\n Re-run with --apply to make changes.")