# 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()