Updated charts for glucose data and profile chart
CI/CD / test (push) Failing after 6s
CI/CD / sonarqube (push) Skipped
CI/CD / build-and-scan (push) Skipped
CI/CD / deploy (push) Skipped

This commit is contained in:
2026-09-06 14:43:19 +02:00
parent bd66d3dacd
commit 95b3aff310
6 changed files with 262 additions and 120 deletions
+36 -16
View File
@@ -230,7 +230,7 @@ def _insulin_profile_figure_png(profile: dict) -> bytes:
if bars:
hours = [b["hour"] for b in bars]
values = [b["value"] for b in bars]
ax.step(hours, values, where="post", color=_BASAL_COLOR, linewidth=1.5)
ax.step(hours, values, where="post", color=_BASAL_COLOR, linewidth=1.5, alpha=0.8)
ax.fill_between(hours, values, step="post", color=_BASAL_COLOR, alpha=0.3)
ax.set_xlim(0, 24)
ax.set_xticks(range(0, 25, 2))
@@ -261,9 +261,8 @@ def _add_carbs_overlay(ax2, day_treatments: pd.DataFrame, day_start) -> None:
ax2.bar(hours, carbs["carbs"], width=0.25, color=_CARBS_COLOR, alpha=0.55, label="Koolhydraten")
def _add_bolus_overlay(ax, day_treatments: pd.DataFrame, day_start, lane_y: float) -> None:
bolus = classify_bolus(day_treatments)
for category in (BOLUS_CATEGORY_SMB, BOLUS_CATEGORY_CORRECTION, BOLUS_CATEGORY_FOOD):
def _add_bolus_overlay(ax, bolus: pd.DataFrame, day_start, lane_y: float, categories: tuple[str, ...]) -> None:
for category in categories:
subset = bolus[bolus["category"] == category] if not bolus.empty else bolus
if subset.empty:
continue
@@ -291,35 +290,56 @@ def _add_target_overlay(ax, profile: dict | None, day_start, day_end) -> None:
def _daily_figure_png(
day_df: pd.DataFrame, day_treatments: pd.DataFrame, profile: dict | None, day_label: str, day_start, day_end
) -> bytes:
"""Eén dag als grafiek: glucoselijn + basaal/bolus/koolhydraten/streefwaarde-overlay."""
"""Eén dag als twee gestapelde grafieken (gedeelde tijdas, één gedeelde legenda):
boven glucose + streefwaarde + koolhydraten + voeding/correctiebolus, onder
basaal + SMB — gescheiden omdat één gecombineerde grafiek te veel informatie
bevatte om nog leesbaar te zijn."""
hours = day_df["timestamp"].dt.hour + day_df["timestamp"].dt.minute / 60
fig, ax = plt.subplots(figsize=(11, 3.8))
fig, (ax, ax_basal) = plt.subplots(2, 1, figsize=(11, 5.2), sharex=True, height_ratios=[2.2, 1])
ax2 = ax.twinx()
basal = effective_basal_series(profile, day_treatments, day_start, day_end)
_add_basal_overlay(ax2, basal, day_start)
bolus = classify_bolus(day_treatments)
_add_carbs_overlay(ax2, day_treatments, day_start)
lane_y = (day_df["mmol"].min() - 1.0) if not day_df.empty else LOW - 1.0
_add_bolus_overlay(ax, day_treatments, day_start, lane_y)
_add_bolus_overlay(ax, bolus, day_start, lane_y, categories=(BOLUS_CATEGORY_CORRECTION, BOLUS_CATEGORY_FOOD))
_add_target_overlay(ax, profile, day_start, day_end)
ax.plot(hours, day_df["mmol"], color="#2a78d6", linewidth=1.4, label="Glucose", zorder=6)
_add_threshold_lines(ax)
ax.set_xlim(0, 24)
ax.set_xticks(range(0, 25, 2))
ax.set_ylabel("mmol/L", fontsize=9)
ax2.set_ylabel("Basaal (E/uur) · KH (g)", fontsize=9)
ax2.set_ylabel("Koolhydraten (g)", fontsize=9)
ax.set_title(day_label, fontsize=11, loc="left")
ax.tick_params(labelsize=8)
ax2.tick_params(labelsize=8)
basal = effective_basal_series(profile, day_treatments, day_start, day_end)
_add_basal_overlay(ax_basal, basal, day_start)
smb_lane_y = (basal["rate"].max() * 1.15) if not basal.empty else 1.0
_add_bolus_overlay(ax_basal, bolus, day_start, smb_lane_y, categories=(BOLUS_CATEGORY_SMB,))
ax_basal.set_xlim(0, 24)
ax_basal.set_xticks(range(0, 25, 2))
ax_basal.set_xlabel(_HOUR_OF_DAY_LABEL)
ax_basal.set_ylabel("Basaal (E/uur)", fontsize=9)
ax_basal.tick_params(labelsize=8)
lines1, labels1 = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax.legend(
lines1 + lines2, labels1 + labels2, loc="upper center", bbox_to_anchor=(0.5, -0.2), ncol=6, fontsize=7, frameon=False
lines3, labels3 = ax_basal.get_legend_handles_labels()
ax_basal.legend(
lines1 + lines2 + lines3,
labels1 + labels2 + labels3,
loc="upper center",
bbox_to_anchor=(0.5, -0.35),
ncol=6,
fontsize=7,
frameon=False,
)
fig.tight_layout()
# fig.tight_layout() introspecteert elke as se bounding box om marges te
# berekenen, en matplotlib ondersteunt dat niet betrouwbaar zodra er twee
# hoofd-assen (ax/ax_basal) plus een twinx (ax2) tegelijk in de figuur
# zitten (waarschuwing "Axes ... not compatible with tight_layout").
# subplots_adjust() met vaste marges omzeilt die introspectie.
fig.subplots_adjust(left=0.06, right=0.94, top=0.93, bottom=0.24, hspace=0.35)
return _fig_to_png(fig)