Files
canteen-asset-tracker/scripts/migrate_asset_names.py
T
shawn f1eb453a19 fix: keep GUEST/CAST visible in asset names
- clean_customer_name() no longer strips GUEST/CAST suffixes
- Re-ran migration: 967 asset names updated with GUEST/CAST preserved
- Format: '95301 - Animal Kingdom CAST / address / building'
  vs previous: '95301 - Animal Kingdom / address / building'
2026-05-29 00:13:08 -04:00

315 lines
11 KiB
Python

#!/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()