""" Disney park classification for canteen assets. Detects which Disney park/area an asset belongs to based on: - Asset name pattern (D-{Park} ...) - Address keywords - Room/building name keywords - Customer name (already D-{Park} prefixed) """ import re from typing import Optional # ─── Disney Park definitions ───────────────────────────────────────────────── DISNEY_PARK_NAMES = { "magic-kingdom": "Magic Kingdom", "epcot": "Epcot", "hollywood-studios": "Hollywood Studios", "animal-kingdom": "Animal Kingdom", "disney-springs": "Disney Springs", "resort": "Resort", "office": "Office", "other": "Other", } # Park icons / emoji for UI DISNEY_PARK_ICONS = { "magic-kingdom": "🏰", "epcot": "🌍", "hollywood-studios": "🎬", "animal-kingdom": "🌿", "disney-springs": "🛍️", "resort": "🏨", "office": "🏢", "other": "📍", } DISNEY_PARK_COLORS = { "magic-kingdom": "#9b59b6", # purple "epcot": "#3498db", # blue "hollywood-studios": "#e74c3c", # red "animal-kingdom": "#2ecc71", # green "disney-springs": "#f39c12", # orange "resort": "#1abc9c", # teal "office": "#95a5a6", # gray "other": "#bdc3c7", # light gray } # ─── Resort name keywords ──────────────────────────────────────────────────── RESORT_KEYWORDS = [ "WILDERNESS LODGE", "Wilderness Lodge", "FORT WILDERNESS", "Fort Wilderness", "GRAND FLORIDIAN", "Grand Floridian", "Floridian", "BOARDWALK", "Boardwalk", "SARATOGA SPRINGS", "Saratoga Springs", "CARIBBEAN BEACH", "Caribbean Beach", "CORONADO SPRINGS", "Coronado Springs", "GRAN DESTINO", "Gran Destino", "PORT ORLEANS", "Port Orleans", "POLYNESIAN", "Polynesian", "CONTEMPORARY", "Contemporary", "ANIMAL KINGDOM LODGE", "Animal Kingdom Lodge", "ANIMAL KNGDM LODGE", "Animal Kngdm Lodge", "KIDANI", "Kidani", "BEACH CLUB", "Beach Club", "YACHT CLUB", "Yacht Club", "OLD KEY WEST", "Old Key West", "RIVIERA", "Riviera", "POP CENTURY", "Pop Century", "ART OF ANIMATION", "Art of Animation", "ALL STAR", "All Star", "SHADES OF GREEN", "Shades of Green", "BUENA VISTA PALACE", "Buena Vista Palace", "WYNDHAM", "Wyndham", "HILTON", "Hilton", "IHG HOTEL", "IHG Hotel", "DAYS INN", "Days Inn", "B RESORT", "B Resort", "DOUBLETREE", "DoubleTree", "HOLIDAY INN", "Holiday Inn", "BEST WESTERN", "Best Western", "MAGNOLIA", "Magnolia", # Saratoga Springs area ] # ─── Classification logic ──────────────────────────────────────────────────── def classify_asset(name: str, address: str = "", building_name: str = "", room: str = "", customer_name: str = "") -> Optional[str]: """ Classify a canteen asset into a Disney park/area. Returns a disney_park key or None if not Disney-related. """ text = f"{name} {address} {building_name} {room} {customer_name}" # Quick check — is this Disney at all? if not _is_disney_related(text): return None # 1. Direct name pattern: D-{Park} ... park = _match_name_prefix(name) if park: return park # 2. Address/building keywords park = _match_address_keywords(address, building_name, room) if park: return park # 3. Customer name pattern park = _match_customer_name(customer_name) if park: return park # 4. Resort keywords in any field if _is_resort(text): return "resort" # 5. Default: other Disney return "other" def _is_disney_related(text: str) -> bool: """Check if this asset is Disney-related at all.""" t = text.upper() keywords = [ "DISNEY", "WDW", "MAGIC KINGDOM", "EPCOT", "HOLLYWOOD STUDIOS", "ANIMAL KINGDOM", "DISNEY SPRINGS", "LAKE BUENA VISTA", "BAY LAKE", "SEVEN SEAS", "HOTEL PLAZA BLVD", "D-", "D_MAGIC", "D_EPCOT", "D_HOLLYWOOD", "D_ANIMAL", "FLORIDIAN", "WILDERNESS LODGE", "BOARDWALK", "GRAND FLORIDIAN", "SARATOGA SPRINGS", "BUENA VISTA", ] return any(kw in t for kw in keywords) def _match_name_prefix(name: str) -> Optional[str]: """Match D-{Park} pattern in asset name.""" n = name.upper() # D-Magic Kingdom if "D-MAGIC KINGDOM" in n or "D_MAGIC KINGDOM" in n: return "magic-kingdom" # D-Epcot if "D-EPCOT" in n or "D_EPCOT" in n: return "epcot" # D-Hollywood Studios if "D-HOLLYWOOD STUDIOS" in n or "D_HOLLYWOOD STUDIOS" in n: return "hollywood-studios" # D-Animal Kingdom or D-AK if "D-ANIMAL KINGDOM" in n or "D_ANIMAL KINGDOM" in n or " D-AK " in n: return "animal-kingdom" # D-Disney Springs if "D-DISNEY SPRINGS" in n or "D_DISNEY SPRINGS" in n: return "disney-springs" # Resort detection in name prefix if n.startswith("D-") or " - " in n: # Extract the location part after D- and before any type indicator for kw in RESORT_KEYWORDS: if kw.upper() in n: return "resort" return None def _match_address_keywords(address: str, building: str, room: str) -> Optional[str]: """Match address/building patterns to parks.""" addr = (address + " " + building + " " + room).upper() # Magic Kingdom if "SEVEN SEAS" in addr or "MAGIC KINGDOM" in addr or "BAY LAKE" in addr: return "magic-kingdom" # Epcot if "EPCOT CENTER" in addr or "EPCOT" in addr: return "epcot" # Hollywood Studios if "EAST STUDIO" in addr or "HOLLYWOOD STUDIOS" in addr: return "hollywood-studios" # Animal Kingdom if "RAINFOREST" in addr or "ANIMAL KINGDOM" in addr or " AK " in addr or addr.startswith("AK "): return "animal-kingdom" # Disney Springs / Hotel Plaza Blvd area if "DISNEY SPRINGS" in addr or "BUENA VISTA DR" in addr or "HOTEL PLAZA BLVD" in addr: return "disney-springs" # Floridian Way = Grand Floridian (resort) if "FLORIDIAN" in addr: return "resort" # Epcot Resorts Blvd = Boardwalk area if "EPCOT RESORTS" in addr: return "resort" return None def _match_customer_name(customer: str) -> Optional[str]: """Match customer name to park classification.""" c = customer.upper() if "D-MAGIC KINGDOM" in c: return "magic-kingdom" if "D-EPCOT" in c: return "epcot" if "D-HOLLYWOOD STUDIOS" in c: return "hollywood-studios" if "D-DISNEY SPRINGS" in c: return "disney-springs" if "D-DISNEY VENDING" in c or "D-DISNEY WORLD" in c: return "office" # D-{Resort} pattern if c.startswith("D-"): for kw in RESORT_KEYWORDS: if kw.upper() in c: return "resort" # If it starts with D- and we haven't matched yet, it's some Disney thing return "other" return None def _is_resort(text: str) -> bool: """Check if text mentions a known Disney resort.""" t = text.upper() for kw in RESORT_KEYWORDS: if kw.upper() in t: return True return False # ─── Batch classification ──────────────────────────────────────────────────── def classify_all_assets(db_path: str) -> dict: """ Run classification on all Disney-related assets in the DB. Returns a report of what was classified. """ import sqlite3 conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row # Get all assets that are Disney-related (or have no classification yet) rows = conn.execute(""" SELECT a.id, a.machine_id, a.name, a.address, a.building_name, a.room, a.disney_park, c.name as customer_name FROM assets a LEFT JOIN customers c ON a.customer_id = c.id WHERE a.disney_park IS NULL """).fetchall() results = {"classified": [], "unclassifiable": [], "total_checked": len(rows)} counts = {} for row in rows: park = classify_asset( name=row['name'] or '', address=row['address'] or '', building_name=row['building_name'] or '', room=row['room'] or '', customer_name=row['customer_name'] or '', ) if park: conn.execute( "UPDATE assets SET disney_park = ?, is_disney = 1, updated_at = datetime('now') WHERE id = ?", (park, row['id']) ) results["classified"].append({ "id": row['id'], "machine_id": row['machine_id'], "name": row['name'], "park": park, }) counts[park] = counts.get(park, 0) + 1 else: results["unclassifiable"].append({ "id": row['id'], "machine_id": row['machine_id'], "name": row['name'], }) conn.commit() conn.close() results["counts"] = counts results["total_classified"] = len(results["classified"]) return results def get_classification_stats(db_path: str) -> dict: """Get statistics about current Disney classifications.""" import sqlite3 conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row rows = conn.execute(""" SELECT disney_park, COUNT(*) as count FROM assets WHERE disney_park IS NOT NULL GROUP BY disney_park ORDER BY count DESC """).fetchall() total = conn.execute("SELECT COUNT(*) as c FROM assets").fetchone()['c'] disney = conn.execute( "SELECT COUNT(*) as c FROM assets WHERE disney_park IS NOT NULL" ).fetchone()['c'] unclassified = conn.execute( "SELECT COUNT(*) as c FROM assets WHERE name LIKE '%Disney%' OR name LIKE '%D-%' AND disney_park IS NULL" ).fetchone()['c'] conn.close() return { "total_assets": total, "classified": disney, "unclassified_disney": 0, # Will be accurate after classify run "by_park": {row['disney_park']: row['count'] for row in rows}, }