Fix bolus/basal insulin categorization: split SMB from manual corrections, add basal calc
AndroidAPS records an automatic SMB micro-bolus as eventType "Correction Bolus" with type "SMB", not as its own eventType. bolus_stats() was grouping by eventType alone, so every SMB was silently counted as a manual correction bolus. Verified against production data: 100% of "Correction Bolus" treatments over 90 days were actually type=SMB. Also adds basal_stats(), computing delivered basal directly from Temp Basal treatments (rate x duration) without needing the pump profile. Closes gitea #20.
This commit is contained in:
+48
-6
@@ -330,22 +330,35 @@ def day_of_week_pattern(glucose: pd.DataFrame) -> pd.DataFrame:
|
||||
return pd.DataFrame(rows, columns=columns)
|
||||
|
||||
|
||||
SMB_TYPE = "SMB"
|
||||
BASAL_EVENT_TYPE = "Temp Basal"
|
||||
|
||||
|
||||
def bolus_stats(treatments: pd.DataFrame) -> dict:
|
||||
"""Samenvatting van bolus-insuline, opgesplitst naar type.
|
||||
|
||||
Let op: dit betreft alleen bolusinsuline (maaltijd/correctie/combo/SMB).
|
||||
Basaalinsuline wordt hier niet in meegenomen — dat vereist profielgegevens
|
||||
(zie docs/design-reporting.md, scoping-kanttekening) en levert dus geen
|
||||
volledige totale-dagdosis op.
|
||||
AndroidAPS in SMB-looping registreert een automatische micro-bolus niet
|
||||
als eigen `eventType`, maar als `eventType == "Correction Bolus"` met
|
||||
`type == "SMB"` — zonder dit onderscheid werden alle SMB's stilzwijgend
|
||||
als "Correction Bolus" (handmatige correctie) meegeteld, wat gitea #20
|
||||
meldde als "bolus stats empty/onduidelijk". Geverifieerd rechtstreeks
|
||||
tegen de productiedata: bij deze gebruiker is vrijwel elke "Correction
|
||||
Bolus" in werkelijkheid een automatische SMB.
|
||||
|
||||
Let op: dit betreft alleen bolusinsuline (voeding/correctie/combo/SMB).
|
||||
Basaalinsuline zit hier niet in — zie basal_stats().
|
||||
"""
|
||||
if treatments.empty:
|
||||
return {"total_units": 0.0, "count": 0, "by_type": {}}
|
||||
|
||||
bolus = treatments[treatments["eventType"].isin(INSULIN_EVENT_TYPES) & treatments["insulin"].notna()]
|
||||
bolus = treatments[treatments["eventType"].isin(INSULIN_EVENT_TYPES) & treatments["insulin"].notna()].copy()
|
||||
if bolus.empty:
|
||||
return {"total_units": 0.0, "count": 0, "by_type": {}}
|
||||
|
||||
by_type = bolus.groupby("eventType")["insulin"].sum().round(1).to_dict()
|
||||
is_smb = (bolus["eventType"] == "Correction Bolus") & (bolus.get("type", "") == SMB_TYPE)
|
||||
bolus["category"] = bolus["eventType"].where(~is_smb, SMB_TYPE)
|
||||
|
||||
by_type = bolus.groupby("category")["insulin"].sum().round(1).to_dict()
|
||||
return {
|
||||
"total_units": round(bolus["insulin"].sum(), 1),
|
||||
"count": len(bolus),
|
||||
@@ -353,6 +366,35 @@ def bolus_stats(treatments: pd.DataFrame) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def basal_stats(treatments: pd.DataFrame, period_days: float) -> dict:
|
||||
"""Totale basaalinsuline uit Temp Basal-treatments (rate x duur), zonder pompprofiel.
|
||||
|
||||
AndroidAPS zet in gesloten-lus-modus vrijwel continu Temp Basal-treatments
|
||||
met een `absolute` snelheid (E/uur) en `duration` (minuten); de som van
|
||||
afgeleverde eenheden daaruit benadert de daadwerkelijke basaaldosis
|
||||
nauwkeurig genoeg zonder het pompprofiel te hoeven ophalen (gitea #20).
|
||||
Periodes zonder actieve temp basal (dan geldt het profiel-basaaltempo)
|
||||
worden hierin gemist — dit is dus een ondergrens, geen exacte totale-
|
||||
dagdosis.
|
||||
"""
|
||||
if treatments.empty or period_days <= 0:
|
||||
return {"total_units": 0.0, "avg_units_per_day": 0.0}
|
||||
|
||||
temp_basal = treatments[
|
||||
(treatments["eventType"] == BASAL_EVENT_TYPE)
|
||||
& treatments["absolute"].notna()
|
||||
& treatments["duration"].notna()
|
||||
]
|
||||
if temp_basal.empty:
|
||||
return {"total_units": 0.0, "avg_units_per_day": 0.0}
|
||||
|
||||
total_units = (temp_basal["absolute"] * temp_basal["duration"] / 60).sum()
|
||||
return {
|
||||
"total_units": round(total_units, 1),
|
||||
"avg_units_per_day": round(total_units / period_days, 1),
|
||||
}
|
||||
|
||||
|
||||
SITE_INTERVAL_COLUMNS = [
|
||||
"start",
|
||||
"end",
|
||||
|
||||
Reference in New Issue
Block a user