Update app.py
Browse files
app.py
CHANGED
|
@@ -1,18 +1,15 @@
|
|
| 1 |
import csv
|
| 2 |
import os
|
| 3 |
import gradio as gr
|
|
|
|
|
|
|
| 4 |
|
| 5 |
CSV_PATH = "zones.csv"
|
| 6 |
|
| 7 |
def load_zones(csv_path: str = CSV_PATH):
|
| 8 |
-
"""
|
| 9 |
-
Loads zones from a CSV with columns:
|
| 10 |
-
zone_id,zone_name,latitude,longitude,venue_density,foot_traffic_index,
|
| 11 |
-
nightlife_score,daytime_score,primary_vibe,notes
|
| 12 |
-
"""
|
| 13 |
if not os.path.exists(csv_path):
|
| 14 |
raise FileNotFoundError(
|
| 15 |
-
f"Can't find {csv_path}.
|
| 16 |
)
|
| 17 |
|
| 18 |
zones = []
|
|
@@ -32,28 +29,25 @@ def load_zones(csv_path: str = CSV_PATH):
|
|
| 32 |
}
|
| 33 |
missing = required - set(reader.fieldnames or [])
|
| 34 |
if missing:
|
| 35 |
-
raise ValueError(
|
| 36 |
-
"zones.csv is missing columns: " + ", ".join(sorted(missing))
|
| 37 |
-
)
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
except (TypeError, ValueError):
|
| 45 |
-
return default
|
| 46 |
|
|
|
|
| 47 |
zones.append(
|
| 48 |
{
|
| 49 |
-
"zone_id": row
|
| 50 |
-
"zone_name": row
|
| 51 |
-
"lat": to_float("latitude"),
|
| 52 |
-
"lon": to_float("longitude"),
|
| 53 |
-
"venue_density": to_float("venue_density"),
|
| 54 |
-
"foot_traffic": to_float("foot_traffic_index"),
|
| 55 |
-
"nightlife": to_float("nightlife_score"),
|
| 56 |
-
"daytime": to_float("daytime_score"),
|
| 57 |
"vibe": (row.get("primary_vibe") or "").strip(),
|
| 58 |
"notes": (row.get("notes") or "").strip(),
|
| 59 |
}
|
|
@@ -61,12 +55,6 @@ def load_zones(csv_path: str = CSV_PATH):
|
|
| 61 |
return zones
|
| 62 |
|
| 63 |
def score_zone(zone, time_of_day: str) -> float:
|
| 64 |
-
"""
|
| 65 |
-
Simple, explainable scoring:
|
| 66 |
-
- foot_traffic is the main "mobility" signal
|
| 67 |
-
- time_of_day picks nightlife/daytime emphasis
|
| 68 |
-
- venue_density boosts places that have lots of options nearby
|
| 69 |
-
"""
|
| 70 |
ft = zone["foot_traffic"]
|
| 71 |
vd = zone["venue_density"]
|
| 72 |
nl = zone["nightlife"]
|
|
@@ -75,34 +63,52 @@ def score_zone(zone, time_of_day: str) -> float:
|
|
| 75 |
if time_of_day == "Tonight":
|
| 76 |
time_factor = nl
|
| 77 |
elif time_of_day == "Weekend":
|
| 78 |
-
time_factor = 0.6 * nl + 0.4 * dt
|
| 79 |
-
else: #
|
| 80 |
time_factor = 0.5 * nl + 0.5 * dt
|
| 81 |
|
| 82 |
-
# Weighted blend (kept simple and stable)
|
| 83 |
return (0.55 * ft) + (0.30 * time_factor) + (0.15 * vd)
|
| 84 |
|
| 85 |
-
def
|
| 86 |
-
if
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
)
|
|
|
|
| 97 |
|
| 98 |
def find_hot_places(time_of_day: str, top_k: int):
|
| 99 |
try:
|
| 100 |
zones = load_zones(CSV_PATH)
|
| 101 |
except Exception as e:
|
| 102 |
-
return f"⚠️ {e}"
|
| 103 |
|
| 104 |
if not zones:
|
| 105 |
-
return "No zones found in zones.csv."
|
| 106 |
|
| 107 |
ranked = []
|
| 108 |
for z in zones:
|
|
@@ -112,16 +118,30 @@ def find_hot_places(time_of_day: str, top_k: int):
|
|
| 112 |
ranked.sort(key=lambda x: x[0], reverse=True)
|
| 113 |
ranked = ranked[: max(1, int(top_k))]
|
| 114 |
|
|
|
|
| 115 |
lines = []
|
| 116 |
for i, (s, z) in enumerate(ranked, start=1):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
lines.append(
|
| 118 |
f"{i}. 📍 {z['zone_name']} (score: {s:.2f})\n"
|
| 119 |
f"Vibe: {z['vibe']}\n"
|
| 120 |
-
f"{
|
| 121 |
-
f"Coords: {z['lat']:.4f}, {z['lon']:.4f}"
|
| 122 |
)
|
| 123 |
|
| 124 |
-
|
|
|
|
|
|
|
| 125 |
|
| 126 |
with gr.Blocks() as demo:
|
| 127 |
gr.Markdown(
|
|
@@ -134,16 +154,23 @@ with gr.Blocks() as demo:
|
|
| 134 |
time_input = gr.Dropdown(
|
| 135 |
choices=["Now", "Tonight", "Weekend"],
|
| 136 |
value="Now",
|
| 137 |
-
label="When are you going out?"
|
| 138 |
)
|
| 139 |
top_k = gr.Slider(
|
| 140 |
-
minimum=1,
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
| 142 |
)
|
| 143 |
|
| 144 |
-
output = gr.Textbox(label="Hot Zones", lines=16)
|
| 145 |
-
|
| 146 |
btn = gr.Button("Find hot places")
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
demo.launch()
|
|
|
|
| 1 |
import csv
|
| 2 |
import os
|
| 3 |
import gradio as gr
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import plotly.express as px
|
| 6 |
|
| 7 |
CSV_PATH = "zones.csv"
|
| 8 |
|
| 9 |
def load_zones(csv_path: str = CSV_PATH):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
if not os.path.exists(csv_path):
|
| 11 |
raise FileNotFoundError(
|
| 12 |
+
f"Can't find {csv_path}. Add zones.csv in the Space Files tab (same folder as app.py)."
|
| 13 |
)
|
| 14 |
|
| 15 |
zones = []
|
|
|
|
| 29 |
}
|
| 30 |
missing = required - set(reader.fieldnames or [])
|
| 31 |
if missing:
|
| 32 |
+
raise ValueError("zones.csv is missing columns: " + ", ".join(sorted(missing)))
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
def to_float(val, default=0.0):
|
| 35 |
+
try:
|
| 36 |
+
return float(val)
|
| 37 |
+
except (TypeError, ValueError):
|
| 38 |
+
return default
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
for row in reader:
|
| 41 |
zones.append(
|
| 42 |
{
|
| 43 |
+
"zone_id": (row.get("zone_id") or "").strip(),
|
| 44 |
+
"zone_name": (row.get("zone_name") or "").strip(),
|
| 45 |
+
"lat": to_float(row.get("latitude")),
|
| 46 |
+
"lon": to_float(row.get("longitude")),
|
| 47 |
+
"venue_density": to_float(row.get("venue_density")),
|
| 48 |
+
"foot_traffic": to_float(row.get("foot_traffic_index")),
|
| 49 |
+
"nightlife": to_float(row.get("nightlife_score")),
|
| 50 |
+
"daytime": to_float(row.get("daytime_score")),
|
| 51 |
"vibe": (row.get("primary_vibe") or "").strip(),
|
| 52 |
"notes": (row.get("notes") or "").strip(),
|
| 53 |
}
|
|
|
|
| 55 |
return zones
|
| 56 |
|
| 57 |
def score_zone(zone, time_of_day: str) -> float:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
ft = zone["foot_traffic"]
|
| 59 |
vd = zone["venue_density"]
|
| 60 |
nl = zone["nightlife"]
|
|
|
|
| 63 |
if time_of_day == "Tonight":
|
| 64 |
time_factor = nl
|
| 65 |
elif time_of_day == "Weekend":
|
| 66 |
+
time_factor = 0.6 * nl + 0.4 * dt
|
| 67 |
+
else: # Now
|
| 68 |
time_factor = 0.5 * nl + 0.5 * dt
|
| 69 |
|
|
|
|
| 70 |
return (0.55 * ft) + (0.30 * time_factor) + (0.15 * vd)
|
| 71 |
|
| 72 |
+
def build_map(df: pd.DataFrame, time_of_day: str):
|
| 73 |
+
if df.empty:
|
| 74 |
+
return None
|
| 75 |
+
|
| 76 |
+
# Center roughly around Sydney
|
| 77 |
+
center_lat = df["lat"].mean()
|
| 78 |
+
center_lon = df["lon"].mean()
|
| 79 |
+
|
| 80 |
+
fig = px.scatter_mapbox(
|
| 81 |
+
df,
|
| 82 |
+
lat="lat",
|
| 83 |
+
lon="lon",
|
| 84 |
+
hover_name="zone_name",
|
| 85 |
+
hover_data={
|
| 86 |
+
"score": ":.2f",
|
| 87 |
+
"vibe": True,
|
| 88 |
+
"lat": False,
|
| 89 |
+
"lon": False,
|
| 90 |
+
},
|
| 91 |
+
size="score",
|
| 92 |
+
size_max=18,
|
| 93 |
+
zoom=10,
|
| 94 |
+
center={"lat": center_lat, "lon": center_lon},
|
| 95 |
+
title=f"Hot Zones Map ({time_of_day})",
|
| 96 |
+
)
|
| 97 |
+
fig.update_layout(
|
| 98 |
+
mapbox_style="open-street-map",
|
| 99 |
+
margin={"l": 0, "r": 0, "t": 40, "b": 0},
|
| 100 |
+
height=520,
|
| 101 |
)
|
| 102 |
+
return fig
|
| 103 |
|
| 104 |
def find_hot_places(time_of_day: str, top_k: int):
|
| 105 |
try:
|
| 106 |
zones = load_zones(CSV_PATH)
|
| 107 |
except Exception as e:
|
| 108 |
+
return f"⚠️ {e}", None
|
| 109 |
|
| 110 |
if not zones:
|
| 111 |
+
return "No zones found in zones.csv.", None
|
| 112 |
|
| 113 |
ranked = []
|
| 114 |
for z in zones:
|
|
|
|
| 118 |
ranked.sort(key=lambda x: x[0], reverse=True)
|
| 119 |
ranked = ranked[: max(1, int(top_k))]
|
| 120 |
|
| 121 |
+
rows = []
|
| 122 |
lines = []
|
| 123 |
for i, (s, z) in enumerate(ranked, start=1):
|
| 124 |
+
rows.append(
|
| 125 |
+
{
|
| 126 |
+
"rank": i,
|
| 127 |
+
"zone_name": z["zone_name"],
|
| 128 |
+
"lat": z["lat"],
|
| 129 |
+
"lon": z["lon"],
|
| 130 |
+
"score": s,
|
| 131 |
+
"vibe": z["vibe"],
|
| 132 |
+
"notes": z["notes"],
|
| 133 |
+
}
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
lines.append(
|
| 137 |
f"{i}. 📍 {z['zone_name']} (score: {s:.2f})\n"
|
| 138 |
f"Vibe: {z['vibe']}\n"
|
| 139 |
+
f"Why: {z['notes']}"
|
|
|
|
| 140 |
)
|
| 141 |
|
| 142 |
+
df = pd.DataFrame(rows)
|
| 143 |
+
fig = build_map(df, time_of_day)
|
| 144 |
+
return "\n\n---\n\n".join(lines), fig
|
| 145 |
|
| 146 |
with gr.Blocks() as demo:
|
| 147 |
gr.Markdown(
|
|
|
|
| 154 |
time_input = gr.Dropdown(
|
| 155 |
choices=["Now", "Tonight", "Weekend"],
|
| 156 |
value="Now",
|
| 157 |
+
label="When are you going out?",
|
| 158 |
)
|
| 159 |
top_k = gr.Slider(
|
| 160 |
+
minimum=1,
|
| 161 |
+
maximum=12,
|
| 162 |
+
value=6,
|
| 163 |
+
step=1,
|
| 164 |
+
label="How many zones to show?",
|
| 165 |
)
|
| 166 |
|
|
|
|
|
|
|
| 167 |
btn = gr.Button("Find hot places")
|
| 168 |
+
|
| 169 |
+
# On desktop this shows side-by-side; on phone it stacks nicely.
|
| 170 |
+
with gr.Row():
|
| 171 |
+
output_text = gr.Textbox(label="Ranked Zones", lines=16)
|
| 172 |
+
output_map = gr.Plot(label="Map")
|
| 173 |
+
|
| 174 |
+
btn.click(fn=find_hot_places, inputs=[time_input, top_k], outputs=[output_text, output_map])
|
| 175 |
|
| 176 |
demo.launch()
|