Spaces:
Sleeping
Sleeping
Feat: Attach weather (type, temp_c) at save using Open-Meteo for Laurier; cache for 10m; soften fallback message to non-error phrasing; update both RPC and fallback paths without UI jitter.
Browse files- streamlit_app.py +85 -2
streamlit_app.py
CHANGED
|
@@ -440,6 +440,60 @@ def main():
|
|
| 440 |
|
| 441 |
ensure_ai_detection_defaults()
|
| 442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
# Modern sign-out button
|
| 444 |
col1, col2, col3 = st.columns([1, 1, 1])
|
| 445 |
with col2:
|
|
@@ -700,6 +754,7 @@ def main():
|
|
| 700 |
st.markdown(ModernUIComponents.create_status_message("Saving item...", "loading"), unsafe_allow_html=True)
|
| 701 |
ts_iso = datetime.utcnow().isoformat()
|
| 702 |
ingest_id = deterministic_ingest_id(int(v["id"]), user_email, name_clean, int(quantity), ts_iso)
|
|
|
|
| 703 |
|
| 704 |
try:
|
| 705 |
ok, msg = try_rpc_ingest(
|
|
@@ -711,16 +766,35 @@ def main():
|
|
| 711 |
if ok:
|
| 712 |
save_status.empty()
|
| 713 |
st.markdown(ModernUIComponents.create_status_message("Item logged successfully!", "success"), unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 714 |
log_event("item_logged", user_email, {
|
| 715 |
"visit_id": v["id"],
|
| 716 |
"item_name": name_clean,
|
| 717 |
"quantity": quantity
|
| 718 |
})
|
| 719 |
else:
|
| 720 |
-
st.markdown(ModernUIComponents.create_status_message(
|
| 721 |
fallback_direct_insert(user_email, int(v["id"]), name_clean, int(quantity),
|
| 722 |
clean_text(category,80), clean_text(unit,40),
|
| 723 |
-
clean_text(barcode,64), ts_iso, ingest_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
save_status.empty()
|
| 725 |
st.markdown(ModernUIComponents.create_status_message("Item logged successfully (fallback method)!", "success"), unsafe_allow_html=True)
|
| 726 |
log_event("item_logged_fallback", user_email, {
|
|
@@ -733,6 +807,15 @@ def main():
|
|
| 733 |
fallback_direct_insert(user_email, int(v["id"]), name_clean, int(quantity),
|
| 734 |
clean_text(category,80), clean_text(unit,40),
|
| 735 |
clean_text(barcode,64), ts_iso, ingest_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 736 |
save_status.empty()
|
| 737 |
st.markdown(ModernUIComponents.create_status_message("Item logged successfully (fallback method)!", "success"), unsafe_allow_html=True)
|
| 738 |
log_event("item_logged_fallback", user_email, {
|
|
|
|
| 440 |
|
| 441 |
ensure_ai_detection_defaults()
|
| 442 |
|
| 443 |
+
# ---------------- Weather (Laurier Waterloo campus) ----------------
|
| 444 |
+
if "_weather_cache" not in st.session_state:
|
| 445 |
+
st.session_state["_weather_cache"] = {"at": None, "type": None, "temp_c": None}
|
| 446 |
+
|
| 447 |
+
def _map_weather_code(code: int) -> str:
|
| 448 |
+
try:
|
| 449 |
+
c = int(code)
|
| 450 |
+
except Exception:
|
| 451 |
+
return None
|
| 452 |
+
# Open-Meteo weather codes grouped into simple buckets
|
| 453 |
+
if c == 0:
|
| 454 |
+
return "clear"
|
| 455 |
+
if c in {1, 2, 3}:
|
| 456 |
+
return "cloudy"
|
| 457 |
+
if c in {45, 48}:
|
| 458 |
+
return "fog"
|
| 459 |
+
if c in {51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82}:
|
| 460 |
+
return "rain"
|
| 461 |
+
if c in {71, 73, 75, 77, 85, 86}:
|
| 462 |
+
return "snow"
|
| 463 |
+
if c in {95, 96, 99}:
|
| 464 |
+
return "thunderstorm"
|
| 465 |
+
return "unknown"
|
| 466 |
+
|
| 467 |
+
def fetch_weather_at_laurier() -> tuple[Optional[str], Optional[float]]:
|
| 468 |
+
"""Fetch current weather near Wilfrid Laurier University, Waterloo (lat 43.4753, lon -80.5273)."""
|
| 469 |
+
try:
|
| 470 |
+
lat, lon = 43.4753, -80.5273
|
| 471 |
+
url = (
|
| 472 |
+
f"https://api.open-meteo.com/v1/forecast" \
|
| 473 |
+
f"?latitude={lat}&longitude={lon}¤t=temperature_2m,weather_code&timezone=auto"
|
| 474 |
+
)
|
| 475 |
+
r = requests.get(url, timeout=6)
|
| 476 |
+
if r.status_code != 200:
|
| 477 |
+
return None, None
|
| 478 |
+
data = r.json() or {}
|
| 479 |
+
cur = data.get("current") or {}
|
| 480 |
+
temp_c = cur.get("temperature_2m")
|
| 481 |
+
code = cur.get("weather_code")
|
| 482 |
+
return _map_weather_code(code), float(temp_c) if temp_c is not None else None
|
| 483 |
+
except Exception:
|
| 484 |
+
return None, None
|
| 485 |
+
|
| 486 |
+
def get_cached_weather() -> tuple[Optional[str], Optional[float]]:
|
| 487 |
+
now = datetime.utcnow()
|
| 488 |
+
cache = st.session_state.get("_weather_cache") or {}
|
| 489 |
+
ts = cache.get("at")
|
| 490 |
+
# refresh every 10 minutes
|
| 491 |
+
if ts and isinstance(ts, datetime) and (now - ts).total_seconds() < 600:
|
| 492 |
+
return cache.get("type"), cache.get("temp_c")
|
| 493 |
+
wtype, temp_c = fetch_weather_at_laurier()
|
| 494 |
+
st.session_state["_weather_cache"] = {"at": now, "type": wtype, "temp_c": temp_c}
|
| 495 |
+
return wtype, temp_c
|
| 496 |
+
|
| 497 |
# Modern sign-out button
|
| 498 |
col1, col2, col3 = st.columns([1, 1, 1])
|
| 499 |
with col2:
|
|
|
|
| 754 |
st.markdown(ModernUIComponents.create_status_message("Saving item...", "loading"), unsafe_allow_html=True)
|
| 755 |
ts_iso = datetime.utcnow().isoformat()
|
| 756 |
ingest_id = deterministic_ingest_id(int(v["id"]), user_email, name_clean, int(quantity), ts_iso)
|
| 757 |
+
weather_type, temp_c = get_cached_weather()
|
| 758 |
|
| 759 |
try:
|
| 760 |
ok, msg = try_rpc_ingest(
|
|
|
|
| 766 |
if ok:
|
| 767 |
save_status.empty()
|
| 768 |
st.markdown(ModernUIComponents.create_status_message("Item logged successfully!", "success"), unsafe_allow_html=True)
|
| 769 |
+
# Best-effort: attach weather on the row created by RPC using ingest_id
|
| 770 |
+
try:
|
| 771 |
+
if weather_type is not None or temp_c is not None:
|
| 772 |
+
sb.table("visit_items_p").update({
|
| 773 |
+
"weather_type": weather_type,
|
| 774 |
+
"temp_c": temp_c
|
| 775 |
+
}).eq("ingest_id", ingest_id).execute()
|
| 776 |
+
except Exception:
|
| 777 |
+
pass
|
| 778 |
log_event("item_logged", user_email, {
|
| 779 |
"visit_id": v["id"],
|
| 780 |
"item_name": name_clean,
|
| 781 |
"quantity": quantity
|
| 782 |
})
|
| 783 |
else:
|
| 784 |
+
st.markdown(ModernUIComponents.create_status_message("Using reliable save path...", "info"), unsafe_allow_html=True)
|
| 785 |
fallback_direct_insert(user_email, int(v["id"]), name_clean, int(quantity),
|
| 786 |
clean_text(category,80), clean_text(unit,40),
|
| 787 |
+
clean_text(barcode,64), ts_iso, ingest_id,
|
| 788 |
+
)
|
| 789 |
+
# After fallback insert, attach weather fields as part of payload
|
| 790 |
+
try:
|
| 791 |
+
if weather_type is not None or temp_c is not None:
|
| 792 |
+
sb.table("visit_items_p").update({
|
| 793 |
+
"weather_type": weather_type,
|
| 794 |
+
"temp_c": temp_c
|
| 795 |
+
}).eq("ingest_id", ingest_id).execute()
|
| 796 |
+
except Exception:
|
| 797 |
+
pass
|
| 798 |
save_status.empty()
|
| 799 |
st.markdown(ModernUIComponents.create_status_message("Item logged successfully (fallback method)!", "success"), unsafe_allow_html=True)
|
| 800 |
log_event("item_logged_fallback", user_email, {
|
|
|
|
| 807 |
fallback_direct_insert(user_email, int(v["id"]), name_clean, int(quantity),
|
| 808 |
clean_text(category,80), clean_text(unit,40),
|
| 809 |
clean_text(barcode,64), ts_iso, ingest_id)
|
| 810 |
+
# Attach weather after direct insert
|
| 811 |
+
try:
|
| 812 |
+
if weather_type is not None or temp_c is not None:
|
| 813 |
+
sb.table("visit_items_p").update({
|
| 814 |
+
"weather_type": weather_type,
|
| 815 |
+
"temp_c": temp_c
|
| 816 |
+
}).eq("ingest_id", ingest_id).execute()
|
| 817 |
+
except Exception:
|
| 818 |
+
pass
|
| 819 |
save_status.empty()
|
| 820 |
st.markdown(ModernUIComponents.create_status_message("Item logged successfully (fallback method)!", "success"), unsafe_allow_html=True)
|
| 821 |
log_event("item_logged_fallback", user_email, {
|