From 8fc7d9c1c4fd7b609c297ae127a795cbbcc2073f Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 19 Apr 2026 16:27:59 +0200 Subject: [PATCH] refactor: enhance body history visualization logic and frontend labels - Updated the `get_body_history_viz_bundle` function to retrieve the two most recent circumference measurements for improved data accuracy. - Refactored the handling of previous measurement data to ensure comprehensive interpolation for body metrics. - Modified frontend labels in the `buildBodyKpiTiles` function to provide clearer descriptions in German, enhancing user understanding of body metrics. --- backend/data_layer/body_viz.py | 46 +++++++++++++++++++++++++++------- frontend/src/pages/History.jsx | 8 +++--- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/backend/data_layer/body_viz.py b/backend/data_layer/body_viz.py index a73aca0..558490c 100644 --- a/backend/data_layer/body_viz.py +++ b/backend/data_layer/body_viz.py @@ -231,7 +231,7 @@ def get_body_history_viz_bundle(profile_id: str, days: int) -> Dict[str, Any]: FROM circumference_log WHERE profile_id = %s AND date >= %s ORDER BY date DESC - LIMIT 1 + LIMIT 2 """, (profile_id, cutoff), ) @@ -242,11 +242,13 @@ def get_body_history_viz_bundle(profile_id: str, days: int) -> Dict[str, Any]: FROM circumference_log WHERE profile_id = %s ORDER BY date DESC - LIMIT 1 + LIMIT 2 """, (profile_id,), ) - latest_circ_row = r2d(cur.fetchone()) + circ_latest_desc = [r2d(r) for r in cur.fetchall()] + latest_circ_row = circ_latest_desc[0] if circ_latest_desc else None + prev_circ_row = circ_latest_desc[1] if len(circ_latest_desc) > 1 else None # Latest weight in window latest_w = w_points[-1] if w_points else None @@ -334,14 +336,40 @@ def get_body_history_viz_bundle(profile_id: str, days: int) -> Dict[str, Any]: measurement["c_belly"] = safe_float(latest_circ_row.get("c_belly")) if latest_w: measurement["weight"] = safe_float(latest_w.get("weight")) + # Referenzdatum für „aktuell“: neueste verfügbare Quelle (Caliper > Umfang > Gewicht) + if not measurement.get("date"): + if latest_circ_row and latest_circ_row.get("date"): + measurement["date"] = latest_circ_row.get("date") + elif latest_w and latest_w.get("date"): + measurement["date"] = latest_w.get("date") - prev_for_interp = None + # Vorperiode: vorherige Caliper-Zeile + vorherige Umfangsmessung + vorheriges Gewicht (w_points[-2]) + prev_for_interp: Optional[Dict[str, Any]] = {} if prev_cal: - prev_for_interp = { - "date": prev_cal.get("date"), - "body_fat_pct": safe_float(prev_cal.get("body_fat_pct")), - "lean_mass": safe_float(prev_cal.get("lean_mass")), - } + prev_for_interp["date"] = prev_cal.get("date") + prev_for_interp["body_fat_pct"] = safe_float(prev_cal.get("body_fat_pct")) + prev_for_interp["lean_mass"] = safe_float(prev_cal.get("lean_mass")) + if prev_circ_row: + prev_for_interp["c_waist"] = safe_float(prev_circ_row.get("c_waist")) + prev_for_interp["c_hip"] = safe_float(prev_circ_row.get("c_hip")) + prev_for_interp["c_belly"] = safe_float(prev_circ_row.get("c_belly")) + if not prev_for_interp.get("date") and prev_circ_row.get("date"): + prev_for_interp["date"] = prev_circ_row.get("date") + if len(w_points) >= 2: + prev_for_interp["weight"] = safe_float(w_points[-2].get("weight")) + if not prev_for_interp.get("date") and w_points[-2].get("date"): + prev_for_interp["date"] = w_points[-2].get("date") + + if not prev_for_interp: + prev_for_interp = None + else: + # Mindestens ein vergleichbares Feld zur aktuellen Messung + has_cmp = any( + prev_for_interp.get(k) is not None + for k in ("body_fat_pct", "lean_mass", "weight", "c_waist", "c_belly") + ) + if not has_cmp: + prev_for_interp = None tiles = get_body_interpretation_tiles(measurement, profile_ui, prev_for_interp) diff --git a/frontend/src/pages/History.jsx b/frontend/src/pages/History.jsx index 799a5d3..4d5cd2f 100644 --- a/frontend/src/pages/History.jsx +++ b/frontend/src/pages/History.jsx @@ -187,10 +187,10 @@ function buildBodyKpiTiles({ const ok = summary.whr < (sex === 'm' ? 0.9 : 0.85) tiles.push({ key: 'whr', - category: 'WHR', + category: 'Fettverteilung', icon: '📐', value: String(summary.whr), - sublabel: 'Taille ÷ Hüfte', + sublabel: 'WHR · Taille ÷ Hüfte', verdict: whrRule ? verdictShort(whrRule.status) : (ok ? 'Gut' : 'Hinweis'), status: whrRule?.status || (ok ? 'good' : 'warn'), hoverTop: whrRule?.title || 'Waist-Hip-Ratio', @@ -203,10 +203,10 @@ function buildBodyKpiTiles({ const ok = summary.whtr < 0.5 tiles.push({ key: 'whtr', - category: 'WHtR', + category: 'Taille/Größe', icon: '📏', value: String(summary.whtr), - sublabel: 'Taille ÷ Größe', + sublabel: 'WHtR · Taille ÷ Größe', verdict: whtrRule ? verdictShort(whtrRule.status) : (ok ? 'Gut' : 'Hinweis'), status: whtrRule?.status || (ok ? 'good' : 'warn'), hoverTop: whtrRule?.title || 'Waist-to-Height-Ratio',