feat: tag Disney assets with park/resort codes and display names

- Script: scripts/tag_disney_properties.py (supports dry-run + --apply)
- Maps 112 Disney location IDs to park/resort/office/other codes
- Updated 994 assets: disney_park column + appended display name with emoji
- Added disney_park column to locations table, populated 112 rows
- Covers all 364 locations via case-insensitive substring matching
This commit is contained in:
2026-05-23 19:50:16 -04:00
parent a5708623cc
commit 76e28614ae
+336
View File
@@ -0,0 +1,336 @@
#!/usr/bin/env python3
"""Tag Disney assets with their park/resort code and update display names.
Step 1: Map each location name → disney_park code + display name
Step 2: Update assets (disney_park + append display name)
Step 3: Update locations (add disney_park column if missing, set values)
Usage:
python scripts/tag_disney_properties.py # dry-run
python scripts/tag_disney_properties.py --apply # actually update the DB
"""
import argparse
import sqlite3
import sys
import os
# ── Disney property mapping ──────────────────────────────────────────────────
# Each location name pattern is mapped to (disney_park_code, display_name, emoji)
# Patterns are checked as case-insensitive substring matches, ordered by priority.
PARK_MAPPING = [
# --- Resorts (specific before generic! order matters) ---
("D-All Star Movie", "resort", "🏨 All Star Movies"),
("D-All Star Music", "resort", "🏨 All Star Music"),
("D-All Star Sports", "resort", "🏨 All Star Sports"),
("D-ALL STAR", "resort", "🏨 All Star"),
("ART OF ANIMATION", "resort", "🏨 Art of Animation"),
("D-ART OF ANIMATION", "resort", "🏨 Art of Animation"),
("Animal Kngdm LODGE", "resort", "🏨 Animal Kingdom Lodge"),
("Animal Kngdm Lodge", "resort", "🏨 Animal Kingdom Lodge"),
("BAY LAKE TOWER", "resort", "🏨 Bay Lake Tower"),
("D-BAY LAKE TOWER", "resort", "🏨 Bay Lake Tower"),
("BLIZZARD BEACH", "resort", "🏨 Blizzard Beach"),
("D-BLIZZARD BEACH", "resort", "🏨 Blizzard Beach"),
("Boardwalk CAST", "resort", "🏨 Boardwalk"),
("Boardwalk GUEST", "resort", "🏨 Boardwalk"),
("D-Boardwalk", "resort", "🏨 Boardwalk"),
("CARIBBEAN BEACH", "resort", "🏨 Caribbean Beach"),
("D-CARIBBEAN BEACH", "resort", "🏨 Caribbean Beach"),
("D-Caribbean Beach", "resort", "🏨 Caribbean Beach"),
("CORONADO SPRINGS", "resort", "🏨 Coronado Springs"),
("D-CORONADO SPRINGS", "resort", "🏨 Coronado Springs"),
("ContemporaryHotel", "resort", "🏨 Contemporary"),
("D-ContemporaryHotel", "resort", "🏨 Contemporary"),
("FANTASIA GOLF", "resort", "🏨 Fantasia Golf"),
("D-FANTASIA GOLF", "resort", "🏨 Fantasia Golf"),
("FORT WILDERNESS", "resort", "🏨 Fort Wilderness"),
("D-FORT WILDERNESS", "resort", "🏨 Fort Wilderness"),
("GRAN DESTINO", "resort", "🏨 Gran Destino"),
("D-GRAN DESTINO", "resort", "🏨 Gran Destino"),
("D-Gran Destino", "resort", "🏨 Gran Destino"),
("GRAND FLORIDIAN", "resort", "🏨 Grand Floridian"),
("D-GRAND FLORIDIAN", "resort", "🏨 Grand Floridian"),
("D-GRAND FLORIDIAN", "resort", "🏨 Grand Floridian"),
("Island Tower Polynesian", "resort", "🏨 Island Tower Polynesian"),
("D-Island Tower", "resort", "🏨 Island Tower"),
("Kidani Village", "resort", "🏨 Kidani Village"),
("D-Kidani Village", "resort", "🏨 Kidani Village"),
("OLD KEY WEST", "resort", "🏨 Old Key West"),
("D-OLD KEY WEST", "resort", "🏨 Old Key West"),
("POLYNESIAN RESORT", "resort", "🏨 Polynesian Resort"),
("D-POLYNESIAN RESORT", "resort", "🏨 Polynesian Resort"),
("POP CENTURY", "resort", "🏨 Pop Century"),
("D-POP CENTURY", "resort", "🏨 Pop Century"),
("PORT ORLEANS FrQt", "resort", "🏨 Port Orleans French Quarter"),
("D-Port Orleans FrQt", "resort", "🏨 Port Orleans French Quarter"),
("Port Orleans Rvsd", "resort", "🏨 Port Orleans Riverside"),
("D-Port Orleans Rvsd", "resort", "🏨 Port Orleans Riverside"),
("RIVIERA RESORT", "resort", "🏨 Riviera Resort"),
("D-RIVIERA RESORT", "resort", "🏨 Riviera Resort"),
("Saratoga Springs", "resort", "🏨 Saratoga Springs"),
("D-Saratoga Springs", "resort", "🏨 Saratoga Springs"),
("Treehouse Villas", "resort", "🏨 Treehouse Villas"),
("D-Treehouse Villas", "resort", "🏨 Treehouse Villas"),
("TYPHOON LAGOON CAST", "resort", "🏨 Typhoon Lagoon"),
("D-TYPHOON LAGOON", "resort", "🏨 Typhoon Lagoon"),
("WILDERNESS LODGE", "resort", "🏨 Wilderness Lodge"),
("D-WILDERNESS LODGE", "resort", "🏨 Wilderness Lodge"),
("Winter Summerland", "resort", "🏨 Winter Summerland"),
("D-Winter Summerland", "resort", "🏨 Winter Summerland"),
("YACHT AND BEACH", "resort", "🏨 Yacht and Beach"),
("D-YACHT AND BEACH", "resort", "🏨 Yacht and Beach"),
]
# ── Office mapping ────────────────────────────────────────────────────────────
OFFICE_MAPPING = [
("DRC Cast", "office", "🏢 DRC"),
]
# ── Other mapping ─────────────────────────────────────────────────────────────
OTHER_MAPPING = [
("DISNEY VENDING", "other", "📍 Disney Vending"),
("DISNEY WORLD SS", "other", "📍 Support Services"),
("D-ESPN", "other", "📍 ESPN"),
("ESPN", "other", "📍 ESPN"),
("WIDE WORLD SPORTS", "other", "📍 Wide World of Sports"),
("D-WIDE WORLD SPORTS", "other", "📍 Wide World of Sports"),
("Celebration", "other", "📍 Celebration"),
("D-Celebration", "other", "📍 Celebration"),
]
# ── Build the full mapping ────────────────────────────────────────────────────
# Preprocess: lowercase all patterns for case-insensitive matching
# Order matters: more specific patterns come first
def _normalise(name):
"""Strip D- prefix checks and normalise whitespace."""
return ' '.join(name.split()).upper()
def _build_location_mapping():
"""Build dict: location_name_upper -> (disney_park_code, display_name)."""
mapping = {}
# Park patterns - we'll handle these specially since we're doing substring matching
park_patterns = [
# (substring_check_fn, code, display_name)
]
# Resort patterns
resort_patterns = []
for substr, code, display in PARK_MAPPING:
if substr is None:
continue
resort_patterns.append((substr.upper(), code, display))
office_patterns = []
for substr, code, display in OFFICE_MAPPING:
office_patterns.append((substr.upper(), code, display))
other_patterns = []
for substr, code, display in OTHER_MAPPING:
other_patterns.append((substr.upper(), code, display))
# Park detection patterns (ordered by specificity)
# These need to be checked BEFORE the generic patterns
park_checks = [
# Check for specific parks first
(lambda n: "MAGIC KINGDOM" in n, "magic-kingdom", "🏰 Magic Kingdom"),
(lambda n: n.startswith("D-EPCOT"), "epcot", "🌍 Epcot"),
(lambda n: "EPCOT CAST" in n, "epcot", "🌍 Epcot"),
(lambda n: "EPCOT GUEST" in n, "epcot", "🌍 Epcot"),
(lambda n: "EPCOT" in n and n.startswith("D-"), "epcot", "🌍 Epcot"),
(lambda n: "HOLLYWOOD STUDIOS" in n, "hollywood-studios", "🎬 Hollywood Studios"),
(lambda n: "ANIMAL KINGDOM" in n and "LODGE" not in n and "CAST" in n, "animal-kingdom", "🌿 Animal Kingdom"),
(lambda n: "ANIMAL KNGDM" in n and "LODGE" not in n and "LODG" not in n, "animal-kingdom", "🌿 Animal Kingdom"),
(lambda n: "ANIMAL KINGDOM GUEST" in n, "animal-kingdom", "🌿 Animal Kingdom"),
(lambda n: "DISNEY SPRINGS" in n, "disney-springs", "🛍️ Disney Springs"),
]
def classify(name):
"""Return (code, display) for a Disney location name, or None."""
n = _normalise(name)
# 1) Check parks first (most specific)
for check_fn, code, display in park_checks:
if check_fn(n):
return code, display
# 2) Check office
for substr, code, display in office_patterns:
if substr in n:
return code, display
# 3) Check resorts
for substr, code, display in resort_patterns:
if substr in n:
return code, display
# 4) Check other
for substr, code, display in other_patterns:
if substr in n:
return code, display
return None
return classify
def get_db_path():
"""Find the assets.db path."""
# Try common locations
candidates = [
os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets.db"),
"/home/oplabs/projects/canteen-asset-tracker/assets.db",
]
for p in candidates:
if os.path.exists(p):
return p
raise FileNotFoundError("Could not find assets.db")
def main():
parser = argparse.ArgumentParser(description="Tag Disney assets with park/resort codes")
parser.add_argument("--apply", action="store_true", help="Actually update the database")
args = parser.parse_args()
classify = _build_location_mapping()
db_path = get_db_path()
print(f"Database: {db_path}")
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
cur = conn.cursor()
# ── Step 1: Load all Disney locations ────────────────────────────────────
cur.execute("SELECT id, name FROM locations ORDER BY name")
all_locations = [dict(r) for r in cur.fetchall()]
# Build: location_id -> (disney_park_code, display_name)
loc_park_map = {} # location_id -> (code, display_name)
loc_display_map = {} # location_id -> display_name (for appending)
unmapped_locations = []
for loc in all_locations:
name = loc["name"]
result = classify(name)
if result:
loc_park_map[loc["id"]] = result
else:
# Check if this is a Disney location (starts with D-, DISNEY, DRC, Disney Springs)
n_upper = _normalise(name)
if any(name.startswith(p) for p in ["D-", "DISNEY", "DRC", "Disney Springs"]):
n = _normalise(name)
if n.startswith("D-ANIMAL KINGDOM") and "LODGE" not in n:
loc_park_map[loc["id"]] = ("animal-kingdom", "🌿 Animal Kingdom")
elif n.startswith("D-ANIMAL KNGDM") and "LODGE" not in n:
loc_park_map[loc["id"]] = ("animal-kingdom", "🌿 Animal Kingdom")
else:
unmapped_locations.append(loc)
# Non-Disney locations are ignored
# ── Step 1b: Print unmapped Disney locations ─────────────────────────────
if unmapped_locations:
print(f"\n⚠️ {len(unmapped_locations)} Disney locations could not be mapped:")
for loc in unmapped_locations:
print(f" ID {loc['id']}: {loc['name']}")
# ── Step 2: Update assets ────────────────────────────────────────────────
cur.execute("SELECT id, name, location_id FROM assets WHERE is_disney=1")
disney_assets = [dict(r) for r in cur.fetchall()]
park_counts = {}
name_updates = []
name_skipped = []
for asset in disney_assets:
loc_id = asset["location_id"]
if loc_id not in loc_park_map:
continue
code, display = loc_park_map[loc_id]
park_counts[display] = park_counts.get(display, 0) + 1
# Check if the display name is already appended
current_name = asset["name"]
suffix = f" {display}"
if suffix not in current_name:
new_name = current_name + suffix
name_updates.append((code, new_name, asset["id"]))
else:
name_skipped.append((asset["id"], current_name))
# Still update the disney_park code even if name already has suffix
name_updates.append((code, current_name, asset["id"]))
# ── Step 3: Identify locations that need updating ────────────────────────
# Check if locations has the column
cur.execute("PRAGMA table_info(locations)")
loc_cols = [r["name"] for r in cur.fetchall()]
needs_add_column = "disney_park" not in loc_cols
# Build set of location IDs that are Disney
disney_loc_ids = set(loc_park_map.keys())
print(f"\n{'='*60}")
print(f"MODE: {'APPLY' if args.apply else 'DRY-RUN'} (pass --apply to execute)")
print(f"{'='*60}")
print(f"\n📊 Summary: {len(disney_assets)} Disney assets, {len(loc_park_map)} mapped locations")
print(f"\n📋 Assets per park/resort:")
for display, count in sorted(park_counts.items(), key=lambda x: -x[1]):
print(f" {display}: {count} assets")
print(f"\n📝 Name updates: {len(name_updates)}")
print(f"⏭️ Already tagged (skipped): {len(name_skipped)}")
# Show a sample
print(f"\n🔍 Sample updates (first 5):")
for i, (code, new_name, aid) in enumerate(name_updates[:5]):
old = next((a["name"] for a in disney_assets if a["id"] == aid), "")
print(f" Asset {aid}:")
print(f" Before: {old}")
print(f" After: {new_name}")
print(f" Park: {code}")
# ── Execute updates ──────────────────────────────────────────────────────
if args.apply:
print(f"\n{'='*60}")
print("APPLYING CHANGES...")
print(f"{'='*60}")
# Update assets
updated_assets = 0
for code, new_name, aid in name_updates:
cur.execute(
"UPDATE assets SET disney_park = ?, name = ? WHERE id = ?",
(code, new_name, aid),
)
updated_assets += 1
# Add column to locations if needed
if needs_add_column:
print(f"\n Adding disney_park column to locations table...")
cur.execute("ALTER TABLE locations ADD COLUMN disney_park TEXT DEFAULT NULL")
# Update locations
updated_locations = 0
for loc_id, (code, display) in loc_park_map.items():
cur.execute(
"UPDATE locations SET disney_park = ? WHERE id = ?",
(code, loc_id),
)
updated_locations += 1
conn.commit()
print(f"\n✅ Done! Updated {updated_assets} assets and {updated_locations} locations.")
else:
print(f"\n💡 Run with --apply to execute these changes.")
if needs_add_column:
print(" (Note: disney_park column will be added to locations table)")
conn.close()
if __name__ == "__main__":
main()