gluco-api / core /tools /build_glucose_plot.py
JulianTekles's picture
Update core/tools/build_glucose_plot.py
3b0cfc0 verified
# core/tools/build_glucose_plot.py
from __future__ import annotations
import io
from datetime import datetime, timedelta
from typing import List, Dict, Any, Tuple
import matplotlib
matplotlib.use("Agg") # Headless für HF
import matplotlib.pyplot as plt
from core.tools.nightscout import get_sgv_entries
def _parse_entries(entries: List[Dict[str, Any]]) -> Tuple[List[datetime], List[int]]:
times: List[datetime] = []
values: List[int] = []
for e in entries or []:
sgv = e.get("sgv")
ts = e.get("date") # ms epoch
if sgv is None or ts is None:
continue
try:
times.append(datetime.utcfromtimestamp(float(ts) / 1000.0))
values.append(int(float(sgv)))
except Exception:
continue
return times, values
def build_glucose_plot_png(hours: int = 3) -> bytes:
"""
PNG Glukoseverlauf der letzten `hours`.
Zielbereich: 80–180 (grün)
Zonen:
< 80 rot
80–95 gelb
95–180 grün
180–250 gelb
> 250 rot
"""
# ---- Nightscout Daten ----
entries: List[Dict[str, Any]] = get_sgv_entries(hours=hours) # <<< WICHTIG: hours=..., nicht since_hours
if not entries:
raise RuntimeError("No Nightscout data available")
times, values = _parse_entries(entries)
if not times:
raise RuntimeError("No valid SGV points")
# ---- Plot ----
fig, ax = plt.subplots(figsize=(9, 4.8))
# Achsenhintergrund explizit weiß (verhindert "alles blau" durch Styles)
ax.set_facecolor("white")
fig.patch.set_facecolor("white")
# Y-Limits so, dass alle Zonen sichtbar sind
ymin = min(40, min(values) - 20)
ymax = max(300, max(values) + 20)
ax.set_ylim(ymin, ymax)
# Deutlichere Farben + höheres Alpha (Telegram komprimiert!)
RED = "#ff6b6b"
YELLOW = "#ffd166"
GREEN = "#2ecc71"
# Zonen (zorder klein -> liegen "hinten")
ax.axhspan(ymin, 80, facecolor=RED, alpha=0.35, zorder=0)
ax.axhspan(80, 95, facecolor=YELLOW, alpha=0.35, zorder=0)
ax.axhspan(95, 180, facecolor=GREEN, alpha=0.28, zorder=0)
ax.axhspan(180, 250, facecolor=YELLOW, alpha=0.35, zorder=0)
ax.axhspan(250, ymax, facecolor=RED, alpha=0.35, zorder=0)
# Zielbereich als dünne Kontur (hilft visuell)
ax.axhline(80, linewidth=1, alpha=0.4, zorder=1)
ax.axhline(180, linewidth=1, alpha=0.4, zorder=1)
# Verlauf (zorder hoch -> immer sichtbar oben)
ax.plot(times, values, linewidth=2.2, marker="o", markersize=3.2, zorder=5)
ax.set_ylabel("mg/dL")
ax.set_title(f"Glukoseverlauf – letzte {hours}h (Ziel: 80–180)")
ax.grid(True, alpha=0.25, zorder=2)
fig.autofmt_xdate()
buf = io.BytesIO()
plt.tight_layout()
plt.savefig(buf, format="png", dpi=140)
plt.close(fig)
buf.seek(0)
return buf.read()