Add clinical threshold lines to AGP/Dagoverzicht charts, TIR stacked bar to Samenvatting

Closes gitea #14, #15.
This commit is contained in:
2026-08-01 22:26:26 +02:00
parent 699848b479
commit 00fb81b283
5 changed files with 135 additions and 3 deletions
+92 -1
View File
@@ -15,7 +15,15 @@ import plotly.graph_objects as go
import streamlit as st
from nightscout_client import fetch_entries_range, fetch_treatments_range
from reporting import completeness_warning, detect_events, summarize_events
from reporting import (
HIGH,
LOW,
VERY_HIGH,
VERY_LOW,
completeness_warning,
detect_events,
summarize_events,
)
MMOL_HOVERTEMPLATE = "%{x|%d-%m %H:%M} · %{y:.1f} mmol/L<extra></extra>"
@@ -41,6 +49,14 @@ PALETTE = {
"muted": "#898781",
"text": "#52514e",
"good": "#0ca30c",
# Losstaand van "low"/"high" hierboven (die blijven gedeeld tussen
# andere grafieken): de 5-bands TIR-grafiek (gitea #14) heeft juist
# zichtbaar onderscheid tussen laag/zeer-laag en hoog/zeer-hoog nodig.
"tir_very_low": "#8b1a1a",
"tir_low": "#d03b3b",
"tir_in_range": "#0ca30c",
"tir_high": "#fab219",
"tir_very_high": "#c76f0a",
},
"dark": {
"glucose": "#3987e5",
@@ -55,9 +71,24 @@ PALETTE = {
"muted": "#898781",
"text": "#c3c2b7",
"good": "#0ca30c",
"tir_very_low": "#a32626",
"tir_low": "#d95d5d",
"tir_in_range": "#0ca30c",
"tir_high": "#fab219",
"tir_very_high": "#d9852e",
},
}
# Klinische drempellijnen (Battelino et al. 2019), gedeeld tussen de AGP- en
# Dagoverzicht-pagina's (FR3, FR4 — gitea #15) zodat beide grafieken exact
# dezelfde 4 lijnen op dezelfde manier tonen.
THRESHOLD_LINES = [
(VERY_LOW, "3.0"),
(LOW, "3.9"),
(HIGH, "10.0"),
(VERY_HIGH, "13.9"),
]
def hex_to_rgba(hex_color: str, alpha: float) -> str:
"""Zet een '#rrggbb'-kleur om naar een 'rgba(r,g,b,a)'-string voor Plotly-fills."""
@@ -98,6 +129,66 @@ def load_range_data(start: date, end: date):
return glucose, treatments
def add_threshold_lines(fig: go.Figure, colors: dict, row: int | None = None) -> None:
"""Teken de 4 klinische drempellijnen (3.0/3.9/10.0/13.9 mmol/L) op een grafiek.
Gedeeld door de AGP- en Dagoverzicht-pagina's (gitea #15). `row` wordt
doorgegeven aan `add_hline` voor subplot-figuren (Dagoverzicht); voor een
los-staande figuur (AGP) blijft dit `None`.
"""
kwargs = {} if row is None else {"row": row, "col": 1}
for value, label in THRESHOLD_LINES:
fig.add_hline(
y=value,
line={"color": colors["muted"], "width": 1, "dash": "dot"},
annotation_text=label,
annotation_position="right",
annotation_font={"size": 9, "color": colors["muted"]},
**kwargs,
)
TIR_BAND_ORDER = ["very_low", "low", "in_range", "high", "very_high"]
TIR_BAND_LABELS = {
"very_low": "Zeer laag",
"low": "Laag",
"in_range": "In bereik",
"high": "Hoog",
"very_high": "Zeer hoog",
}
def build_tir_stacked_bar_figure(tir: dict, colors: dict) -> go.Figure:
"""Bouw de 5-bands tijd-in-bereik als één gestapelde staafgrafiek (gitea #14).
Eén staaf, geen x-as-label, percentage op de y-as; van onder naar boven
"Zeer laag" -> "Zeer hoog" — dezelfde volgorde als de klinische banden.
"""
fig = go.Figure()
for band in TIR_BAND_ORDER:
fig.add_trace(
go.Bar(
x=[""],
y=[tir[band]],
name=TIR_BAND_LABELS[band],
marker_color=colors[f"tir_{band}"],
hovertemplate=f"{TIR_BAND_LABELS[band]}: %{{y:.0f}}%<extra></extra>",
)
)
fig.update_layout(
barmode="stack",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
font_color=colors["text"],
height=140,
margin={"l": 10, "r": 10, "t": 10, "b": 10},
yaxis={"title": "%", "range": [0, 100], "gridcolor": colors["grid"]},
xaxis={"visible": False},
legend={"orientation": "h", "yanchor": "bottom", "y": 1.02, "xanchor": "left", "x": 0},
)
return fig
def render_completeness_warning(glucose: pd.DataFrame, start: date, end: date) -> None:
"""Toon reporting.completeness_warning()'s oordeel als st.warning, indien nodig.