#!/usr/bin/env python3 """ Migrate asset names to readable format and populate location fields. Name format: {machine_id} - {customer} / {address} / {building_name}, Floor {floor}, Room {room} Also populates missing location fields (building_name, floor, etc.) from seed_location, customer_name, and address data. Usage: python3 scripts/migrate_asset_names.py # dry-run python3 scripts/migrate_asset_names.py --apply # write to DB python3 scripts/migrate_asset_names.py --force # overwrite existing location fields too """ import re import sqlite3 import sys from pathlib import Path ASSET_DB = str(Path(__file__).resolve().parent.parent / "assets.db") # ─── Building name extraction patterns ────────────────────────────────────── # Disney hotel/resort name extraction from customer_name # "D-All Star Music GUEST" → "All Star Music" # "D-Port Orleans Rvsd GUEST" → "Port Orleans Riverside" DISNEY_RESORT_PATTERNS = [ # "D-All Star Music GUEST" -> "All Star Music" (re.compile(r'^D[- ](.*?)\s+(?:GUEST|CAST|GUEST ROOM|GST)$', re.I), lambda m: m.group(1).strip()), # "D-All Star Music GUEST - MOV10" -> "All Star Music" (re.compile(r'^D[- ](.*?)\s+GUEST\s*-\s*\w+$', re.I), lambda m: m.group(1).strip()), # "D-CORONADO SPRINGS GUEST" -> "Coronado Springs" (re.compile(r'^D[- ](.*?)\s+(?:GUEST|CAST|ROOM|GUEST ROOM)$', re.I), lambda m: m.group(1).strip().title()), # Universal Studios / Valencia / etc. (re.compile(r'^Universal Studios (.+)$', re.I), lambda m: f'Universal Studios {m.group(1).strip()}'), ] # Manual overrides BUILDING_NAME_MAP = { "D-Port Orleans Rvsd GUEST": "Port Orleans Riverside", "D-Port Orleans Frrnc GUEST": "Port Orleans French Quarter", "D-CORONADO SPRINGS GUEST": "Coronado Springs", "D-All Star Music GUEST": "All Star Music", "D-All Star Movie Guest": "All Star Movies", "D-All Star Sports Guest": "All Star Sports", "D-ART OF ANIMATION Guest": "Art of Animation", "D-POP CENTURY GUEST": "Pop Century", "D-CARIBBEAN BEACH GUEST": "Caribbean Beach", "D-Saratoga Springs GUEST": "Saratoga Springs", "D-POLYNESIAN RESORT GUEST": "Polynesian Village Resort", "D-Animal Kngdm LodgeGuest": "Animal Kingdom Lodge", "D-Contemporary Guest": "Contemporary Resort", "D-Grand Floridian Guest": "Grand Floridian Resort", "D-WILDERNESS LODGE GUEST": "Wilderness Lodge", "D-Yacht & Beach Guest": "Yacht & Beach Club", "D-BoardWalk Guest": "BoardWalk Inn", "D-Riviera Resort Guest": "Riviera Resort", "D-Island Tower Polynesian": "Polynesian Village Resort - Island Tower", "D-DISNEY VENDING CAST": "Disney Vending", "D-Disney Springs CAST": "Disney Springs", "D-Magic Kingdom CAST": "Magic Kingdom", "D-Epcot CAST": "Epcot", "D-Hollywood Studios CAST": "Hollywood Studios", "D-Animal Kingdom CAST": "Animal Kingdom", "D-Celebration CAST": "Celebration", "D-WIDE WORLD SPORTS GUEST": "ESPN Wide World of Sports", "D-DISNEY WORLD SS CAST": "Disney World Shared Services", } def clean_customer_name(raw: str) -> str: """Clean a customer name for display — keeps GUEST/CAST suffix visible. D-All Star Music GUEST → All Star Music GUEST Home Depot #0232 Southlan → Home Depot #0232 Southlan """ if not raw: return "" name = raw # Remove D- prefix (Disney identifier) only if name.startswith("D-") or name.startswith("D "): name = name[2:] name = name.strip() return name def best_customer_for_name(asset: dict) -> str: """Get the best customer string for the asset name.""" c = asset.get("customer_name", "") or "" if c: return clean_customer_name(c) return "" def build_location_string(asset: dict) -> str: """Build the location/address part for the asset name.""" # Use address if available addr = (asset.get("address") or "").strip() # If no address, try seed_location minus customer prefix if not addr: seed = (asset.get("seed_location") or "").strip() if seed and " - " in seed: addr = seed.split(" - ", 1)[1].strip() elif seed: addr = seed return addr def build_building_details(asset: dict) -> str: """Build building details string (building_name, floor, etc.).""" parts = [] bldg = (asset.get("building_name") or "").strip() floor = (asset.get("floor") or "").strip() room = (asset.get("room") or "").strip() bldg_num = (asset.get("building_number") or "").strip() trailer = (asset.get("trailer_number") or "").strip() if bldg: parts.append(bldg) if bldg_num and (bldg not in bldg_num and bldg_num not in bldg): parts.append(f"Bldg {bldg_num}") if trailer: parts.append(f"Trailer {trailer}") if floor: parts.append(f"Floor {floor}") if room: parts.append(f"Room {room}") return ", ".join(parts) def build_new_name(asset: dict) -> str: """Build the new asset name.""" mid = (asset.get("machine_id") or "").strip() # Customer — cleaned customer = best_customer_for_name(asset) # Location (street address or seed_location minus customer prefix) location = build_location_string(asset) loc_clean = "" if location: loc_clean = location.split(",")[0].strip() # Building details building = build_building_details(asset) # Avoid dupes: if building starts with loc_clean, drop the building if building and location: bldg_lower = building.lower() loc_lower = loc_clean.lower() if bldg_lower.startswith(loc_lower) or loc_lower.startswith(bldg_lower): # Dupes — keep whichever is more descriptive if len(building) > len(loc_clean): loc_clean = "" else: building = "" # Assemble parts = [mid] if customer: parts.append(f" - {customer}") if location and loc_clean: parts.append(f" / {loc_clean}") if building: parts.append(f" / {building}") return "".join(parts) def infer_building_from_customer(customer_name: str) -> str: """Try to extract a building/resort name from the customer name.""" if not customer_name: return "" if customer_name in BUILDING_NAME_MAP: return BUILDING_NAME_MAP[customer_name] for pat, func in DISNEY_RESORT_PATTERNS: m = pat.search(customer_name) if m: return func(m) return "" def main(): apply_changes = "--apply" in sys.argv force = "--force" in sys.argv mode = "DRY RUN" if not apply_changes else "APPLYING" print(f"DB: {ASSET_DB}") print(f"Mode: {mode}") if force: print(" (--force: overwrite existing location fields)") print() conn = sqlite3.connect(ASSET_DB) conn.execute("PRAGMA journal_mode=WAL") # Load all assets cur = conn.execute(""" SELECT id, machine_id, name, customer_name, seed_location, address, building_name, floor, room, trailer_number, building_number, location_area, place FROM assets ORDER BY id """) rows = cur.fetchall() total = len(rows) print(f"Loaded {total} assets") cols = ["id", "machine_id", "name", "customer_name", "seed_location", "address", "building_name", "floor", "room", "trailer_number", "building_number", "location_area", "place"] stats = { "name_changed": 0, "building_name_filled": 0, "floor_filled": 0, "building_number_filled": 0, } name_updates = [] bldg_updates = [] for row in rows: asset = dict(zip(cols, row)) # ── 1. Infer building_name from customer_name if empty ── old_bldg = (asset["building_name"] or "").strip() new_bldg = old_bldg if not old_bldg or force: inferred = infer_building_from_customer(asset["customer_name"] or "") if inferred and inferred != old_bldg: new_bldg = inferred if not old_bldg: stats["building_name_filled"] += 1 if apply_changes: bldg_updates.append((new_bldg, "building_name", asset["id"])) if mode == "DRY RUN" and not old_bldg: print(f" [{asset['machine_id']}] building_name '{inferred}' ← from '{asset['customer_name']}'") # ── 2. Build new name ── old_name = (asset["name"] or "").strip() new_name = build_new_name(asset) if new_name != old_name: stats["name_changed"] += 1 name_updates.append((new_name, asset["id"])) if mode == "DRY RUN" and stats["name_changed"] <= 10: print(f" Name: {old_name[:60]}") print(f" → {new_name[:80]}") print() # ── Apply ── if apply_changes: if name_updates: conn.executemany( "UPDATE assets SET name=?, updated_at=datetime('now') WHERE id=?", name_updates, ) # Apply location updates for value, column, aid in bldg_updates: conn.execute(f"UPDATE assets SET {column}=?, updated_at=datetime('now') WHERE id=?", (value, aid)) conn.commit() # Verify cur = conn.execute("SELECT COUNT(*) FROM assets WHERE building_name != ''") bldg_count = cur.fetchone()[0] cur = conn.execute("SELECT COUNT(*) FROM assets WHERE floor != ''") floor_count = cur.fetchone()[0] print(f"\n ✅ Applied {len(name_updates)} name changes, {len(bldg_updates)} location updates") else: # Show some sample new names bldg_filled = stats["building_name_filled"] if name_updates: print(f"\n Sample new names:") conn2 = sqlite3.connect(ASSET_DB) for mid in ["95339", "95417", "98271", "90009", "95301", "68601"]: cur2 = conn2.execute( "SELECT id, machine_id, name, customer_name, address, building_name, floor FROM assets WHERE machine_id=?", (mid,) ) for r2 in cur2.fetchall(): a = dict(zip(cols, r2)) print(f"\n {a['machine_id']}") print(f" Old: {a['name'][:70]}") print(f" New: {build_new_name(a)[:70]}") if a.get("customer_name"): print(f" Bldg inferred: '{infer_building_from_customer(a['customer_name'])}'") conn2.close() # Summary print(f"\n{'=' * 60}") print(f"SUMMARY") print(f"{'=' * 60}") print(f" Names changed: {stats['name_changed']}") print(f" Building names filled: {stats['building_name_filled']}") if apply_changes: cur = conn.execute("SELECT COUNT(*) FROM assets WHERE building_name != ''") print(f" Total with building_name: {cur.fetchone()[0]}") cur = conn.execute("SELECT COUNT(*) FROM assets WHERE floor != ''") print(f" Total with floor: {cur.fetchone()[0]}") cur = conn.execute("SELECT COUNT(*) FROM assets WHERE building_name != '' OR floor != '' OR building_number != '' OR room != ''") print(f" Total with ANY location data: {cur.fetchone()[0]}") conn.close() if __name__ == "__main__": main()