| import csv |
| import os |
| import gradio as gr |
| import pandas as pd |
| import plotly.express as px |
|
|
| CSV_PATH = "zones.csv" |
|
|
| def load_zones(csv_path: str = CSV_PATH): |
| if not os.path.exists(csv_path): |
| raise FileNotFoundError( |
| f"Can't find {csv_path}. Add zones.csv in the Space Files tab (same folder as app.py)." |
| ) |
|
|
| zones = [] |
| with open(csv_path, "r", encoding="utf-8", newline="") as f: |
| reader = csv.DictReader(f) |
| required = { |
| "zone_id", |
| "zone_name", |
| "latitude", |
| "longitude", |
| "venue_density", |
| "foot_traffic_index", |
| "nightlife_score", |
| "daytime_score", |
| "primary_vibe", |
| "notes", |
| } |
| missing = required - set(reader.fieldnames or []) |
| if missing: |
| raise ValueError("zones.csv is missing columns: " + ", ".join(sorted(missing))) |
|
|
| def to_float(val, default=0.0): |
| try: |
| return float(val) |
| except (TypeError, ValueError): |
| return default |
|
|
| for row in reader: |
| zones.append( |
| { |
| "zone_id": (row.get("zone_id") or "").strip(), |
| "zone_name": (row.get("zone_name") or "").strip(), |
| "lat": to_float(row.get("latitude")), |
| "lon": to_float(row.get("longitude")), |
| "venue_density": to_float(row.get("venue_density")), |
| "foot_traffic": to_float(row.get("foot_traffic_index")), |
| "nightlife": to_float(row.get("nightlife_score")), |
| "daytime": to_float(row.get("daytime_score")), |
| "vibe": (row.get("primary_vibe") or "").strip(), |
| "notes": (row.get("notes") or "").strip(), |
| } |
| ) |
| return zones |
|
|
| def score_zone(zone, time_of_day: str) -> float: |
| ft = zone["foot_traffic"] |
| vd = zone["venue_density"] |
| nl = zone["nightlife"] |
| dt = zone["daytime"] |
|
|
| if time_of_day == "Tonight": |
| time_factor = nl |
| elif time_of_day == "Weekend": |
| time_factor = 0.6 * nl + 0.4 * dt |
| else: |
| time_factor = 0.5 * nl + 0.5 * dt |
|
|
| return (0.55 * ft) + (0.30 * time_factor) + (0.15 * vd) |
|
|
| def build_map(df: pd.DataFrame, time_of_day: str): |
| if df.empty: |
| return None |
|
|
| |
| center_lat = df["lat"].mean() |
| center_lon = df["lon"].mean() |
|
|
| fig = px.scatter_mapbox( |
| df, |
| lat="lat", |
| lon="lon", |
| hover_name="zone_name", |
| hover_data={ |
| "score": ":.2f", |
| "vibe": True, |
| "lat": False, |
| "lon": False, |
| }, |
| size="score", |
| size_max=18, |
| zoom=10, |
| center={"lat": center_lat, "lon": center_lon}, |
| title=f"Hot Zones Map ({time_of_day})", |
| ) |
| fig.update_layout( |
| mapbox_style="open-street-map", |
| margin={"l": 0, "r": 0, "t": 40, "b": 0}, |
| height=520, |
| ) |
| return fig |
|
|
| def find_hot_places(time_of_day: str, top_k: int): |
| try: |
| zones = load_zones(CSV_PATH) |
| except Exception as e: |
| return f"⚠️ {e}", None |
|
|
| if not zones: |
| return "No zones found in zones.csv.", None |
|
|
| ranked = [] |
| for z in zones: |
| s = score_zone(z, time_of_day) |
| ranked.append((s, z)) |
|
|
| ranked.sort(key=lambda x: x[0], reverse=True) |
| ranked = ranked[: max(1, int(top_k))] |
|
|
| rows = [] |
| lines = [] |
| for i, (s, z) in enumerate(ranked, start=1): |
| rows.append( |
| { |
| "rank": i, |
| "zone_name": z["zone_name"], |
| "lat": z["lat"], |
| "lon": z["lon"], |
| "score": s, |
| "vibe": z["vibe"], |
| "notes": z["notes"], |
| } |
| ) |
|
|
| lines.append( |
| f"{i}. 📍 {z['zone_name']} (score: {s:.2f})\n" |
| f"Vibe: {z['vibe']}\n" |
| f"Why: {z['notes']}" |
| ) |
|
|
| df = pd.DataFrame(rows) |
| fig = build_map(df, time_of_day) |
| return "\n\n---\n\n".join(lines), fig |
|
|
| with gr.Blocks() as demo: |
| gr.Markdown( |
| "# 🔥 Sydney Hot Places\n" |
| "Zone-level hotspot ranking using **mobility-style aggregated signals** (privacy-safe proxies).\n\n" |
| "**Setup:** add a `zones.csv` file in the Space Files tab (same folder as `app.py`)." |
| ) |
|
|
| with gr.Row(): |
| time_input = gr.Dropdown( |
| choices=["Now", "Tonight", "Weekend"], |
| value="Now", |
| label="When are you going out?", |
| ) |
| top_k = gr.Slider( |
| minimum=1, |
| maximum=12, |
| value=6, |
| step=1, |
| label="How many zones to show?", |
| ) |
|
|
| btn = gr.Button("Find hot places") |
|
|
| |
| with gr.Row(): |
| output_text = gr.Textbox(label="Ranked Zones", lines=16) |
| output_map = gr.Plot(label="Map") |
|
|
| btn.click(fn=find_hot_places, inputs=[time_input, top_k], outputs=[output_text, output_map]) |
|
|
| demo.launch() |