- Added new functions for BMI and goal weight/body fat percentage retrieval in `body_metrics.py`. - Introduced training frequency and inter-session gap calculations in `activity_metrics.py`. - Updated placeholder registrations to include new metrics for nutrition and activity. - Improved data handling in `placeholder_resolver.py` for better integration of new metrics. - Enhanced documentation across modules to reflect the new functionalities. These updates improve the accuracy and comprehensiveness of health and fitness assessments within the application.
238 lines
11 KiB
Python
238 lines
11 KiB
Python
"""
|
||
Registry: BMI, Profil-Ziele (goal_weight, goal_bf_pct), body_progress_score.
|
||
|
||
Profilfelder sind unabhängig von der goals-Tabelle; operative Ziele über andere Keys.
|
||
"""
|
||
|
||
from placeholder_registry import (
|
||
PlaceholderMetadata,
|
||
MissingValuePolicy,
|
||
EvidenceType,
|
||
OutputType,
|
||
PlaceholderType,
|
||
register_placeholder,
|
||
)
|
||
|
||
|
||
def register_body_extras():
|
||
bmi = PlaceholderMetadata(
|
||
key="bmi",
|
||
category="Körper",
|
||
description="Body-Mass-Index aus letztem Gewicht und Profilgröße (cm)",
|
||
resolver_module="backend/placeholder_resolver.py",
|
||
resolver_function="calculate_bmi",
|
||
data_layer_module="backend/data_layer/body_metrics.py",
|
||
data_layer_function="get_bmi_data",
|
||
source_tables=["profiles", "weight_log"],
|
||
semantic_contract=(
|
||
"BMI = Gewicht_kg / (Größe_m)² mit Größe_m = profiles.height / 100 "
|
||
"und Gewicht = jüngster Eintrag in weight_log."
|
||
),
|
||
business_meaning="Standard-Körpermaß für Coaching und Risiko-Kontext",
|
||
unit="kg/m²",
|
||
time_window="latest weight + aktuelle Profilgröße",
|
||
output_type=OutputType.NUMERIC,
|
||
placeholder_type=PlaceholderType.RAW_DATA,
|
||
format_hint="Eine Dezimalstelle, ohne Einheit im String",
|
||
example_output="24.3",
|
||
minimum_data_requirements="Profil mit height > 0 und mindestens ein weight_log",
|
||
quality_filter_policy=None,
|
||
confidence_logic="high nur wenn BMI berechenbar; sonst insufficient / Anzeige nicht verfügbar",
|
||
missing_value_policy=MissingValuePolicy(
|
||
available=False,
|
||
value_raw=None,
|
||
missing_reason="no_data",
|
||
legacy_display="nicht verfügbar",
|
||
),
|
||
known_limitations=(
|
||
"Keine ethnischen Referenzkurven; Profilgröße kann veraltet sein. "
|
||
"Unterscheidet nicht Muskelmasse vs. Fett."
|
||
),
|
||
layer_1_decision="body_metrics.get_bmi_data",
|
||
layer_2a_decision="placeholder_resolver.calculate_bmi (Format)",
|
||
layer_2b_reuse_possible=True,
|
||
architecture_alignment="Phase 0c",
|
||
issue_53_alignment="Layer 1 als Quelle",
|
||
evidence={},
|
||
)
|
||
for field in (
|
||
"key", "category", "description", "resolver_module", "resolver_function",
|
||
"data_layer_module", "data_layer_function", "source_tables",
|
||
"semantic_contract", "business_meaning", "unit", "time_window",
|
||
"output_type", "placeholder_type", "format_hint", "example_output",
|
||
"minimum_data_requirements", "confidence_logic", "missing_value_policy",
|
||
"known_limitations", "layer_1_decision", "layer_2a_decision",
|
||
"layer_2b_reuse_possible", "architecture_alignment", "issue_53_alignment",
|
||
):
|
||
bmi.set_evidence(field, EvidenceType.CODE_DERIVED)
|
||
bmi.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||
bmi.set_evidence("known_limitations", EvidenceType.MIXED)
|
||
register_placeholder(bmi)
|
||
|
||
gw = PlaceholderMetadata(
|
||
key="goal_weight",
|
||
category="Körper",
|
||
description="Zielgewicht aus Profilfeld profiles.goal_weight (kg)",
|
||
resolver_module="backend/placeholder_resolver.py",
|
||
resolver_function="get_goal_weight",
|
||
data_layer_module="backend/data_layer/body_metrics.py",
|
||
data_layer_function="get_profile_goal_weight_data",
|
||
source_tables=["profiles"],
|
||
semantic_contract=(
|
||
"Strategisches Soll-Gewicht im Profil; unabhängig von der goals-Tabelle "
|
||
"(dort detaillierte Ziele mit Fortschritt)."
|
||
),
|
||
business_meaning="Schneller Abgleich Prompt vs. Profil-Default-Zielgewicht",
|
||
unit="kg",
|
||
time_window="Profil-Snapshot",
|
||
output_type=OutputType.NUMERIC,
|
||
placeholder_type=PlaceholderType.RAW_DATA,
|
||
format_hint="Eine Dezimalstelle oder Text „nicht gesetzt“",
|
||
example_output="82.0",
|
||
minimum_data_requirements="profiles.goal_weight IS NOT NULL",
|
||
quality_filter_policy=None,
|
||
confidence_logic="high wenn gesetzt",
|
||
missing_value_policy=MissingValuePolicy(
|
||
available=False,
|
||
value_raw=None,
|
||
missing_reason="not_set",
|
||
legacy_display="nicht gesetzt",
|
||
),
|
||
known_limitations="Kann von aktiven goals.weight-Zielen abweichen.",
|
||
layer_1_decision="body_metrics.get_profile_goal_weight_data",
|
||
layer_2a_decision="placeholder_resolver.get_goal_weight",
|
||
layer_2b_reuse_possible=True,
|
||
architecture_alignment="Phase 0c",
|
||
issue_53_alignment="Layer 1 als Quelle",
|
||
evidence={},
|
||
)
|
||
for field in (
|
||
"key", "category", "description", "resolver_module", "resolver_function",
|
||
"data_layer_module", "data_layer_function", "source_tables",
|
||
"semantic_contract", "unit", "time_window", "output_type",
|
||
"placeholder_type", "format_hint", "example_output",
|
||
"minimum_data_requirements", "confidence_logic", "missing_value_policy",
|
||
"layer_1_decision", "layer_2a_decision", "layer_2b_reuse_possible",
|
||
"architecture_alignment", "issue_53_alignment",
|
||
):
|
||
gw.set_evidence(field, EvidenceType.CODE_DERIVED)
|
||
gw.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||
gw.set_evidence("known_limitations", EvidenceType.MIXED)
|
||
register_placeholder(gw)
|
||
|
||
gbf = PlaceholderMetadata(
|
||
key="goal_bf_pct",
|
||
category="Körper",
|
||
description="Ziel-Körperfettanteil aus Profilfeld profiles.goal_bf_pct (%)",
|
||
resolver_module="backend/placeholder_resolver.py",
|
||
resolver_function="get_goal_bf_pct",
|
||
data_layer_module="backend/data_layer/body_metrics.py",
|
||
data_layer_function="get_profile_goal_bf_pct_data",
|
||
source_tables=["profiles"],
|
||
semantic_contract="Strategisches Ziel-KFA im Profil.",
|
||
business_meaning="Prompt-Abgleich mit Profil-Ziel-KFA",
|
||
unit="%",
|
||
time_window="Profil-Snapshot",
|
||
output_type=OutputType.NUMERIC,
|
||
placeholder_type=PlaceholderType.RAW_DATA,
|
||
format_hint="Eine Dezimalstelle oder Text „nicht gesetzt“",
|
||
example_output="15.0",
|
||
minimum_data_requirements="profiles.goal_bf_pct IS NOT NULL",
|
||
quality_filter_policy=None,
|
||
confidence_logic="high wenn gesetzt",
|
||
missing_value_policy=MissingValuePolicy(
|
||
available=False,
|
||
value_raw=None,
|
||
missing_reason="not_set",
|
||
legacy_display="nicht gesetzt",
|
||
),
|
||
known_limitations="Kann von goals body_fat abweichen.",
|
||
layer_1_decision="body_metrics.get_profile_goal_bf_pct_data",
|
||
layer_2a_decision="placeholder_resolver.get_goal_bf_pct",
|
||
layer_2b_reuse_possible=True,
|
||
architecture_alignment="Phase 0c",
|
||
issue_53_alignment="Layer 1 als Quelle",
|
||
evidence={},
|
||
)
|
||
for field in (
|
||
"key", "category", "description", "resolver_module", "resolver_function",
|
||
"data_layer_module", "data_layer_function", "source_tables",
|
||
"semantic_contract", "unit", "time_window", "output_type",
|
||
"placeholder_type", "format_hint", "example_output",
|
||
"minimum_data_requirements", "confidence_logic", "missing_value_policy",
|
||
"layer_1_decision", "layer_2a_decision", "layer_2b_reuse_possible",
|
||
"architecture_alignment", "issue_53_alignment",
|
||
):
|
||
gbf.set_evidence(field, EvidenceType.CODE_DERIVED)
|
||
gbf.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||
gbf.set_evidence("known_limitations", EvidenceType.MIXED)
|
||
register_placeholder(gbf)
|
||
|
||
bps = PlaceholderMetadata(
|
||
key="body_progress_score",
|
||
category="Körper",
|
||
description="Körper-Fortschritts-Score 0–100, gewichtet nach Focus (Abnehmen, Muskelaufbau, Recomp)",
|
||
resolver_module="backend/placeholder_resolver.py",
|
||
resolver_function="_safe_int",
|
||
data_layer_module="backend/data_layer/body_metrics.py",
|
||
data_layer_function="calculate_body_progress_score",
|
||
source_tables=[
|
||
"user_focus_area_weights",
|
||
"focus_area_definitions",
|
||
"goals",
|
||
"weight_log",
|
||
"caliper_log",
|
||
"circumference_log",
|
||
],
|
||
semantic_contract=(
|
||
"Gewichteter Mittelwert aus bis zu drei Komponenten: Trend vs. Gewichtsziel, "
|
||
"Körperzusammensetzung (FM/LBM/Recomp-Quadrant), Taille-Trend. "
|
||
"Komponenten nur aktiv, wenn passende Focus-Gewichte > 0."
|
||
),
|
||
business_meaning="Meta-KPI: passt dokumentierter Körperfortschritt zur gewichteten Körper-Priorität?",
|
||
unit="Score (0–100)",
|
||
time_window="composite (u. a. 28d Deltas, Ziel-Fortschritt)",
|
||
output_type=OutputType.NUMERIC,
|
||
placeholder_type=PlaceholderType.SCORE,
|
||
format_hint="Ganzzahl oder „nicht verfügbar“",
|
||
example_output="72",
|
||
minimum_data_requirements=(
|
||
"Summe der Körper-Focus-Gewichte (weight_loss + muscle_gain + body_recomposition) > 0 "
|
||
"und mindestens eine bewertbare Komponente mit Daten."
|
||
),
|
||
quality_filter_policy=None,
|
||
confidence_logic="Kein separates Confidence-Feld; None wenn keine Körper-Gewichtung oder keine Teilscores.",
|
||
missing_value_policy=MissingValuePolicy(
|
||
available=False,
|
||
value_raw=None,
|
||
missing_reason="not_applicable",
|
||
legacy_display="nicht verfügbar",
|
||
),
|
||
known_limitations=(
|
||
"Abhängig von user_focus_area_weights und aktiven weight-goals für Gewichts-Teilscore. "
|
||
"Taille-Score wird mit festem Basisgewicht 20+ eingemischt und kann dominieren."
|
||
),
|
||
layer_1_decision="body_metrics.calculate_body_progress_score",
|
||
layer_2a_decision="placeholder_resolver._safe_int('body_progress_score', …)",
|
||
layer_2b_reuse_possible=True,
|
||
architecture_alignment="Phase 0c",
|
||
issue_53_alignment="Layer 1 als Quelle",
|
||
evidence={},
|
||
)
|
||
for field in (
|
||
"key", "category", "description", "resolver_module", "resolver_function",
|
||
"data_layer_module", "data_layer_function", "source_tables",
|
||
"semantic_contract", "unit", "time_window", "output_type",
|
||
"placeholder_type", "format_hint", "example_output",
|
||
"minimum_data_requirements", "confidence_logic", "missing_value_policy",
|
||
"layer_1_decision", "layer_2a_decision", "layer_2b_reuse_possible",
|
||
"architecture_alignment", "issue_53_alignment",
|
||
):
|
||
bps.set_evidence(field, EvidenceType.CODE_DERIVED)
|
||
bps.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
|
||
bps.set_evidence("known_limitations", EvidenceType.MIXED)
|
||
register_placeholder(bps)
|
||
|
||
|
||
register_body_extras()
|