feat: add disney_area column and resort area mapping
- Add DISNEY_AREA_MAP in parser.py mapping 40+ Disney properties to 8 resort areas - Add disney_area to parse_customer() output, resolver after disney_park - Update db_writer.py schema: ORDERED_COLUMNS, AUTO_COLUMNS, COLUMN_TYPES - Add /api/status endpoint query for by_disney_area distribution - Add area row to web dashboard UI - Auto-migration adds column to existing databases
This commit is contained in:
@@ -47,6 +47,7 @@ AUTO_FIELDS = {
|
|||||||
"location_area",
|
"location_area",
|
||||||
"dex_report_date",
|
"dex_report_date",
|
||||||
"disney_park",
|
"disney_park",
|
||||||
|
"disney_area",
|
||||||
"remote_pricing_status",
|
"remote_pricing_status",
|
||||||
"alerts",
|
"alerts",
|
||||||
"telemetry_provider",
|
"telemetry_provider",
|
||||||
@@ -89,6 +90,7 @@ ALL_COLUMNS = [
|
|||||||
"deployed", # preserve
|
"deployed", # preserve
|
||||||
"pulled_date", # preserve
|
"pulled_date", # preserve
|
||||||
"disney_park", # auto
|
"disney_park", # auto
|
||||||
|
"disney_area", # auto
|
||||||
"remote_pricing_status", # auto
|
"remote_pricing_status", # auto
|
||||||
"alerts", # auto
|
"alerts", # auto
|
||||||
"telemetry_provider", # auto
|
"telemetry_provider", # auto
|
||||||
@@ -130,6 +132,7 @@ COLUMN_TYPES = {
|
|||||||
"deployed": "INTEGER",
|
"deployed": "INTEGER",
|
||||||
"pulled_date": "TEXT",
|
"pulled_date": "TEXT",
|
||||||
"disney_park": "TEXT",
|
"disney_park": "TEXT",
|
||||||
|
"disney_area": "TEXT",
|
||||||
"remote_pricing_status": "TEXT",
|
"remote_pricing_status": "TEXT",
|
||||||
"alerts": "TEXT",
|
"alerts": "TEXT",
|
||||||
"telemetry_provider": "TEXT",
|
"telemetry_provider": "TEXT",
|
||||||
|
|||||||
@@ -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 Table (Section 5) ─────────────────────────────────────
|
||||||
|
|
||||||
MAKE_NORMALIZATION = {
|
MAKE_NORMALIZATION = {
|
||||||
@@ -1029,6 +1112,7 @@ def parse_customer(customer_val):
|
|||||||
result = {
|
result = {
|
||||||
"company": None,
|
"company": None,
|
||||||
"disney_park": None,
|
"disney_park": None,
|
||||||
|
"disney_area": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
if not raw:
|
if not raw:
|
||||||
@@ -1065,6 +1149,10 @@ def parse_customer(customer_val):
|
|||||||
else:
|
else:
|
||||||
result["company"] = raw
|
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
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -1489,6 +1577,7 @@ def parse_excel(filepath):
|
|||||||
# Customer
|
# Customer
|
||||||
"company": customer_info["company"],
|
"company": customer_info["company"],
|
||||||
"disney_park": customer_info["disney_park"],
|
"disney_park": customer_info["disney_park"],
|
||||||
|
"disney_area": customer_info["disney_area"],
|
||||||
|
|
||||||
# Telemetry
|
# Telemetry
|
||||||
"telemetry_provider": device_info["telemetry_provider"],
|
"telemetry_provider": device_info["telemetry_provider"],
|
||||||
|
|||||||
+4
-1
@@ -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")
|
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()}
|
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")
|
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()}
|
result["by_priority"] = {r["pri"]: r["c"] for r in cur.fetchall()}
|
||||||
|
|
||||||
@@ -300,7 +303,7 @@ async def upload(file: UploadFile = File(...)):
|
|||||||
preview_cols = [
|
preview_cols = [
|
||||||
"machine_id", "serial_number", "name", "class", "make", "model",
|
"machine_id", "serial_number", "name", "class", "make", "model",
|
||||||
"company", "place", "building_name", "floor", "zone",
|
"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",
|
"yearly_sales", "has_cashless", "alerts_summary",
|
||||||
]
|
]
|
||||||
preview = []
|
preview = []
|
||||||
|
|||||||
@@ -930,6 +930,7 @@
|
|||||||
<div><span style="color:var(--text2);">Top makes:</span> ${makes || '—'}</div>
|
<div><span style="color:var(--text2);">Top makes:</span> ${makes || '—'}</div>
|
||||||
<div><span style="color:var(--text2);">GPS:</span> <span class="badge-db ${gpsClass}">${gps}/${total} (${pct}%)</span></div>
|
<div><span style="color:var(--text2);">GPS:</span> <span class="badge-db ${gpsClass}">${gps}/${total} (${pct}%)</span></div>
|
||||||
<div><span style="color:var(--text2);">Priority:</span> ${Object.entries(d.by_priority || {}).map(([k,v]) => `${k}: ${v}`).join(', ')}</div>
|
<div><span style="color:var(--text2);">Priority:</span> ${Object.entries(d.by_priority || {}).map(([k,v]) => `${k}: ${v}`).join(', ')}</div>
|
||||||
|
<div><span style="color:var(--text2);">🏰 Area:</span> ${Object.entries(d.by_disney_area || {}).map(([k,v]) => `${k}: ${v}`).join(', ')}</div>
|
||||||
</div>
|
</div>
|
||||||
${d.last_modified ? `<div style="font-size:11px;color:var(--text3)">Last modified: ${d.last_modified.slice(0,19)}</div>` : ''}
|
${d.last_modified ? `<div style="font-size:11px;color:var(--text3)">Last modified: ${d.last_modified.slice(0,19)}</div>` : ''}
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user