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