Spaces:
Running
Running
File size: 2,907 Bytes
9519ca6 4832db5 9519ca6 4832db5 3b0cfc0 9f7fcbb 3b0cfc0 9f7fcbb 4832db5 3b0cfc0 9f7fcbb 3b0cfc0 9f7fcbb 3b0cfc0 9f7fcbb 3b0cfc0 9f7fcbb 9519ca6 3b0cfc0 4832db5 3b0cfc0 9519ca6 4832db5 3b0cfc0 4832db5 3b0cfc0 9f7fcbb 3b0cfc0 4832db5 3b0cfc0 9f7fcbb 3b0cfc0 4832db5 3b0cfc0 4832db5 3b0cfc0 4832db5 3b0cfc0 4832db5 9519ca6 3b0cfc0 9519ca6 4832db5 9519ca6 3b0cfc0 9519ca6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | # 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() |