mitai-jinkendo/backend/placeholder_registrations/body_metrics.py
Lars 57800b686a
All checks were successful
Deploy Development / deploy (push) Successful in 52s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s
fix: Body Cluster - PlaceholderType.TEXT_SUMMARY → INTERPRETED
- caliper_summary + circ_summary used invalid PlaceholderType.TEXT_SUMMARY
- TEXT_SUMMARY is OutputType, not PlaceholderType
- Changed to PlaceholderType.INTERPRETED (summaries interpret raw data)

Valid PlaceholderType values: ATOMIC, RAW_DATA, INTERPRETED, SCORE, META
Valid OutputType values: NUMERIC, STRING, BOOLEAN, JSON, LIST, TEXT_SUMMARY
2026-04-02 19:11:06 +02:00

1308 lines
75 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Body Metrics Placeholder Registrations
Registers 17 body composition and measurement placeholders:
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.
Code inspection: backend/data_layer/body_metrics.py (830 lines)
"""
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()