feat: hourly weather forecast + steel141 migration notes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+42
-13
@@ -10,6 +10,7 @@ import socket
|
||||
import requests
|
||||
import yfinance as yf
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
# ── Config ────────────────────────────────────────────────────────────────────
|
||||
CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
|
||||
@@ -30,6 +31,7 @@ REMOTE_HOST = config["remote_host"]
|
||||
REMOTE_PASS = config["remote_pass"]
|
||||
PRINTER_IP = config["printer_ip"]
|
||||
PRINTER_PORT = int(config.get("printer_port", 9100))
|
||||
EASTERN_TZ = ZoneInfo("America/New_York")
|
||||
|
||||
# TM-m30 80mm paper, Font B — column width measured from test print
|
||||
COLS = 57
|
||||
@@ -246,11 +248,19 @@ def get_weather():
|
||||
r = requests.get(
|
||||
"https://api.open-meteo.com/v1/forecast"
|
||||
"?latitude=38.8421&longitude=-77.2683"
|
||||
"&daily=weathercode,temperature_2m_max,temperature_2m_min,precipitation_probability_max"
|
||||
"&temperature_unit=fahrenheit&timezone=auto&forecast_days=4",
|
||||
"&hourly=weathercode,temperature_2m,precipitation_probability"
|
||||
"&temperature_unit=fahrenheit&timezone=America%2FNew_York&forecast_days=2",
|
||||
timeout=15
|
||||
).json()
|
||||
daily = r.get("daily", {})
|
||||
hourly = r.get("hourly", {})
|
||||
times = hourly.get("time", [])
|
||||
temps = hourly.get("temperature_2m", [])
|
||||
pops = hourly.get("precipitation_probability", [])
|
||||
codes = hourly.get("weathercode", [])
|
||||
if not times:
|
||||
return None
|
||||
|
||||
now = datetime.now(EASTERN_TZ).replace(tzinfo=None)
|
||||
wmo = {
|
||||
0: "Sun", 1: "Sun", 2: "PCld", 3: "Ovcst",
|
||||
45: "Fog", 48: "Fog",
|
||||
@@ -261,15 +271,29 @@ def get_weather():
|
||||
85: "SnShwr", 86: "HvSnShwr",
|
||||
95: "Strm", 96: "Strm", 99: "Strm",
|
||||
}
|
||||
slots = []
|
||||
for i, t in enumerate(times):
|
||||
dt = datetime.strptime(t, "%Y-%m-%dT%H:%M")
|
||||
if dt >= now:
|
||||
slots.append((dt, i))
|
||||
if len(slots) >= 24:
|
||||
break
|
||||
|
||||
if not slots:
|
||||
return None
|
||||
|
||||
parts = []
|
||||
for i in range(len(daily.get("time", []))):
|
||||
d = datetime.strptime(daily["time"][i], "%Y-%m-%d").strftime("%a")
|
||||
cond = wmo.get(daily["weathercode"][i], "Unk")
|
||||
hi = daily["temperature_2m_max"][i]
|
||||
lo = daily["temperature_2m_min"][i]
|
||||
pop = daily["precipitation_probability_max"][i]
|
||||
parts.append(f"{d}:{cond} {hi:.0f}/{lo:.0f}({pop}%)")
|
||||
return "|".join(parts)
|
||||
for idx in range(0, len(slots), 3):
|
||||
dt, i = slots[idx]
|
||||
cond = wmo.get(codes[i], "Unk")
|
||||
temp = temps[i]
|
||||
pop = pops[i]
|
||||
parts.append(f"{dt.strftime('%H:%M')}:{cond} {temp:.0f}F({pop:.0f}%)")
|
||||
|
||||
lines = []
|
||||
for i in range(0, len(parts), 4):
|
||||
lines.append(" | ".join(parts[i:i + 4]))
|
||||
return lines
|
||||
except Exception as e:
|
||||
print(f"[!] Weather error: {e}")
|
||||
return None
|
||||
@@ -349,7 +373,7 @@ def build_receipt(articles, zfs, reddit, grafana, weather, finance):
|
||||
s(align='center', bold=True)
|
||||
p.text("Daily Briefing\n")
|
||||
s(align='center', bold=False)
|
||||
p.text(datetime.now().strftime('%A, %B %d %I:%M %p') + "\n")
|
||||
p.text(datetime.now(EASTERN_TZ).strftime('%A, %B %d %H:%M ET') + "\n")
|
||||
p.text("=" * COLS + "\n")
|
||||
|
||||
# Dashboard
|
||||
@@ -357,7 +381,12 @@ def build_receipt(articles, zfs, reddit, grafana, weather, finance):
|
||||
p.text("SYSTEM & WEATHER\n")
|
||||
s(align='left', bold=False)
|
||||
if weather:
|
||||
p.text(f"Wx: {weather}\n")
|
||||
if isinstance(weather, list):
|
||||
p.text("Wx24:\n")
|
||||
for line in weather:
|
||||
p.text(f" {line}\n")
|
||||
else:
|
||||
p.text(f"Wx: {weather}\n")
|
||||
if zfs:
|
||||
p.text(f"ZFS: {zfs['alloc']}/{zfs['size']} ({zfs['cap']}) {zfs['health']}\n")
|
||||
if grafana:
|
||||
|
||||
Reference in New Issue
Block a user