From b00f6ac51251e5c83dbd291b627a6d852418edf6 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 2 Apr 2026 12:27:58 +0200 Subject: [PATCH] feat: Placeholder Registry Part B - Protein Placeholders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registers 5 protein-related placeholders with complete metadata: - protein_ziel_low: Lower protein target (1.6 g/kg × latest weight) - protein_ziel_high: Upper protein target (2.2 g/kg × latest weight) - protein_g_per_kg: Protein intake per kg body weight - protein_days_in_target: Days in protein range (format: 5/7) - protein_adequacy_28d: Protein adequacy score (0-100) All placeholders with evidence-based tagging: - 22 metadata fields per placeholder - CODE_DERIVED: Technical fields from source inspection - DRAFT_DERIVED: Semantic fields from canonical requirements - UNRESOLVED: Fields requiring clarification - TO_VERIFY: Assumptions needing verification Critical issues documented in known_limitations: - protein_g_per_kg: Weight basis inconsistency (protein 7d avg / weight latest) - protein_adequacy_28d: Score logic explicitly documented (1.4-1.6-2.2 thresholds) Registry now contains 9 placeholders total (4 Part A + 5 Part B). Framework: PLACEHOLDER_REGISTRY_FRAMEWORK.md (verbindlich ab 2026-04-02) Change Plan: .claude/task/rework_0b_placeholder/NUTRITION_PART_B_CHANGE_PLAN.md Co-Authored-By: Claude Opus 4.6 --- backend/placeholder_registrations/__init__.py | 3 +- .../nutrition_part_b.py | 429 ++++++++++++++++++ 2 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 backend/placeholder_registrations/nutrition_part_b.py diff --git a/backend/placeholder_registrations/__init__.py b/backend/placeholder_registrations/__init__.py index e1905cc..5d942ba 100644 --- a/backend/placeholder_registrations/__init__.py +++ b/backend/placeholder_registrations/__init__.py @@ -6,5 +6,6 @@ Auto-imports all placeholder registrations to populate the global registry. # Import all registration modules to trigger auto-registration from . import nutrition_part_a +from . import nutrition_part_b -__all__ = ['nutrition_part_a'] +__all__ = ['nutrition_part_a', 'nutrition_part_b'] diff --git a/backend/placeholder_registrations/nutrition_part_b.py b/backend/placeholder_registrations/nutrition_part_b.py new file mode 100644 index 0000000..43d20ab --- /dev/null +++ b/backend/placeholder_registrations/nutrition_part_b.py @@ -0,0 +1,429 @@ +""" +Nutrition Part B Placeholder Registrations + +Registers the 5 protein-specific metrics in the central placeholder registry: +- protein_ziel_low +- protein_ziel_high +- protein_g_per_kg +- protein_days_in_target +- protein_adequacy_28d + +Evidence-based metadata with clear tagging of source. +Includes documentation of open points (weight basis inconsistency, score logic). +""" + +from placeholder_registry import ( + PlaceholderMetadata, + MissingValuePolicy, + EvidenceType, + OutputType, + PlaceholderType, + register_placeholder +) + + +def register_nutrition_part_b(): + """ + Register Part B protein placeholders. + + Metadata sources: + - code-derived: extracted from actual code + - draft-derived: from canonical requirements draft + - mixed: combination of code and draft + - unresolved: not explicitly documented + - to_verify: claimed but not verified + """ + + # ── protein_ziel_low ────────────────────────────────────────────────────── + + low_metadata = PlaceholderMetadata( + key="protein_ziel_low", + category="Ernährung", + description="Unteres Proteinziel (1.6 g/kg)", + + # Technical + resolver_module="backend/placeholder_resolver.py", + resolver_function="get_protein_ziel_low", + data_layer_module="backend/data_layer/nutrition_metrics.py", + data_layer_function="get_protein_targets_data", + source_tables=["weight_log"], + + # Semantic + semantic_contract=( + "Liefert die untere Proteinziel-Grenze basierend auf aktuellem " + "Körpergewicht (1.6 g/kg). Ziel für Muskelerhalt in Maintenance-Phasen." + ), + business_meaning="Maintenance-Ziel für Muskelerhalt", + unit="g/day", + time_window="snapshot", + output_type=OutputType.NUMERIC, + placeholder_type=PlaceholderType.INTERPRETED, + format_hint="Ganzzahl", + example_output="128", + + # Quality + confidence_logic="Binary: weight vorhanden/nicht vorhanden", + missing_value_policy=MissingValuePolicy( + available=False, + value_raw=None, + missing_reason="no_data", + legacy_display="nicht verfügbar" + ), + known_limitations=( + "Basiert auf single-point weight (latest entry); " + "anfällig für Gewichts-Outlier (z.B. nach Refeed-Tag)" + ), + + # Architecture + layer_1_decision="Data Layer (nutrition_metrics.get_protein_targets_data)", + layer_2a_decision="Placeholder Resolver (formatting only)", + layer_2b_reuse_possible=None, # to_verify + architecture_alignment="Phase 0c Multi-Layer Architecture conform", + issue_53_alignment="Layer separation established" + ) + + # Evidence + low_metadata.set_evidence("key", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("category", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("description", EvidenceType.MIXED) + low_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED) + low_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED) + low_metadata.set_evidence("unit", EvidenceType.MIXED) # implicit in code, confirmed by draft + low_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("placeholder_type", EvidenceType.MIXED) + low_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("known_limitations", EvidenceType.MIXED) + low_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY) + low_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED) + low_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED) + + register_placeholder(low_metadata) + + # ── protein_ziel_high ───────────────────────────────────────────────────── + + high_metadata = PlaceholderMetadata( + key="protein_ziel_high", + category="Ernährung", + description="Oberes Proteinziel (2.2 g/kg)", + + # Technical (same as protein_ziel_low) + resolver_module="backend/placeholder_resolver.py", + resolver_function="get_protein_ziel_high", + data_layer_module="backend/data_layer/nutrition_metrics.py", + data_layer_function="get_protein_targets_data", + source_tables=["weight_log"], + + # Semantic + semantic_contract=( + "Liefert die obere Proteinziel-Grenze basierend auf aktuellem " + "Körpergewicht (2.2 g/kg). Ziel für Muskelaufbau in hypertrophen Phasen." + ), + business_meaning="Muskelaufbau-Ziel für hypertrophe Phasen", + unit="g/day", + time_window="snapshot", + output_type=OutputType.NUMERIC, + placeholder_type=PlaceholderType.INTERPRETED, + format_hint="Ganzzahl", + example_output="176", + + # Quality + confidence_logic="Binary: weight vorhanden/nicht vorhanden", + missing_value_policy=MissingValuePolicy( + available=False, + value_raw=None, + missing_reason="no_data", + legacy_display="nicht verfügbar" + ), + known_limitations=( + "Basiert auf single-point weight (latest entry); " + "anfällig für Gewichts-Outlier" + ), + + # Architecture + layer_1_decision="Data Layer (nutrition_metrics.get_protein_targets_data)", + layer_2a_decision="Placeholder Resolver (formatting only)", + layer_2b_reuse_possible=None, + architecture_alignment="Phase 0c Multi-Layer Architecture conform", + issue_53_alignment="Layer separation established" + ) + + # Evidence (identical to protein_ziel_low) + high_metadata.set_evidence("key", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("category", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("description", EvidenceType.MIXED) + high_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED) + high_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED) + high_metadata.set_evidence("unit", EvidenceType.MIXED) + high_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("placeholder_type", EvidenceType.MIXED) + high_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("known_limitations", EvidenceType.MIXED) + high_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY) + high_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED) + high_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED) + + register_placeholder(high_metadata) + + # ── protein_g_per_kg ────────────────────────────────────────────────────── + + gpk_metadata = PlaceholderMetadata( + key="protein_g_per_kg", + category="Ernährung", + description="Protein g/kg Körpergewicht", + + # Technical + resolver_module="backend/placeholder_resolver.py", + resolver_function="_safe_float", + data_layer_module="backend/data_layer/nutrition_metrics.py", + data_layer_function="calculate_protein_g_per_kg", + source_tables=["nutrition_log", "weight_log"], + + # Semantic + semantic_contract=( + "Liefert die durchschnittliche Proteinzufuhr relativ zum Körpergewicht. " + "Berechnung: protein_7d_avg / latest_weight. " + "WICHTIG: Protein ist geglättet (7d), Gewicht ist single-point." + ), + business_meaning="Zentraler Zielindikator für Muskelerhalt und Aufbau", + unit="g/kg/day", + time_window="mixed", # protein 7d, weight snapshot + output_type=OutputType.NUMERIC, + placeholder_type=PlaceholderType.INTERPRETED, + format_hint="Dezimalzahl (1-2 Stellen)", + example_output="1.95", + + # Quality + confidence_logic="Minimum von protein_confidence und weight_availability", + missing_value_policy=MissingValuePolicy( + available=False, + value_raw=None, + missing_reason="insufficient_data", + legacy_display="nicht verfügbar" + ), + known_limitations=( + "KRITISCHE INKONSISTENZ: Protein ist geglättet (7d average), " + "Gewicht ist single-point (latest). Anfällig für Gewichts-Outlier. " + "Ein Refeed-Tag kann den Wert stark verfälschen, obwohl Protein-Intake stabil ist." + ), + + # Architecture + layer_1_decision="Data Layer (nutrition_metrics.calculate_protein_g_per_kg)", + layer_2a_decision="Placeholder Resolver (_safe_float wrapper)", + layer_2b_reuse_possible=None, + architecture_alignment="Phase 0c Multi-Layer Architecture conform", + issue_53_alignment="Layer separation established" + ) + + # Evidence + gpk_metadata.set_evidence("key", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("category", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("description", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("semantic_contract", EvidenceType.MIXED) # code + explicit documentation + gpk_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED) + gpk_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED) # explicitly documented as mixed + gpk_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("placeholder_type", EvidenceType.MIXED) + gpk_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("confidence_logic", EvidenceType.UNRESOLVED) # not explicitly documented + gpk_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED) # identified from code analysis + gpk_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY) + gpk_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED) + gpk_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED) + + register_placeholder(gpk_metadata) + + # ── protein_days_in_target ──────────────────────────────────────────────── + + days_metadata = PlaceholderMetadata( + key="protein_days_in_target", + category="Ernährung", + description="Tage im Protein-Zielbereich (7d)", + + # Technical + resolver_module="backend/placeholder_resolver.py", + resolver_function="_safe_str", + data_layer_module="backend/data_layer/nutrition_metrics.py", + data_layer_function="calculate_protein_days_in_target", + source_tables=["nutrition_log", "weight_log"], + + # Semantic + semantic_contract=( + "Liefert Anzahl Tage im Protein-Zielbereich relativ zu Gesamttagen. " + "Target-Range: 1.6-2.2 g/kg (hardcoded). " + "Format: 'X/Y' (z.B. '5/7' = 5 von 7 Tagen im Ziel)." + ), + business_meaning="Adhärenz-Indikator für Proteinversorgung", + unit="days_ratio", + time_window="7d", + output_type=OutputType.STRING, + placeholder_type=PlaceholderType.INTERPRETED, + format_hint="String format 'X/Y' (e.g. '5/7')", + example_output="5/7", + + # Quality + confidence_logic="Abhängig von nutrition_log Datenabdeckung", + missing_value_policy=MissingValuePolicy( + available=False, + value_raw=None, + missing_reason="no_data", + legacy_display="nicht verfügbar" + ), + known_limitations=( + "Target-Range 1.6-2.2 g/kg fest kodiert (default parameters), " + "nicht konfigurierbar. Keine Integration mit Goal-System." + ), + + # Architecture + layer_1_decision="Data Layer (nutrition_metrics.calculate_protein_days_in_target)", + layer_2a_decision="Placeholder Resolver (_safe_str wrapper)", + layer_2b_reuse_possible=None, + architecture_alignment="Phase 0c Multi-Layer Architecture conform", + issue_53_alignment="Layer separation established" + ) + + # Evidence + days_metadata.set_evidence("key", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("category", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("description", EvidenceType.MIXED) + days_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("semantic_contract", EvidenceType.MIXED) + days_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED) + days_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("placeholder_type", EvidenceType.MIXED) + days_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("confidence_logic", EvidenceType.UNRESOLVED) + days_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY) + days_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED) + days_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED) + + register_placeholder(days_metadata) + + # ── protein_adequacy_28d ────────────────────────────────────────────────── + + adequacy_metadata = PlaceholderMetadata( + key="protein_adequacy_28d", + category="Ernährung", + description="Protein Adequacy Score (0-100)", + + # Technical + resolver_module="backend/placeholder_resolver.py", + resolver_function="_safe_int", + data_layer_module="backend/data_layer/nutrition_metrics.py", + data_layer_function="calculate_protein_adequacy_28d", + source_tables=["nutrition_log", "weight_log"], + + # Semantic + semantic_contract=( + "Liefert standardisierten Angemessenheitswert der Proteinversorgung " + "über 28 Tage relativ zu definierten Protein-Zielbereichen (1.6-2.2 g/kg). " + "Score-Logik: " + "- Days in target [1.6-2.2]: 100 points; " + "- Days slightly below [1.4-1.6]: partial points (linear interpolation); " + "- Days far below (<1.4): 0 points; " + "- Days above (>2.2): 100 points (no penalty). " + "Final score: average over 28d." + ), + business_meaning="Verdichteter Zielerreichungsindikator für Proteinversorgung", + unit="score", + time_window="28d", + output_type=OutputType.NUMERIC, + placeholder_type=PlaceholderType.SCORE, + format_hint="Integer 0-100, höher = besser", + example_output="82", + + # Quality + confidence_logic="Abgeleitet aus Datenabdeckung über 28d", + missing_value_policy=MissingValuePolicy( + available=False, + value_raw=None, + missing_reason="insufficient_data", + legacy_display="nicht verfügbar" + ), + known_limitations=( + "Score muss transparent erklärt werden; ohne Skalen-Dokumentation " + "interpretationsanfällig. Scoring-Schwellen [1.4, 1.6, 2.2] nicht explizit " + "im Code dokumentiert, nur in Logik implementiert." + ), + + # Architecture + layer_1_decision="Data Layer (nutrition_metrics.calculate_protein_adequacy_28d)", + layer_2a_decision="Placeholder Resolver (_safe_int wrapper)", + layer_2b_reuse_possible=None, + architecture_alignment="Phase 0c Multi-Layer Architecture conform", + issue_53_alignment="Layer separation established" + ) + + # Evidence + adequacy_metadata.set_evidence("key", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("category", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("description", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("semantic_contract", EvidenceType.MIXED) # code + explicit documentation + adequacy_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED) + adequacy_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("placeholder_type", EvidenceType.MIXED) + adequacy_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("confidence_logic", EvidenceType.UNRESOLVED) + adequacy_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("known_limitations", EvidenceType.MIXED) # code analysis + draft + adequacy_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY) + adequacy_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED) + adequacy_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED) + + register_placeholder(adequacy_metadata) + + +# Auto-register on import +register_nutrition_part_b()