diff --git a/db_writer.py b/db_writer.py index d3a3348..88fbace 100644 --- a/db_writer.py +++ b/db_writer.py @@ -47,6 +47,7 @@ AUTO_FIELDS = { "location_area", "dex_report_date", "disney_park", + "disney_area", "remote_pricing_status", "alerts", "telemetry_provider", @@ -89,6 +90,7 @@ ALL_COLUMNS = [ "deployed", # preserve "pulled_date", # preserve "disney_park", # auto + "disney_area", # auto "remote_pricing_status", # auto "alerts", # auto "telemetry_provider", # auto @@ -130,6 +132,7 @@ COLUMN_TYPES = { "deployed": "INTEGER", "pulled_date": "TEXT", "disney_park": "TEXT", + "disney_area": "TEXT", "remote_pricing_status": "TEXT", "alerts": "TEXT", "telemetry_provider": "TEXT", diff --git a/parser.py b/parser.py index 899b960..7923480 100644 --- a/parser.py +++ b/parser.py @@ -87,6 +87,89 @@ DISNEY_CUSTOMER_PREFIXES = [ ] +# ─── Disney Resort Area Mapping (Section 2) ──────────────────────────────────── +# +# Maps cleaned disney_park values to their Walt Disney World resort area. +# Based on official WDW geography: resorts grouped by proximity to parks +# and transportation access (monorail, Skyliner, boat, bus). +# +# Areas: +# Magic Kingdom Resort Area — Monorail loop & Seven Seas Lagoon +# Epcot Resort Area — Crescent Lake, Skyliner to Epcot/HS +# Animal Kingdom Resort Area — Near Animal Kingdom park +# Disney Springs Resort Area — Near shopping/dining district +# Wide World of Sports Area — All-Star/Pop Century corridor (SE) +# Parks & Attractions — Theme parks, ESPN, cast areas +# Water Parks — Blizzard Beach, Typhoon Lagoon, golf +# Admin & Support — Corporate/admin locations + +DISNEY_AREA_MAP = { + # ──── Magic Kingdom Resort Area ───────────────────────────────────────────── + # Hotels along the monorail loop and Seven Seas Lagoon / Bay Lake + "Disney's Contemporary Resort": "Magic Kingdom Resort Area", + "Disney's Polynesian Village Resort": "Magic Kingdom Resort Area", + "Disney's Grand Floridian Resort": "Magic Kingdom Resort Area", + "Bay Lake Tower at Contemporary": "Magic Kingdom Resort Area", + "Disney's Wilderness Lodge": "Magic Kingdom Resort Area", + "Fort Wilderness Resort & Campground": "Magic Kingdom Resort Area", + "D-Island Tower Polynesian": "Magic Kingdom Resort Area", + + # ──── Epcot Resort Area ───────────────────────────────────────────────────── + # Hotels along Crescent Lake — walk/Skyliner/boat to Epcot & Hollywood Studios + "Disney's BoardWalk Inn": "Epcot Resort Area", + "Disney's Yacht & Beach Club Resorts": "Epcot Resort Area", + "Disney's Riviera Resort": "Epcot Resort Area", + + # ──── Animal Kingdom Resort Area ──────────────────────────────────────────── + # Near Animal Kingdom park (Jambo House, Kidani Village) + "D-Animal Kngdm Lodge": "Animal Kingdom Resort Area", + "D-Kidani Village GUEST": "Animal Kingdom Resort Area", + "D-Coronado Springs": "Animal Kingdom Resort Area", + "D-Gran Destino": "Animal Kingdom Resort Area", # At Coronado Springs + + # ──── Disney Springs Resort Area ──────────────────────────────────────────── + # Near the shopping/dining district + "Saratoga Springs Resort & Spa": "Disney Springs Resort Area", + "Treehouse Villas (Saratoga Springs)": "Disney Springs Resort Area", + "Disney Springs": "Disney Springs Resort Area", + "Port Orleans Riverside": "Disney Springs Resort Area", + "Port Orleans French Quarter": "Disney Springs Resort Area", + "D-Caribbean Beach Guest": "Disney Springs Resort Area", + "D-CARIBBEAN BEACH CAST": "Disney Springs Resort Area", + "D-OLD KEY WEST GUEST": "Disney Springs Resort Area", + "D-OLD KEY WEST CAST": "Disney Springs Resort Area", + + # ──── Wide World of Sports Resort Area ────────────────────────────────────── + # All-Star Resorts & Pop Century / Art of Animation corridor (southeastern) + "Art of Animation Resort": "Wide World of Sports Resort Area", + "Disney's Pop Century Resort": "Wide World of Sports Resort Area", + "All-Star Sports Resort": "Wide World of Sports Resort Area", + "All-Star Music Resort": "Wide World of Sports Resort Area", + "D-All Star Movie": "Wide World of Sports Resort Area", + + # ──── Parks & Attractions ─────────────────────────────────────────────────── + # Theme parks, cast-member areas, sports complex + "D-Magic Kingdom": "Parks & Attractions", + "D-Hollywood Studios": "Parks & Attractions", + "D-Animal Kingdom": "Parks & Attractions", + "D-Epcot": "Parks & Attractions", + "ESPN Wide World of Sports": "Parks & Attractions", + "D-ESPN 2 CAST": "Parks & Attractions", + + # ──── Water Parks ─────────────────────────────────────────────────────────── + "Blizzard Beach Water Park": "Water Parks", + "D-TYPHOON LAGOON CAST": "Water Parks", + "Winter Summerland Mini Golf": "Water Parks", + "D-FANTASIA GOLF Guest": "Water Parks", + + # ──── Admin & Support ─────────────────────────────────────────────────────── + "Celebration (Admin)": "Admin & Support", + "Disney Regional Center": "Admin & Support", + "Disney World Support Services": "Admin & Support", + "Disney Support Services": "Admin & Support", +} + + # ─── Make Normalization Table (Section 5) ───────────────────────────────────── MAKE_NORMALIZATION = { @@ -1029,6 +1112,7 @@ def parse_customer(customer_val): result = { "company": None, "disney_park": None, + "disney_area": None, } if not raw: @@ -1065,6 +1149,10 @@ def parse_customer(customer_val): else: result["company"] = raw + # Resolve Disney resort area from disney_park + if result["disney_park"] and result["disney_park"] in DISNEY_AREA_MAP: + result["disney_area"] = DISNEY_AREA_MAP[result["disney_park"]] + return result @@ -1489,6 +1577,7 @@ def parse_excel(filepath): # Customer "company": customer_info["company"], "disney_park": customer_info["disney_park"], + "disney_area": customer_info["disney_area"], # Telemetry "telemetry_provider": device_info["telemetry_provider"], diff --git a/web/server.py b/web/server.py index 08be19d..f791e11 100644 --- a/web/server.py +++ b/web/server.py @@ -212,6 +212,9 @@ async def status(): cur.execute("SELECT COALESCE(NULLIF(make,''),'Unknown') as mk, COUNT(*) as c FROM assets GROUP BY mk ORDER BY c DESC") result["by_make"] = {r["mk"]: r["c"] for r in cur.fetchall()} + cur.execute("SELECT COALESCE(NULLIF(disney_area,''),'Non-Disney') as area, COUNT(*) as c FROM assets GROUP BY area ORDER BY c DESC") + result["by_disney_area"] = {r["area"]: r["c"] for r in cur.fetchall()} + cur.execute("SELECT COALESCE(NULLIF(priority,''),'None') as pri, COUNT(*) as c FROM assets GROUP BY pri ORDER BY pri") result["by_priority"] = {r["pri"]: r["c"] for r in cur.fetchall()} @@ -300,7 +303,7 @@ async def upload(file: UploadFile = File(...)): preview_cols = [ "machine_id", "serial_number", "name", "class", "make", "model", "company", "place", "building_name", "floor", "zone", - "priority", "telemetry_provider", "disney_park", "is_disney", + "priority", "telemetry_provider", "disney_park", "disney_area", "is_disney", "yearly_sales", "has_cashless", "alerts_summary", ] preview = [] diff --git a/web/static/index.html b/web/static/index.html index a9cb2cf..3c925ed 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -930,6 +930,7 @@