f1eb453a19
- 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'
315 lines
11 KiB
Python
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()
|