""" 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()