"""
Body Metrics Placeholder Registrations

Registers 17 Körper-Metriken in diesem Modul; insgesamt 21 Körper-Keys in der Registry
(zusätzlich body_extras.py: bmi, goal_weight, goal_bf_pct, body_progress_score).

Weight & Trends (7):
- weight_aktuell
- weight_trend
- weight_7d_median
- weight_28d_slope
- weight_90d_slope

Body Composition (5):
- kf_aktuell
- fm_28d_change
- lbm_28d_change
- waist_hip_ratio
- recomposition_quadrant

Circumference Deltas (5):
- waist_28d_delta
- arm_28d_delta
- chest_28d_delta
- hip_28d_delta
- thigh_28d_delta

Summaries (2):
- caliper_summary
- circ_summary

Evidence-based metadata with comprehensive formula documentation.
Siehe backend/data_layer/body_metrics.py als Layer-1-Implementierung.
"""

from placeholder_registry import (
    PlaceholderMetadata,
    MissingValuePolicy,
    EvidenceType,
    OutputType,
    PlaceholderType,
    register_placeholder
)


def register_body_metrics():
    """
    Register all body metrics placeholders.

    Metadata sources:
    - CODE_DERIVED: extracted from code inspection
    - DRAFT_DERIVED: from canonical requirements
    - MIXED: combination of code + interpretation
    - UNRESOLVED: not explicitly documented
    - TO_VERIFY: architecture decisions pending review
    """

    # ═════════════════════════════════════════════════════════════════════════
    # WEIGHT & TRENDS (7 Placeholders)
    # ═════════════════════════════════════════════════════════════════════════

    # ── weight_aktuell ───────────────────────────────────────────────────────

    weight_aktuell_metadata = PlaceholderMetadata(
        key="weight_aktuell",
        category="Körper",
        description="Aktuelles Gewicht in kg",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="get_latest_weight",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="get_latest_weight_data",
        source_tables=["weight_log"],

        # Semantic
        semantic_contract=(
            "Liefert den zuletzt gültigen gemessenen Gewichtswert des Nutzers in Kilogramm "
            "als Snapshot. Der Wert ist kein Trend und keine Glättung, sondern der jüngste "
            "verfügbare Messpunkt."
        ),
        business_meaning="Basiswert für Körperstatus, Zielabgleich, BMI-Berechnung, Trend- und Recomposition-Kontext",
        unit="kg",
        time_window="latest",
        output_type=OutputType.NUMERIC,
        placeholder_type=PlaceholderType.RAW_DATA,
        format_hint="Dezimalzahl in kg",
        example_output="78.4",

        # Quality
        minimum_data_requirements="Mindestens 1 gültiger Gewichtseintrag",
        quality_filter_policy="Offensichtlich ungültige oder technisch fehlerhafte Werte ausschließen",
        confidence_logic="high wenn Eintrag vorhanden, insufficient wenn nicht",
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="no_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "Ein einzelner Messwert ist stark tagesabhängig. "
            "Nicht isoliert interpretieren, wenn Trends oder Zielbewertung gefragt sind. "
            "Für Veränderungsaussagen immer mit Trend-/Median-/Delta-Placeholdern kombinieren."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.get_latest_weight_data)",
        layer_2a_decision="Placeholder Resolver (formatting only: f'{weight:.1f} kg')",
        layer_2b_reuse_possible="Ja - weight value direkt nutzbar für Charts",
        architecture_alignment="Phase 0c Multi-Layer Architecture conform",
        issue_53_alignment="Vollständige Layer-Trennung: Data Layer → Resolver → AI/Charts"
    )

    # Evidence tagging
    weight_aktuell_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)  # placeholder_resolver.py:1367
    weight_aktuell_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    weight_aktuell_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # PLACEHOLDER_MAP line 1083
    weight_aktuell_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)  # import at top
    weight_aktuell_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:26
    weight_aktuell_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)  # SQL query line 49
    weight_aktuell_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)  # canonical line 808
    weight_aktuell_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)  # canonical line 809
    weight_aktuell_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)  # resolver return "kg"
    weight_aktuell_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)  # ORDER BY date DESC LIMIT 1
    weight_aktuell_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)  # return type numeric
    weight_aktuell_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)  # raw data, no interpretation
    weight_aktuell_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # f-string formatting
    weight_aktuell_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)  # canonical line 814
    weight_aktuell_metadata.set_evidence("minimum_data_requirements", EvidenceType.DRAFT_DERIVED)
    weight_aktuell_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    weight_aktuell_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)  # body_metrics.py:58-67
    weight_aktuell_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)  # resolver line 64
    weight_aktuell_metadata.set_evidence("known_limitations", EvidenceType.DRAFT_DERIVED)  # canonical line 837-840
    weight_aktuell_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    weight_aktuell_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    weight_aktuell_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    weight_aktuell_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    weight_aktuell_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)

    register_placeholder(weight_aktuell_metadata)

    # ── weight_trend ─────────────────────────────────────────────────────────

    weight_trend_metadata = PlaceholderMetadata(
        key="weight_trend",
        category="Körper",
        description="Gewichtstrend (28d)",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="get_weight_trend",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="get_weight_trend_data",
        source_tables=["weight_log"],

        # Semantic
        semantic_contract=(
            "Liefert den Gewichtstrend über ein 28-Tage-Fenster auf Basis einer einfachen "
            "Delta-Berechnung (erster vs. letzter Wert). Der Placeholder beschreibt Richtung "
            "und Veränderungstendenz des Gewichts über dieses Fenster."
        ),
        business_meaning="Verdichteter Gewichtsverlauf für Fortschritts- und Diagnoseaussagen",
        unit="kg over 28d",
        time_window="28d",
        output_type=OutputType.STRING,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="String: 'steigend (+2.3 kg in 28 Tagen)' | 'sinkend (-1.8 kg)' | 'stabil'",
        example_output="sinkend (-1.8 kg in 28 Tagen)",

        # Quality
        minimum_data_requirements=(
            "Minimum 8 valide Messpunkte (low confidence), "
            "sinnvoll 12+ (medium), stark belastbar 18+ (high)"
        ),
        quality_filter_policy="Ausreißer, Duplikate und technisch unplausible Werte sollen Trend nicht verzerren",
        confidence_logic=(
            "high: >= 18 points (28d), "
            "medium: >= 12 points, "
            "low: >= 8 points, "
            "insufficient: < 8 points"
        ),
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="insufficient_data",
            legacy_display="nicht genug Daten"
        ),
        known_limitations=(
            "ZEIT-INKONSISTENZ: Funktion akzeptiert days-Parameter (default 28d), "
            "aber PLACEHOLDER_MAP nutzt fixen default. "
            "Canonical fordert festes 28d-Fenster. "
            "STABILITÄT: abs(delta) < 0.3 kg → 'stabil' (hardcoded threshold). "
            "Delta-basiert, keine lineare Regression (siehe weight_28d_slope für Slope)."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.get_weight_trend_data) - berechnet delta + direction",
        layer_2a_decision="Placeholder Resolver (formatting: steigend/sinkend/stabil mit delta)",
        layer_2b_reuse_possible="Teilweise - delta + direction nutzbar, formatting Chart-spezifisch",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Layer-Trennung etabliert, aber Zeit-Parametrisierung unklar"
    )

    # Evidence tagging
    weight_trend_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    weight_trend_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    weight_trend_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 1084
    weight_trend_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    weight_trend_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:71
    weight_trend_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)  # SQL line 109
    weight_trend_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)  # draft + code shows delta calc
    weight_trend_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    weight_trend_metadata.set_evidence("unit", EvidenceType.MIXED)  # kg change over period
    weight_trend_metadata.set_evidence("time_window", EvidenceType.MIXED)  # canonical says 28d, code accepts parameter
    weight_trend_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)  # returns string
    weight_trend_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    weight_trend_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # resolver lines 84-89
    weight_trend_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
    weight_trend_metadata.set_evidence("minimum_data_requirements", EvidenceType.CODE_DERIVED)  # calculate_confidence
    weight_trend_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    weight_trend_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)  # body_metrics.py:117
    weight_trend_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)  # resolver line 79
    weight_trend_metadata.set_evidence("known_limitations", EvidenceType.MIXED)  # Zeit-Inkonsistenz + stability threshold
    weight_trend_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    weight_trend_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    weight_trend_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    weight_trend_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    weight_trend_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)

    register_placeholder(weight_trend_metadata)

    # ── weight_7d_median ─────────────────────────────────────────────────────

    weight_7d_median_metadata = PlaceholderMetadata(
        key="weight_7d_median",
        category="Körper",
        description="Gewicht 7d Median (kg)",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="_safe_float('weight_7d_median')",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="calculate_weight_7d_median",
        source_tables=["weight_log"],

        # Semantic
        semantic_contract=(
            "Liefert den Median der validen Gewichtsmessungen der letzten 7 Tage in Kilogramm. "
            "Der Wert dient als robuster Kurzfrist-Referenzwert und ist stabiler als ein Einzelmesspunkt."
        ),
        business_meaning="Kurzfristig geglätteter Gewichtsstatus für Status- und Verlaufsreports",
        unit="kg",
        time_window="7d",
        output_type=OutputType.NUMERIC,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="Dezimalzahl in kg (1 Dezimalstelle)",
        example_output="78.1",

        # Quality
        minimum_data_requirements="Minimum 4 valide Messungen (technical minimum 3), sinnvoll 5+",
        quality_filter_policy="Offensichtliche Ausreißer und technisch unplausible Werte ausschließen",
        confidence_logic=(
            "high: >= 5 valide Messungen, "
            "medium: 4 valide Messungen, "
            "low: 3 valide Messungen, "
            "insufficient: < 3 valide Messungen"
        ),
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="insufficient_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "MEDIAN-BERECHNUNG: statistics.median() - robust gegen Ausreißer. "
            "ROUNDING: 1 Dezimalstelle (round(median, 1)). "
            "Nur Kurzfristsicht, kein echter Trend. "
            "Bei sehr geringer Messdichte wenig belastbar."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.calculate_weight_7d_median)",
        layer_2a_decision="Placeholder Resolver (_safe_float wrapper für formatting)",
        layer_2b_reuse_possible="Ja - Median-Wert direkt nutzbar",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Layer-Trennung vollständig"
    )

    # Evidence tagging
    weight_7d_median_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    weight_7d_median_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    weight_7d_median_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # _safe_float line 486
    weight_7d_median_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    weight_7d_median_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:328
    weight_7d_median_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)  # SQL line 332
    weight_7d_median_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    weight_7d_median_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    weight_7d_median_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
    weight_7d_median_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)  # INTERVAL '7 days'
    weight_7d_median_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    weight_7d_median_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    weight_7d_median_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # round(median, 1)
    weight_7d_median_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    weight_7d_median_metadata.set_evidence("minimum_data_requirements", EvidenceType.CODE_DERIVED)  # len(weights) < 4
    weight_7d_median_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    weight_7d_median_metadata.set_evidence("confidence_logic", EvidenceType.DRAFT_DERIVED)  # from canonical
    weight_7d_median_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)  # _safe_float returns "nicht verfügbar"
    weight_7d_median_metadata.set_evidence("known_limitations", EvidenceType.MIXED)
    weight_7d_median_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    weight_7d_median_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    weight_7d_median_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    weight_7d_median_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    weight_7d_median_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)

    register_placeholder(weight_7d_median_metadata)

    # ── weight_28d_slope ─────────────────────────────────────────────────────

    weight_28d_slope_metadata = PlaceholderMetadata(
        key="weight_28d_slope",
        category="Körper",
        description="Gewichtstrend 28d (kg/Tag)",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="_safe_float('weight_28d_slope', decimals=4)",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="calculate_weight_28d_slope",
        source_tables=["weight_log"],

        # Semantic
        semantic_contract=(
            "Liefert die Steigung der linearen Regression der Gewichtswerte über 28 Tage. "
            "Der Wert beschreibt die durchschnittliche tägliche Änderungsrate des Gewichts (kg/Tag)."
        ),
        business_meaning="Feiner quantitativer Verlaufsindikator für Gewichtsentwicklung",
        unit="kg/day",
        time_window="28d",
        output_type=OutputType.NUMERIC,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="Dezimalzahl in kg/day (4 Dezimalstellen)",
        example_output="-0.026",

        # Quality
        minimum_data_requirements="Minimum 18 valide Messungen (60% coverage), sinnvoll 20+",
        quality_filter_policy="Unplausible Ausreißer und Dubletten bereinigen",
        confidence_logic=(
            "high: hohe Fensterabdeckung (>=18 von 28 Tagen), "
            "medium: mittlere Abdeckung, "
            "low: knapp nutzbar, "
            "insufficient: < 18 valide Messungen"
        ),
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="insufficient_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "LINEAR REGRESSION FORMEL: slope = Σ((x - x̄)(y - ȳ)) / Σ((x - x̄)²). "
            "x = days since start, y = weight values. "
            "MINIMUM DATA: max(18, int(28 * 0.6)) → 60% coverage required. "
            "KEINE AUSREISSER-BEHANDLUNG: Einfache lineare Regression ohne Robust-Methoden. "
            "ANNAHMEN: Linearer Trend, keine Beschleunigung. "
            "ROUNDING: 4 Dezimalstellen (round(slope, 4)). "
            "Sensibel gegenüber Messfrequenz und einzelnen extremen Messungen."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.calculate_weight_28d_slope → _calculate_weight_slope)",
        layer_2a_decision="Placeholder Resolver (_safe_float für formatting, 4 decimals)",
        layer_2b_reuse_possible="Ja - Slope-Wert direkt nutzbar für Trend-Charts",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Vollständige Layer-Trennung, Berechnung in Data Layer"
    )

    # Evidence tagging
    weight_28d_slope_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    weight_28d_slope_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    weight_28d_slope_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 488
    weight_28d_slope_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    weight_28d_slope_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:348
    weight_28d_slope_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)  # _calculate_weight_slope SQL
    weight_28d_slope_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    weight_28d_slope_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    weight_28d_slope_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)  # return kg/day
    weight_28d_slope_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)  # days=28
    weight_28d_slope_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    weight_28d_slope_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    weight_28d_slope_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # round(slope, 4)
    weight_28d_slope_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    weight_28d_slope_metadata.set_evidence("minimum_data_requirements", EvidenceType.CODE_DERIVED)  # max(18, int(days * 0.6))
    weight_28d_slope_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    weight_28d_slope_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)  # derived from min_points
    weight_28d_slope_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
    weight_28d_slope_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)  # formula from body_metrics.py:358-397
    weight_28d_slope_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    weight_28d_slope_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    weight_28d_slope_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    weight_28d_slope_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    weight_28d_slope_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)

    register_placeholder(weight_28d_slope_metadata)

    # ── weight_90d_slope ─────────────────────────────────────────────────────

    weight_90d_slope_metadata = PlaceholderMetadata(
        key="weight_90d_slope",
        category="Körper",
        description="Gewichtstrend 90d (kg/Tag)",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="_safe_float('weight_90d_slope', decimals=4)",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="calculate_weight_90d_slope",
        source_tables=["weight_log"],

        # Semantic
        semantic_contract=(
            "Liefert die Steigung der linearen Regression der Gewichtswerte über 90 Tage. "
            "Der Wert dient der langfristigen Einordnung und glättet kurzfristige Schwankungen stark."
        ),
        business_meaning="Langfristiger Verlaufsindikator für nachhaltige Gewichtsveränderung",
        unit="kg/day",
        time_window="90d",
        output_type=OutputType.NUMERIC,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="Dezimalzahl in kg/day (4 Dezimalstellen)",
        example_output="-0.011",

        # Quality
        minimum_data_requirements="Minimum 54 valide Messungen (60% coverage), sinnvoll 63+",
        quality_filter_policy="Ausreißer und technische Fehler filtern; lange Messlücken berücksichtigen",
        confidence_logic=(
            "high: hohe 90d-Abdeckung (>=54 Tage), "
            "medium: mittlere Abdeckung, "
            "low: knapp nutzbar, "
            "insufficient: < 54 valide Messungen"
        ),
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="insufficient_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "LINEAR REGRESSION: Gleiche Formel wie weight_28d_slope (siehe dort). "
            "MINIMUM DATA: max(18, int(90 * 0.6)) = 54 Messpunkte. "
            "LANGFRIST-CHARAKTERISTIK: Reagiert langsam auf neue Änderungen. "
            "NICHT für kurzfristige Interventionen geeignet. "
            "Saisonale Veränderungen oder Gewichtsphasenwechsel können Slope verzerren."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.calculate_weight_90d_slope → _calculate_weight_slope)",
        layer_2a_decision="Placeholder Resolver (_safe_float für formatting)",
        layer_2b_reuse_possible="Ja - Slope-Wert nutzbar für Langzeit-Trend-Charts",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Vollständige Layer-Trennung"
    )

    # Evidence tagging (same as weight_28d_slope, just different time window)
    weight_90d_slope_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 489
    weight_90d_slope_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:353
    weight_90d_slope_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    weight_90d_slope_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    weight_90d_slope_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)  # days=90
    weight_90d_slope_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    weight_90d_slope_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    weight_90d_slope_metadata.set_evidence("minimum_data_requirements", EvidenceType.CODE_DERIVED)  # 60% of 90d
    weight_90d_slope_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    weight_90d_slope_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)
    weight_90d_slope_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("known_limitations", EvidenceType.MIXED)
    weight_90d_slope_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    weight_90d_slope_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    weight_90d_slope_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)

    register_placeholder(weight_90d_slope_metadata)


    # ═════════════════════════════════════════════════════════════════════════
    # BODY COMPOSITION (5 Placeholders)
    # ═════════════════════════════════════════════════════════════════════════

    # ── kf_aktuell ───────────────────────────────────────────────────────────

    kf_aktuell_metadata = PlaceholderMetadata(
        key="kf_aktuell",
        category="Körper",
        description="Aktueller Körperfettanteil in %",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="get_latest_bf",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="get_body_composition_data",
        source_tables=["caliper_log"],

        # Semantic
        semantic_contract=(
            "Liefert den aktuellsten verfügbaren Körperfettwert in Prozent. "
            "Der Wert ist nur im Kontext der zugrunde liegenden Messmethode interpretierbar "
            "(Jackson-Pollock, Durnin-Womersley, etc.) und darf nicht als hochpräziser Laborwert "
            "dargestellt werden."
        ),
        business_meaning="Snapshot für Körperkomposition, Fortschrittsbewertung und Zielabgleich",
        unit="%",
        time_window="latest",
        output_type=OutputType.NUMERIC,
        placeholder_type=PlaceholderType.RAW_DATA,
        format_hint="Prozentwert (1 Dezimalstelle)",
        example_output="18.7",

        # Quality
        minimum_data_requirements="Mindestens 1 valider Körperfettmesspunkt (innerhalb 90d lookback)",
        quality_filter_policy="Ungültige, methodisch inkonsistente oder technisch fehlerhafte Werte ausschließen",
        confidence_logic=(
            "high: aktueller valider Messpunkt vorhanden und Methode klar definiert, "
            "medium: Messpunkt vorhanden, aber Messmethode begrenzt dokumentiert, "
            "insufficient: kein valider Wert vorhanden"
        ),
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="no_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "MESSMETHODEN-SENSITIVITÄT: Körperfettwerte sind abhängig von Caliper-Methode "
            "(sf_method: jackson_pollock, durnin_womersley, etc.). "
            "LOOKBACK: 90 Tage default (get_body_composition_data(days=90)). "
            "EINZELWERT-LIMITATION: Ohne Verlauf nur eingeschränkt aussagekräftig. "
            "Möglichst zusammen mit anderen Körperkompositions-Placeholdern nutzen (fm_28d_change, lbm_28d_change)."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.get_body_composition_data)",
        layer_2a_decision="Placeholder Resolver (formatting: f'{body_fat_pct:.1f}%')",
        layer_2b_reuse_possible="Ja - body_fat_pct direkt nutzbar",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Layer-Trennung vollständig"
    )

    # Evidence tagging
    kf_aktuell_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    kf_aktuell_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    kf_aktuell_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 1085
    kf_aktuell_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    kf_aktuell_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:159
    kf_aktuell_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)  # SQL line 188
    kf_aktuell_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    kf_aktuell_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    kf_aktuell_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)  # returns %
    kf_aktuell_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)  # ORDER BY date DESC LIMIT 1
    kf_aktuell_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    kf_aktuell_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    kf_aktuell_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # f-string with .1f
    kf_aktuell_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    kf_aktuell_metadata.set_evidence("minimum_data_requirements", EvidenceType.MIXED)
    kf_aktuell_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    kf_aktuell_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)
    kf_aktuell_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)  # resolver line 102
    kf_aktuell_metadata.set_evidence("known_limitations", EvidenceType.MIXED)
    kf_aktuell_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    kf_aktuell_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    kf_aktuell_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    kf_aktuell_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    kf_aktuell_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)

    register_placeholder(kf_aktuell_metadata)

    # ── fm_28d_change ────────────────────────────────────────────────────────

    fm_28d_change_metadata = PlaceholderMetadata(
        key="fm_28d_change",
        category="Körper",
        description="Fettmasse Änderung 28d (kg)",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="_safe_float('fm_28d_change', decimals=2)",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="calculate_fm_28d_change",
        source_tables=["weight_log", "caliper_log"],

        # Semantic
        semantic_contract=(
            "Liefert die Veränderung der Fettmasse in Kilogramm über 28 Tage. "
            "Negative Werte bedeuten Reduktion der Fettmasse, positive Werte Zunahme."
        ),
        business_meaning="Kernindikator für Fettabbau bzw. Fettzunahme",
        unit="kg",
        time_window="28d",
        output_type=OutputType.NUMERIC,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="Delta in kg (2 Dezimalstellen)",
        example_output="-1.4",

        # Quality
        minimum_data_requirements=(
            "Mindestens 2 Einträge mit SOWOHL weight ALS AUCH body_fat_pct am SELBEN Datum. "
            "Sinnvoll: wiederholte Messungen mit ausreichender Abdeckung im 28d-Fenster."
        ),
        quality_filter_policy="Methodenwechsel, inkonsistente Messserien oder unplausible BF-Werte berücksichtigen",
        confidence_logic=(
            "high: wiederholte konsistente Messungen, "
            "medium: ausreichende aber nicht dichte Messung, "
            "low: knapper Datenbasis, "
            "insufficient: < 2 brauchbare Vergleichspunkte"
        ),
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="insufficient_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "FM-BERECHNUNG: FM = weight_kg × (body_fat_pct / 100). "
            "DELTA: recent_fm - oldest_fm (most recent vs oldest in 28d window). "
            "SAME-DATE REQUIREMENT: Erfordert weight + caliper Messungen am SELBEN Tag (LEFT JOIN). "
            "MESSMETHODEN-ABHÄNGIGKEIT: Body Fat % ist methodensensitiv. "
            "Methodenwechsel (z.B. Jackson-Pollock → Durnin-Womersley) verzerrt FM-Deltas. "
            "ROUNDING: 2 Dezimalstellen (round(change, 2)). "
            "BF-Messmethodenwechsel, stark schwankender BF-Wert, fehlender korrespondierender Gewichtswert sind Edge Cases."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.calculate_fm_28d_change → _calculate_body_composition_change)",
        layer_2a_decision="Placeholder Resolver (_safe_float für formatting)",
        layer_2b_reuse_possible="Ja - FM Delta direkt nutzbar für Composition-Charts",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Layer-Trennung vollständig"
    )

    # Evidence tagging
    fm_28d_change_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    fm_28d_change_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    fm_28d_change_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 489
    fm_28d_change_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    fm_28d_change_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:443
    fm_28d_change_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)  # SQL LEFT JOIN line 462
    fm_28d_change_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    fm_28d_change_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    fm_28d_change_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
    fm_28d_change_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)  # days=28
    fm_28d_change_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    fm_28d_change_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    fm_28d_change_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # round(change, 2)
    fm_28d_change_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    fm_28d_change_metadata.set_evidence("minimum_data_requirements", EvidenceType.CODE_DERIVED)  # len(data) < 2
    fm_28d_change_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    fm_28d_change_metadata.set_evidence("confidence_logic", EvidenceType.DRAFT_DERIVED)
    fm_28d_change_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
    fm_28d_change_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)  # formula from body_metrics.py:453-501
    fm_28d_change_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    fm_28d_change_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    fm_28d_change_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    fm_28d_change_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    fm_28d_change_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)

    register_placeholder(fm_28d_change_metadata)

    # ── lbm_28d_change ───────────────────────────────────────────────────────

    lbm_28d_change_metadata = PlaceholderMetadata(
        key="lbm_28d_change",
        category="Körper",
        description="Magermasse Änderung 28d (kg)",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="_safe_float('lbm_28d_change', decimals=2)",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="calculate_lbm_28d_change",
        source_tables=["weight_log", "caliper_log"],

        # Semantic
        semantic_contract=(
            "Liefert die Veränderung der Magermasse in Kilogramm über 28 Tage. "
            "Positive Werte bedeuten Zunahme, negative Werte Verlust."
        ),
        business_meaning="Kernindikator für Muskelerhalt / Muskelaufbau im Verlauf",
        unit="kg",
        time_window="28d",
        output_type=OutputType.NUMERIC,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="Delta in kg (2 Dezimalstellen)",
        example_output="+0.3",

        # Quality
        minimum_data_requirements="Analog zu fm_28d_change: Min 2 Einträge mit weight + body_fat_pct am selben Datum",
        quality_filter_policy="Messfehler und methodische Inkonsistenzen berücksichtigen",
        confidence_logic="Analog zu fm_28d_change",
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="insufficient_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "LBM-BERECHNUNG: LBM = weight_kg - FM = weight_kg - (weight_kg × body_fat_pct / 100). "
            "DELTA: recent_lbm - oldest_lbm. "
            "SAME-DATE REQUIREMENT: Erfordert weight + caliper am SELBEN Tag. "
            "INDIREKT ABGELEITET: LBM ist meist indirekt (weight - FM), nicht direkt gemessen. "
            "MESSMETHODEN-SENSITIVITÄT: Änderungen in BF-Messmethode beeinflussen LBM. "
            "ÜBERSCHÄTZUNGSRISIKO: Ohne gute Messbasis leicht überschätzt. "
            "ROUNDING: 2 Dezimalstellen. "
            "Rechnerische Artefakte möglich wenn BF-/Gewichtskombination unstabil."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.calculate_lbm_28d_change → _calculate_body_composition_change)",
        layer_2a_decision="Placeholder Resolver (_safe_float für formatting)",
        layer_2b_reuse_possible="Ja - LBM Delta direkt nutzbar",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Layer-Trennung vollständig"
    )

    # Evidence tagging
    lbm_28d_change_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 490
    lbm_28d_change_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:448
    lbm_28d_change_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    lbm_28d_change_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    lbm_28d_change_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    lbm_28d_change_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    lbm_28d_change_metadata.set_evidence("minimum_data_requirements", EvidenceType.MIXED)
    lbm_28d_change_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    lbm_28d_change_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)
    lbm_28d_change_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    lbm_28d_change_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    lbm_28d_change_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)

    register_placeholder(lbm_28d_change_metadata)


    # ── waist_hip_ratio ──────────────────────────────────────────────────────

    waist_hip_ratio_metadata = PlaceholderMetadata(
        key="waist_hip_ratio",
        category="Körper",
        description="Taille/Hüfte-Verhältnis",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="_safe_float('waist_hip_ratio', decimals=3)",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="calculate_waist_hip_ratio",
        source_tables=["circumference_log"],

        # Semantic
        semantic_contract=(
            "Liefert das Verhältnis aus aktuellem Taillen- und Hüftumfang als dimensionslosen Quotienten."
        ),
        business_meaning="Ergänzender Form- und Verteilungsindikator",
        unit="ratio",
        time_window="latest",
        output_type=OutputType.NUMERIC,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="Quotient (3 Dezimalstellen), z.B. 0.91",
        example_output="0.89",

        # Quality
        minimum_data_requirements="Je 1 valider aktueller Taillen- UND Hüftwert (auf selben Datum)",
        quality_filter_policy="Null-/Fehlwerte und unplausible Umfänge ausschließen",
        confidence_logic=(
            "high: beide Maße aktuell und konsistent vorhanden, "
            "insufficient: eines fehlt"
        ),
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="insufficient_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "BERECHNUNG: ratio = c_waist / c_hip. "
            "SAME-DATE REQUIREMENT: Erfordert BEIDE Messungen auf SELBEN Datum "
            "(latest entry WHERE c_waist IS NOT NULL AND c_hip IS NOT NULL). "
            "ROUNDING: 3 Dezimalstellen (round(ratio, 3)). "
            "ERGÄNZENDER INDIKATOR: Nicht zentraler Fortschrittsindikator. "
            "Ohne konsistente Umfangsmessung wenig wertvoll. "
            "Division durch 0 verhindert durch SQL WHERE c_hip IS NOT NULL."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.calculate_waist_hip_ratio)",
        layer_2a_decision="Placeholder Resolver (_safe_float für formatting)",
        layer_2b_reuse_possible="Ja - Ratio direkt nutzbar",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Layer-Trennung vollständig"
    )

    # Evidence tagging
    waist_hip_ratio_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 496
    waist_hip_ratio_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:570
    waist_hip_ratio_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)  # SQL line 574
    waist_hip_ratio_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    waist_hip_ratio_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    waist_hip_ratio_metadata.set_evidence("unit", EvidenceType.MIXED)  # ratio is dimensionless
    waist_hip_ratio_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)  # ORDER BY date DESC LIMIT 1
    waist_hip_ratio_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    waist_hip_ratio_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # round(ratio, 3)
    waist_hip_ratio_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    waist_hip_ratio_metadata.set_evidence("minimum_data_requirements", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    waist_hip_ratio_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)
    waist_hip_ratio_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    waist_hip_ratio_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    waist_hip_ratio_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)

    register_placeholder(waist_hip_ratio_metadata)

    # ── recomposition_quadrant ───────────────────────────────────────────────

    recomposition_quadrant_metadata = PlaceholderMetadata(
        key="recomposition_quadrant",
        category="Körper",
        description="Rekomposition-Status",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="_safe_string('recomposition_quadrant')",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="calculate_recomposition_quadrant",
        source_tables=["weight_log", "caliper_log"],

        # Semantic
        semantic_contract=(
            "Klassifiziert die 28d-Körperentwicklung anhand von Fettmassen- und Magermassenveränderung "
            "in einen fachlich definierten Quadranten bzw. Statusraum."
        ),
        business_meaning="Hochwertiger Diagnose- und Syntheseindikator für Recomposition",
        unit="category",
        time_window="28d",
        output_type=OutputType.STRING,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="String: optimal | cut_with_risk | bulk | unfavorable",
        example_output="optimal",

        # Quality
        minimum_data_requirements="Belastbare Verfügbarkeit von fm_28d_change UND lbm_28d_change",
        quality_filter_policy=(
            "Wenn die zugrunde liegenden Deltas unzureichend oder methodisch unsicher sind, "
            "darf keine harte Quadrantenklassifikation erfolgen"
        ),
        confidence_logic=(
            "Aus den Confidence-Werten von fm_28d_change und lbm_28d_change ableiten (Minimum-Prinzip)"
        ),
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="insufficient_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "QUADRANTEN-LOGIK (pure sign-based):\n"
            "  FM < 0, LBM > 0 → 'optimal' (Fett ab, Muskel auf - Recomposition)\n"
            "  FM < 0, LBM < 0 → 'cut_with_risk' (Fett ab, Muskel ab - aggressiver Cut)\n"
            "  FM > 0, LBM > 0 → 'bulk' (Fett auf, Muskel auf - Bulking-Phase)\n"
            "  FM > 0, LBM < 0 → 'unfavorable' (Fett auf, Muskel ab - worst case)\n\n"
            "SIGN-BASED THRESHOLDS: Rein basierend auf Vorzeichen (< 0 vs. > 0). "
            "KEINE TOLERANZ-ZONE: Kleine Änderungen nahe 0 werden als signifikant behandelt. "
            "KEINE 'STABLE' KATEGORIE: Changes near 0 können Quadrant-Flips verursachen. "
            "MESSMETHODEN-ABHÄNGIGKEIT: Stark abhängig von Qualität der Körperkompositionsdaten. "
            "Methodenwechsel bei BF-Messung kann Quadranten verzerren."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.calculate_recomposition_quadrant)",
        layer_2a_decision="Placeholder Resolver (_safe_string für formatting)",
        layer_2b_reuse_possible="Ja - Quadrant category direkt nutzbar",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Layer-Trennung vollständig"
    )

    # Evidence tagging
    recomposition_quadrant_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    recomposition_quadrant_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    recomposition_quadrant_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # _safe_string mapping
    recomposition_quadrant_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    recomposition_quadrant_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:594
    recomposition_quadrant_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)  # via fm/lbm functions
    recomposition_quadrant_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    recomposition_quadrant_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    recomposition_quadrant_metadata.set_evidence("unit", EvidenceType.MIXED)
    recomposition_quadrant_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)  # 28d via fm/lbm
    recomposition_quadrant_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    recomposition_quadrant_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    recomposition_quadrant_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # return strings line 606-615
    recomposition_quadrant_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
    recomposition_quadrant_metadata.set_evidence("minimum_data_requirements", EvidenceType.MIXED)
    recomposition_quadrant_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    recomposition_quadrant_metadata.set_evidence("confidence_logic", EvidenceType.DRAFT_DERIVED)
    recomposition_quadrant_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
    recomposition_quadrant_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)  # logic from body_metrics.py:594-616
    recomposition_quadrant_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    recomposition_quadrant_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    recomposition_quadrant_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    recomposition_quadrant_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    recomposition_quadrant_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)

    register_placeholder(recomposition_quadrant_metadata)


    # ═════════════════════════════════════════════════════════════════════════
    # CIRCUMFERENCE DELTAS (5 Placeholders)
    # ═════════════════════════════════════════════════════════════════════════

    # Common metadata for all 5 circumference deltas
    circumference_delta_common = {
        "category": "Körper",
        "resolver_module": "backend/placeholder_resolver.py",
        "data_layer_module": "backend/data_layer/body_metrics.py",
        "source_tables": ["circumference_log"],
        "time_window": "28d",
        "output_type": OutputType.NUMERIC,
        "placeholder_type": PlaceholderType.INTERPRETED,
        "format_hint": "Delta in cm (1 Dezimalstelle)",
        "minimum_data_requirements": (
            "Mindestens 2 valide Messungen: "
            "1 im 28d-Fenster (most recent), 1 VOR dem Fenster (oldest before window)"
        ),
        "quality_filter_policy": "Messfehler, Ausreißer und inkonsistente Einträge berücksichtigen",
        "confidence_logic": (
            "high: mehrere konsistente Messpunkte im 28d-Fenster, "
            "medium: Mindestanforderung knapp erfüllt, "
            "low: Daten formal reichen aber dünn, "
            "insufficient: < 2 valide Messpunkte"
        ),
        "missing_value_policy": MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="insufficient_data",
            legacy_display="nicht verfügbar"
        ),
        "known_limitations": (
            "REFERENZLOGIK (KRITISCH): Vergleicht aktuellsten Wert IM 28d-Fenster "
            "mit ältestem Wert VOR dem 28d-Fenster. "
            "NICHT: Delta zwischen Start und Ende des Fensters. "
            "SQL: recent = latest WHERE date >= CURRENT_DATE - 28d, "
            "oldest = latest WHERE date < CURRENT_DATE - 28d. "
            "SPAN kann > 28d sein bei lückenhaften Messungen. "
            "Beispiel: Messung heute + Messung vor 40 Tagen → Delta span = 40d. "
            "ROUNDING: 1 Dezimalstelle (round(change, 1)). "
            "Bei sehr unregelmäßigen Messungen kann dies zu irreführenden Deltas führen."
        ),
        "layer_1_decision": "Data Layer (body_metrics._calculate_circumference_delta)",
        "layer_2a_decision": "Placeholder Resolver (_safe_float für formatting)",
        "layer_2b_reuse_possible": "Ja - Delta-Wert direkt nutzbar",
        "architecture_alignment": "Phase 0c conform",
        "issue_53_alignment": "Layer-Trennung vollständig"
    }

    # Common evidence for circumference deltas
    circ_delta_evidence = {
        "category": EvidenceType.CODE_DERIVED,
        "resolver_module": EvidenceType.CODE_DERIVED,
        "data_layer_module": EvidenceType.CODE_DERIVED,
        "source_tables": EvidenceType.CODE_DERIVED,
        "time_window": EvidenceType.CODE_DERIVED,
        "output_type": EvidenceType.CODE_DERIVED,
        "placeholder_type": EvidenceType.MIXED,
        "format_hint": EvidenceType.CODE_DERIVED,
        "minimum_data_requirements": EvidenceType.CODE_DERIVED,  # SQL shows 2-query pattern
        "quality_filter_policy": EvidenceType.DRAFT_DERIVED,
        "confidence_logic": EvidenceType.DRAFT_DERIVED,
        "missing_value_policy": EvidenceType.CODE_DERIVED,
        "known_limitations": EvidenceType.CODE_DERIVED,  # from _calculate_circumference_delta body_metrics.py:534
        "layer_1_decision": EvidenceType.CODE_DERIVED,
        "layer_2a_decision": EvidenceType.CODE_DERIVED,
        "layer_2b_reuse_possible": EvidenceType.TO_VERIFY,
        "architecture_alignment": EvidenceType.CODE_DERIVED,
        "issue_53_alignment": EvidenceType.CODE_DERIVED,
    }

    # ── waist_28d_delta ──────────────────────────────────────────────────────

    waist_28d_delta_metadata = PlaceholderMetadata(
        key="waist_28d_delta",
        description="Taillenumfang Änderung 28d (cm)",
        resolver_function="_safe_float('waist_28d_delta', decimals=1)",
        data_layer_function="calculate_waist_28d_delta",
        semantic_contract=(
            "Liefert die Veränderung des Taillenumfangs in Zentimetern über 28 Tage. "
            "Negative Werte bedeuten Reduktion, positive Werte Zunahme."
        ),
        business_meaning="Zentraler Delta-Indikator für Körperfett-/Körperformveränderung",
        unit="cm",
        example_output="-2.1",
        **circumference_delta_common
    )
    waist_28d_delta_metadata.evidence.update(circ_delta_evidence)
    waist_28d_delta_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 491
    waist_28d_delta_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:506
    waist_28d_delta_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    waist_28d_delta_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    waist_28d_delta_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
    waist_28d_delta_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    register_placeholder(waist_28d_delta_metadata)

    # ── arm_28d_delta ────────────────────────────────────────────────────────

    arm_28d_delta_metadata = PlaceholderMetadata(
        key="arm_28d_delta",
        description="Armumfang Änderung 28d (cm)",
        resolver_function="_safe_float('arm_28d_delta', decimals=1)",
        data_layer_function="calculate_arm_28d_delta",
        semantic_contract=(
            "Liefert die Veränderung des Armumfangs in Zentimetern über 28 Tage. "
            "Positive Werte bedeuten Zunahme, negative Werte Reduktion."
        ),
        business_meaning="Ergänzender Umfangsindikator für detaillierte Körperentwicklungsanalysen",
        unit="cm",
        example_output="+0.6",
        **circumference_delta_common
    )
    arm_28d_delta_metadata.evidence.update(circ_delta_evidence)
    arm_28d_delta_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 494
    arm_28d_delta_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:521
    arm_28d_delta_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    arm_28d_delta_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    arm_28d_delta_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
    arm_28d_delta_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    register_placeholder(arm_28d_delta_metadata)

    # ── chest_28d_delta ──────────────────────────────────────────────────────

    chest_28d_delta_metadata = PlaceholderMetadata(
        key="chest_28d_delta",
        description="Brustumfang Änderung 28d (cm)",
        resolver_function="_safe_float('chest_28d_delta', decimals=1)",
        data_layer_function="calculate_chest_28d_delta",
        semantic_contract=(
            "Liefert die Veränderung des Brustumfangs in Zentimetern über 28 Tage. "
            "Positive Werte bedeuten Zunahme, negative Werte Reduktion."
        ),
        business_meaning="Ergänzender Umfangsindikator für Oberkörper-Entwicklungsanalysen",
        unit="cm",
        example_output="+0.4",
        **circumference_delta_common
    )
    chest_28d_delta_metadata.evidence.update(circ_delta_evidence)
    chest_28d_delta_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 493
    chest_28d_delta_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:516
    chest_28d_delta_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    chest_28d_delta_metadata.set_evidence("business_meaning", EvidenceType.MIXED)
    chest_28d_delta_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
    chest_28d_delta_metadata.set_evidence("example_output", EvidenceType.MIXED)
    register_placeholder(chest_28d_delta_metadata)

    # ── hip_28d_delta ────────────────────────────────────────────────────────

    hip_28d_delta_metadata = PlaceholderMetadata(
        key="hip_28d_delta",
        description="Hüftumfang Änderung 28d (cm)",
        resolver_function="_safe_float('hip_28d_delta', decimals=1)",
        data_layer_function="calculate_hip_28d_delta",
        semantic_contract=(
            "Liefert die Veränderung des Hüftumfangs in Zentimetern über 28 Tage. "
            "Positive Werte bedeuten Zunahme, negative Werte Reduktion."
        ),
        business_meaning="Ergänzender Umfangsindikator für Unterkörper-Entwicklungsanalysen",
        unit="cm",
        example_output="-0.8",
        **circumference_delta_common
    )
    hip_28d_delta_metadata.evidence.update(circ_delta_evidence)
    hip_28d_delta_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 492
    hip_28d_delta_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:511
    hip_28d_delta_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    hip_28d_delta_metadata.set_evidence("business_meaning", EvidenceType.MIXED)
    hip_28d_delta_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
    hip_28d_delta_metadata.set_evidence("example_output", EvidenceType.DRAFT_DERIVED)
    register_placeholder(hip_28d_delta_metadata)

    # ── thigh_28d_delta ──────────────────────────────────────────────────────

    thigh_28d_delta_metadata = PlaceholderMetadata(
        key="thigh_28d_delta",
        description="Oberschenkelumfang Änderung 28d (cm)",
        resolver_function="_safe_float('thigh_28d_delta', decimals=1)",
        data_layer_function="calculate_thigh_28d_delta",
        semantic_contract=(
            "Liefert die Veränderung des Oberschenkelumfangs in Zentimetern über 28 Tage. "
            "Positive Werte bedeuten Zunahme, negative Werte Reduktion."
        ),
        business_meaning="Ergänzender Umfangsindikator für Beinmuskulatur-Entwicklung",
        unit="cm",
        example_output="+0.3",
        **circumference_delta_common
    )
    thigh_28d_delta_metadata.evidence.update(circ_delta_evidence)
    thigh_28d_delta_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 495
    thigh_28d_delta_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:526
    thigh_28d_delta_metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED)
    thigh_28d_delta_metadata.set_evidence("business_meaning", EvidenceType.MIXED)
    thigh_28d_delta_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
    thigh_28d_delta_metadata.set_evidence("example_output", EvidenceType.MIXED)
    register_placeholder(thigh_28d_delta_metadata)

    # ═════════════════════════════════════════════════════════════════════════
    # SUMMARIES (2 Placeholders)
    # ═════════════════════════════════════════════════════════════════════════

    # ── caliper_summary ──────────────────────────────────────────────────────

    caliper_summary_metadata = PlaceholderMetadata(
        key="caliper_summary",
        category="Körper",
        description="Caliper/Körperfett-Zusammenfassung",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="get_caliper_summary",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="get_body_composition_data",
        source_tables=["caliper_log"],

        # Semantic
        semantic_contract=(
            "Liefert eine strukturierte textuelle Zusammenfassung der verfügbaren "
            "Caliper-bezogenen Körperfettinformationen. Format: 'KF% (Methode am Datum)'."
        ),
        business_meaning="Bequemer High-Level-Einstieg für narrative Körperreports",
        unit="text",
        time_window="latest",
        output_type=OutputType.TEXT_SUMMARY,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="String: '18.7% (jackson_pollock am 2026-03-30)'",
        example_output="18.7% (jackson_pollock am 2026-03-30)",

        # Quality
        minimum_data_requirements="Mindestens 1 valider Caliper-/BF-Bezug für Snapshot-Teil",
        quality_filter_policy="Unsichere Bestandteile müssen kenntlich gemacht oder ausgelassen werden",
        confidence_logic="Abgeleitet aus get_body_composition_data (high wenn vorhanden)",
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="no_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "TEXT-FORMAT: Aktuell Freitext-String, nicht strukturiertes JSON. "
            "Canonical empfiehlt: Strukturierte JSON-Summary statt Freitext. "
            "NO-CHANGE REGEL: Format bleibt als Text-String erhalten (wie implementiert). "
            "LOOKBACK: Nutzt get_body_composition_data mit 90d lookback. "
            "FORMAT: f'{body_fat_pct:.1f}% ({method} am {date})'. "
            "Als Freitext weniger stabil für Multi-Stage-Prompts. "
            "Gefahr von semantischer Unschärfe bei komplexeren Auswertungen."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.get_body_composition_data)",
        layer_2a_decision="Placeholder Resolver (formatting zu Text-String)",
        layer_2b_reuse_possible="Teilweise - besser strukturiert für Charts",
        architecture_alignment="Phase 0c conform (nutzt Data Layer)",
        issue_53_alignment="Layer-Trennung etabliert, aber Text-Summary suboptimal"
    )

    # Evidence tagging
    caliper_summary_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 1087
    caliper_summary_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)
    caliper_summary_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    caliper_summary_metadata.set_evidence("unit", EvidenceType.MIXED)
    caliper_summary_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    caliper_summary_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # resolver line 148
    caliper_summary_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("minimum_data_requirements", EvidenceType.MIXED)
    caliper_summary_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    caliper_summary_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)
    caliper_summary_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("known_limitations", EvidenceType.MIXED)
    caliper_summary_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    caliper_summary_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    caliper_summary_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)

    register_placeholder(caliper_summary_metadata)

    # ── circ_summary ─────────────────────────────────────────────────────────

    circ_summary_metadata = PlaceholderMetadata(
        key="circ_summary",
        category="Körper",
        description="Umfangs-Zusammenfassung",

        # Technical
        resolver_module="backend/placeholder_resolver.py",
        resolver_function="get_circ_summary",
        data_layer_module="backend/data_layer/body_metrics.py",
        data_layer_function="get_circumference_summary_data",
        source_tables=["circumference_log"],

        # Semantic
        semantic_contract=(
            "Liefert eine standardisierte Zusammenfassung der relevanten Umfangsdaten. "
            "Verwendet BEST-OF-EACH Strategie: Jeder Messpunkt zeigt seinen individuell "
            "aktuellsten Wert (max 90d alt). Format: 'Punkt Wert cm (vor X Tagen), ...'"
        ),
        business_meaning="High-Level-Kontext für Körperform- und Umfangsverlauf",
        unit="text",
        time_window="mixed",
        output_type=OutputType.TEXT_SUMMARY,
        placeholder_type=PlaceholderType.INTERPRETED,
        format_hint="String mit allen verfügbaren Messpunkten und Alter",
        example_output="Nacken 38.0cm (vor 2 Tagen), Taille 85.2cm (vor 5 Tagen)",

        # Quality
        minimum_data_requirements="Je nach enthaltenen Teilwerten (mindestens 1 Messpunkt)",
        quality_filter_policy="Ausreißer und inkonsistente Messserien berücksichtigen",
        confidence_logic="Aus calculate_confidence(len(measurements), 8, 'general') - je mehr Punkte desto höher",
        missing_value_policy=MissingValuePolicy(
            available=False,
            value_raw=None,
            missing_reason="no_data",
            legacy_display="nicht verfügbar"
        ),
        known_limitations=(
            "BEST-OF-EACH STRATEGIE (KRITISCH): Jeder Umfangspunkt zeigt seinen individuell "
            "aktuellsten Wert innerhalb 90d. "
            "ERGEBNIS: Messungen sind NICHT notwendigerweise vom selben Datum. "
            "Beispiel: Nacken vor 2 Tagen, Taille vor 5 Tagen, Hüfte vor 10 Tagen. "
            "NICHT: Konsistenter Snapshot. "
            "FÜR KONSISTENTE VERGLEICHE: Einzel-Deltas (waist_28d_delta etc.) nutzen. "
            "TEXT-FORMAT: Freitext-String statt strukturiertes JSON. "
            "Canonical empfiehlt: Strukturierte Summary. "
            "NO-CHANGE REGEL: Format bleibt Text-String (wie implementiert). "
            "Als Summary weniger präzise als einzelne Delta-Placeholder."
        ),

        # Architecture
        layer_1_decision="Data Layer (body_metrics.get_circumference_summary_data) - Best-of-Each Logic",
        layer_2a_decision="Placeholder Resolver (formatting zu Text-String mit Altersangaben)",
        layer_2b_reuse_possible="Teilweise - strukturierte Daten besser für Charts",
        architecture_alignment="Phase 0c conform",
        issue_53_alignment="Layer-Trennung etabliert, aber Text-Summary suboptimal"
    )

    # Evidence tagging
    circ_summary_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
    circ_summary_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
    circ_summary_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)  # line 1088
    circ_summary_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
    circ_summary_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)  # body_metrics.py:217
    circ_summary_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
    circ_summary_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)  # Best-of-Each from code
    circ_summary_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
    circ_summary_metadata.set_evidence("unit", EvidenceType.MIXED)
    circ_summary_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)  # Best-of-Each = mixed
    circ_summary_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
    circ_summary_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
    circ_summary_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)  # resolver formatting
    circ_summary_metadata.set_evidence("example_output", EvidenceType.MIXED)
    circ_summary_metadata.set_evidence("minimum_data_requirements", EvidenceType.MIXED)
    circ_summary_metadata.set_evidence("quality_filter_policy", EvidenceType.DRAFT_DERIVED)
    circ_summary_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)  # body_metrics.py:296
    circ_summary_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
    circ_summary_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)  # Best-of-Each logic critical
    circ_summary_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
    circ_summary_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
    circ_summary_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
    circ_summary_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
    circ_summary_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)

    register_placeholder(circ_summary_metadata)


# Auto-register when module is imported
register_body_metrics()
