Compare commits

...

5 Commits

Author SHA1 Message Date
2112af7794 change calculation to strictly days 2026-01-25 14:30:02 -05:00
b559d5660d beef up gunicorn 2026-01-24 19:23:26 -05:00
3e05f6ff74 adds httpx 2026-01-24 19:10:27 -05:00
52400f888e speed up 2026-01-24 19:09:37 -05:00
97ca92b945 adds flask caching 2026-01-24 19:04:09 -05:00
6 changed files with 53 additions and 47 deletions

4
.gitignore vendored
View File

@@ -2,3 +2,7 @@ utils/__pycache__/__init__.cpython-313.pyc
utils/__pycache__/geocode.cpython-313.pyc utils/__pycache__/geocode.cpython-313.pyc
utils/__pycache__/weather.cpython-313.pyc utils/__pycache__/weather.cpython-313.pyc
__pycache__/config.cpython-313.pyc __pycache__/config.cpython-313.pyc
__pycache__/config.cpython-312.pyc
utils/__pycache__/geocode.cpython-312.pyc
utils/__pycache__/weather.cpython-312.pyc
utils/__pycache__/__init__.cpython-312.pyc

View File

@@ -18,5 +18,6 @@ COPY static/ ./static
# Expose the Flask port # Expose the Flask port
EXPOSE 5000 EXPOSE 5000
# Run the app with Gunicorn # Run the app with Gunicorn, 4 workers, 2 threads each
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"] CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app", "-w", "4", "--threads", "2"]

23
app.py
View File

@@ -1,10 +1,25 @@
from flask import Flask, render_template, jsonify, request from flask import Flask, render_template, jsonify, request
from utils.weather import get_today_snowfall from utils.weather import get_today_snowfall_async
from utils.geocode import get_city_name from utils.geocode import get_city_name_async
from config import MAX_SNOW_INCHES from config import MAX_SNOW_INCHES
from flask_caching import Cache
import asyncio
app = Flask(__name__) app = Flask(__name__)
# Cache config: 10-minute default timeout
cache = Cache(app, config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 600})
@cache.memoize()
def get_snowfall_for_location(lat, lon):
# Run async functions in an event loop
return asyncio.run(fetch_snow_and_city(lat, lon))
async def fetch_snow_and_city(lat, lon):
inches, snowing, tz_name = await get_today_snowfall_async(lat, lon)
city_name = await get_city_name_async(lat, lon)
return inches, snowing, tz_name, city_name
@app.route("/") @app.route("/")
def index(): def index():
return render_template("index.html") return render_template("index.html")
@@ -16,8 +31,7 @@ def snowfall_api():
if lat is None or lon is None: if lat is None or lon is None:
return jsonify({"error": "Missing lat/lon"}), 400 return jsonify({"error": "Missing lat/lon"}), 400
inches, snowing, tz_name = get_today_snowfall(lat, lon) inches, snowing, tz_name, city_name = get_snowfall_for_location(lat, lon)
city_name = get_city_name(lat, lon)
from datetime import datetime from datetime import datetime
import pytz import pytz
@@ -34,6 +48,5 @@ def snowfall_api():
"updated": datetime.now().isoformat() "updated": datetime.now().isoformat()
}) })
if __name__ == "__main__": if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000) app.run(host="0.0.0.0", port=5000)

View File

@@ -3,3 +3,5 @@ requests
gunicorn gunicorn
pytz pytz
timezonefinder timezonefinder
Flask-Caching
httpx

View File

@@ -1,8 +1,8 @@
import requests import httpx
def get_city_name(lat, lon): async def get_city_name_async(lat, lon):
try: async with httpx.AsyncClient(timeout=5) as client:
r = requests.get( r = await client.get(
"https://nominatim.openstreetmap.org/reverse", "https://nominatim.openstreetmap.org/reverse",
params={"lat": lat, "lon": lon, "format": "json", "zoom": 10, "addressdetails": 1}, params={"lat": lat, "lon": lon, "format": "json", "zoom": 10, "addressdetails": 1},
headers={"User-Agent": "AreWeBuriedApp/1.0"} headers={"User-Agent": "AreWeBuriedApp/1.0"}
@@ -10,8 +10,4 @@ def get_city_name(lat, lon):
r.raise_for_status() r.raise_for_status()
data = r.json() data = r.json()
address = data.get("address", {}) address = data.get("address", {})
city = address.get("city") or address.get("town") or address.get("village") or address.get("county") or f"{lat:.2f},{lon:.2f}" return address.get("city") or address.get("town") or address.get("village") or address.get("county") or f"{lat:.2f},{lon:.2f}"
return city
except Exception as e:
print("Reverse geocoding error:", e)
return f"{lat:.2f},{lon:.2f}"

View File

@@ -1,43 +1,33 @@
import requests import httpx
from datetime import datetime
import pytz import pytz
from datetime import datetime
from timezonefinder import TimezoneFinder from timezonefinder import TimezoneFinder
from config import OPEN_METEO_URL from config import OPEN_METEO_URL
tf = TimezoneFinder() tf = TimezoneFinder()
def get_today_snowfall(lat, lon): async def get_today_snowfall_async(lat, lon):
try:
tz_name = tf.timezone_at(lat=lat, lng=lon) or "UTC" tz_name = tf.timezone_at(lat=lat, lng=lon) or "UTC"
params = { async with httpx.AsyncClient(timeout=5) as client:
"latitude": lat, params = {"latitude": lat, "longitude": lon, "daily": "snowfall_sum", "timezone": tz_name}
"longitude": lon, r = await client.get(OPEN_METEO_URL, params=params)
"hourly": "snowfall",
"timezone": tz_name
}
r = requests.get(OPEN_METEO_URL, params=params, timeout=5)
r.raise_for_status() r.raise_for_status()
data = r.json() data = r.json()
hourly = data.get("hourly", {}) daily = data.get("daily", {})
times = hourly.get("time", []) days = daily.get("time", [])
snow_values = hourly.get("snowfall", []) snow_values = daily.get("snowfall_sum", [])
today_str = datetime.now(pytz.timezone(tz_name)).date().isoformat() today_str = datetime.now(pytz.timezone(tz_name)).date().isoformat()
snowfall_cm = 0.0 snowfall_cm = 0.0
snowing_now = False snowing_now = False
for t, s in zip(times, snow_values): for d, s in zip(days, snow_values):
if t.startswith(today_str): if d.startswith(today_str):
snowfall_cm += s snowfall_cm += s
if s > 0: if s > 0:
snowing_now = True snowing_now = True
inches = round(snowfall_cm / 2.54, 1) inches = round(snowfall_cm / 2.54, 1)
return inches, snowing_now, tz_name return inches, snowing_now, tz_name
except Exception as e:
print("Error fetching snowfall:", e)
return 0.0, False, "UTC"