SydSpace / app.py
nmo-genio's picture
Update app.py
20cf0d0 verified
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: # Now
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 roughly around Sydney
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")
# On desktop this shows side-by-side; on phone it stacks nicely.
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()