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:
@@ -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()
|
||||
Reference in New Issue
Block a user