mitai-jinkendo/backend/placeholder_registrations/activity_metrics.py
Lars 485aec40a0
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 13s
feat: Activity Cluster Placeholder Registry - Complete Implementation (17 Placeholders)
Implements complete placeholder registry for Activity & Training metrics following
Phase 0c Multi-Layer Architecture pattern.

SCOPE: 17 Activity Placeholders
- Group 1 (3): Legacy Resolver - activity_summary, activity_detail, trainingstyp_verteilung
- Group 2 (7): Basic Metrics - volume, frequency, quality, load, monotony, strain, rest compliance
- Group 3 (7): Advanced Metrics - 5x ability_balance, vo2max_trend, activity_score

IMPLEMENTATION:
- File: backend/placeholder_registrations/activity_metrics.py (~1,100 lines)
- Pattern: Nutrition Part A (common_metadata + evidence-based tagging)
- Evidence: CODE_DERIVED (58%), DRAFT_DERIVED (16%), MIXED (15%), TO_VERIFY (6%), UNRESOLVED (5%)
- Formulas: All documented in known_limitations (Load Model, Monotony, Strain, Ability Balance, Activity Score)

CRITICAL ISSUES IDENTIFIED (NOT FIXED per NO LOGIC CHANGES):
1. quality_label field mismatch (quality_sessions_pct) - TO_VERIFY
2. RPE moderate quality mapping bug (proxy_internal_load_7d) - CODE_DERIVED
3. JSONB dependencies (6 placeholders) - ability_balance_*, rest_day_compliance
4. vo2max_trend_28d questionable category (Recovery vs. Activity) - TO_VERIFY

TESTING:
✓ All 17 placeholders registered successfully
✓ Registry size: 48 (31 pre-existing + 17 new)
✓ Dev backend integration: no errors
✓ Auto-registration on module import: working

ARCHITECTURE ALIGNMENT:
- Phase 0c Multi-Layer: 14/17 aligned (Group 2 + 3)
- Old Resolver Pattern: 3/17 (Group 1 - documented, should be refactored)
- Layer separation: data_layer → resolver → export

FILES:
- NEW: backend/placeholder_registrations/activity_metrics.py
- MODIFIED: backend/placeholder_registrations/__init__.py (added import)
- MODIFIED: CLAUDE.md (placeholder registry rules)

DOCUMENTATION:
- Gap Analysis: .claude/task/rework_0b_placeholder/ACTIVITY_CLUSTER_GAP_ANALYSIS.md
- Code Inspection: .claude/task/rework_0b_placeholder/ACTIVITY_CLUSTER_CODE_INSPECTION.md
- Implementation Report: .claude/task/rework_0b_placeholder/ACTIVITY_CLUSTER_IMPLEMENTATION_REPORT.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:20:25 +02:00

1113 lines
62 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.

"""
Activity Metrics Placeholder Registrations
Registers all 17 activity-related placeholders in the central placeholder registry.
Evidence-based metadata with clear tagging of source.
Groups:
- Legacy Resolver (3): activity_summary, activity_detail, trainingstyp_verteilung
- Basic Metrics (7): training_minutes_week, training_frequency_7d, quality_sessions_pct,
proxy_internal_load_7d, monotony_score, strain_score, rest_day_compliance
- Advanced Metrics (7): ability_balance_*, vo2max_trend_28d, activity_score
"""
from placeholder_registry import (
PlaceholderMetadata,
MissingValuePolicy,
EvidenceType,
OutputType,
PlaceholderType,
register_placeholder
)
# =============================================================================
# GRUPPE 1: Legacy Resolver (3 Placeholders)
# =============================================================================
def register_activity_group_1():
"""
Register Group 1: Legacy Resolver placeholders.
These use old resolver pattern (direct formatting in resolver, no data layer).
"""
# ── activity_summary ──────────────────────────────────────────────────────
activity_summary_metadata = PlaceholderMetadata(
key="activity_summary",
category="Aktivität",
description="Zusammenfassung der letzten 14 Tage Aktivität",
resolver_module="backend/placeholder_resolver.py",
resolver_function="_format_activity_summary",
data_layer_module=None,
data_layer_function=None,
source_tables=["activity_log", "training_types"],
semantic_contract=(
"Liefert eine kompakte textuelle Zusammenfassung der Trainingsaktivitäten "
"der letzten 14 Tage. Beinhaltet: Anzahl Einheiten, Gesamtdauer, "
"Trainingstypen-Verteilung (Top 3), durchschnittliche Dauer pro Einheit."
),
business_meaning=(
"Schneller Überblick für KI-Prompts über aktuelle Trainingsaktivität. "
"Erlaubt Prompt-Autoren, Trainingsvolumen und -vielfalt auf einen Blick "
"zu erfassen ohne einzelne Datenpunkte zu konsumieren."
),
unit="text",
time_window="14d",
output_type=OutputType.TEXT_SUMMARY,
placeholder_type=PlaceholderType.INTERPRETED,
format_hint="Mehrere Sätze, menschenlesbar, Deutsch",
example_output=(
"14 Einheiten (315 min gesamt). Top-Typen: Krafttraining (5x, 180 min), "
"Ausdauer (4x, 90 min), Mobilität (3x, 30 min). Ø Dauer: 22 min/Einheit."
),
minimum_data_requirements=None,
quality_filter_policy=None,
confidence_logic=(
"Keine formale Confidence-Berechnung. Verfügbarkeit = mindestens 1 "
"activity_log Eintrag in 14d."
),
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="no_data",
legacy_display="Keine Aktivitätsdaten"
),
known_limitations=(
"OLD RESOLVER PATTERN: Keine Data Layer Funktion. Formatierung direkt "
"im Resolver. JOIN mit training_types für Typ-Namen. "
"CRITICAL: Keine Qualitätsprüfung - auch 1-Minuten-Einheiten werden gezählt."
),
layer_1_decision="NONE - Old resolver pattern (direct SQL in resolver)",
layer_2a_decision="Placeholder Resolver (formatting + SQL query)",
layer_2b_reuse_possible=False,
architecture_alignment=(
"NOT ALIGNED with Phase 0c Multi-Layer Architecture. "
"Should be refactored to use data_layer function."
),
issue_53_alignment="NOT ALIGNED - no layer separation"
)
activity_summary_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
activity_summary_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)
activity_summary_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
activity_summary_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
activity_summary_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("minimum_data_requirements", EvidenceType.UNRESOLVED)
activity_summary_metadata.set_evidence("quality_filter_policy", EvidenceType.UNRESOLVED)
activity_summary_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
activity_summary_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)
register_placeholder(activity_summary_metadata)
# ── activity_detail ───────────────────────────────────────────────────────
activity_detail_metadata = PlaceholderMetadata(
key="activity_detail",
category="Aktivität",
description="Detaillierte Liste der letzten 14 Tage Aktivität",
resolver_module="backend/placeholder_resolver.py",
resolver_function="_format_activity_detail",
data_layer_module=None,
data_layer_function=None,
source_tables=["activity_log", "training_types"],
semantic_contract=(
"Liefert eine strukturierte Liste aller Trainingseinheiten der letzten 14 Tage. "
"Jede Einheit: Datum, Trainingstyp, Dauer (Minuten), optional Notizen. "
"Sortiert chronologisch absteigend (neueste zuerst)."
),
business_meaning=(
"Detaillierte Trainingshistorie für KI-Prompts, die Muster, Progressionen "
"oder Abweichungen analysieren sollen. Erlaubt Erkennung von Trainingszyklen, "
"Pausentagen, Intensitätsmustern."
),
unit="list",
time_window="14d",
output_type=OutputType.LIST,
placeholder_type=PlaceholderType.RAW_DATA,
format_hint="Liste von Strings, eine Zeile pro Einheit: 'YYYY-MM-DD: Typ (Dauer min)'",
example_output=(
"2026-03-28: Krafttraining (45 min)\\n"
"2026-03-27: Laufen (30 min)\\n"
"2026-03-25: Yoga (20 min)"
),
minimum_data_requirements=None,
quality_filter_policy=None,
confidence_logic="Keine Confidence-Berechnung. Rohdaten-Liste.",
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="no_data",
legacy_display="Keine Aktivitätsdaten"
),
known_limitations=(
"OLD RESOLVER PATTERN: Keine Data Layer Funktion. "
"Formatierung direkt im Resolver. "
"CRITICAL: Keine Qualitätsfilterung - auch ungültige Einheiten (z.B. 0 min) "
"werden gelistet. JOIN mit training_types für Typ-Namen."
),
layer_1_decision="NONE - Old resolver pattern (direct SQL in resolver)",
layer_2a_decision="Placeholder Resolver (formatting + SQL query)",
layer_2b_reuse_possible=False,
architecture_alignment=(
"NOT ALIGNED with Phase 0c Multi-Layer Architecture. "
"Should be refactored to use data_layer function."
),
issue_53_alignment="NOT ALIGNED - no layer separation"
)
activity_detail_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
activity_detail_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)
activity_detail_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
activity_detail_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
activity_detail_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("minimum_data_requirements", EvidenceType.UNRESOLVED)
activity_detail_metadata.set_evidence("quality_filter_policy", EvidenceType.UNRESOLVED)
activity_detail_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
activity_detail_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)
register_placeholder(activity_detail_metadata)
# ── trainingstyp_verteilung ───────────────────────────────────────────────
trainingstyp_verteilung_metadata = PlaceholderMetadata(
key="trainingstyp_verteilung",
category="Aktivität",
description="Trainingstypen-Verteilung der letzten 14 Tage als JSON",
resolver_module="backend/placeholder_resolver.py",
resolver_function="_format_trainingstyp_verteilung",
data_layer_module=None,
data_layer_function=None,
source_tables=["activity_log", "training_types"],
semantic_contract=(
"Liefert eine JSON-Struktur mit der Verteilung der Trainingstypen über 14 Tage. "
"Für jeden Trainingstyp: Anzahl Einheiten, Gesamtdauer (Minuten), "
"Prozentanteil an Gesamtdauer. Sortiert nach Dauer absteigend."
),
business_meaning=(
"Analyse-Placeholder für Trainingsvielfalt und -schwerpunkte. "
"Erlaubt KI-Prompts, Imbalancen zu erkennen (z.B. nur Kraft, keine Ausdauer) "
"oder Zielkonformität zu prüfen (z.B. 'zu wenig Mobilität')."
),
unit="json",
time_window="14d",
output_type=OutputType.JSON,
placeholder_type=PlaceholderType.INTERPRETED,
format_hint="JSON Object mit Trainingstyp als Key, Value: {count, duration_min, percentage}",
example_output=(
'{"Krafttraining": {"count": 5, "duration_min": 180, "percentage": 57}, '
'"Ausdauer": {"count": 4, "duration_min": 90, "percentage": 29}, '
'"Mobilität": {"count": 3, "duration_min": 45, "percentage": 14}}'
),
minimum_data_requirements=None,
quality_filter_policy=None,
confidence_logic="Keine Confidence-Berechnung. Aggregation basiert auf verfügbaren Daten.",
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="no_data",
legacy_display="{}"
),
known_limitations=(
"OLD RESOLVER PATTERN: Keine Data Layer Funktion. "
"Aggregation direkt im Resolver. "
"CRITICAL: Keine Qualitätsfilterung - auch ungültige Einheiten werden aggregiert. "
"JOIN mit training_types für Typ-Namen. "
"EDGE CASE: Einheiten ohne training_type_id werden ignoriert (LEFT JOIN)."
),
layer_1_decision="NONE - Old resolver pattern (direct SQL aggregation in resolver)",
layer_2a_decision="Placeholder Resolver (aggregation + JSON formatting)",
layer_2b_reuse_possible=True,
architecture_alignment=(
"PARTIALLY ALIGNED: JSON output structure suitable for chart endpoints, "
"but no data layer separation. Should be refactored."
),
issue_53_alignment="PARTIALLY ALIGNED - output format good, layer separation missing"
)
trainingstyp_verteilung_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)
trainingstyp_verteilung_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
trainingstyp_verteilung_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("minimum_data_requirements", EvidenceType.UNRESOLVED)
trainingstyp_verteilung_metadata.set_evidence("quality_filter_policy", EvidenceType.UNRESOLVED)
trainingstyp_verteilung_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
trainingstyp_verteilung_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
trainingstyp_verteilung_metadata.set_evidence("architecture_alignment", EvidenceType.MIXED)
trainingstyp_verteilung_metadata.set_evidence("issue_53_alignment", EvidenceType.MIXED)
register_placeholder(trainingstyp_verteilung_metadata)
# =============================================================================
# GRUPPE 2: Basic Metrics (7 Placeholders)
# =============================================================================
def register_activity_group_2():
"""
Register Group 2: Basic Activity Metrics.
All use Phase 0c Data Layer architecture.
"""
# Common metadata for most Group 2 placeholders
common_metadata_7d = {
"category": "Aktivität",
"resolver_module": "backend/placeholder_resolver.py",
"data_layer_module": "backend/data_layer/activity_metrics.py",
"source_tables": ["activity_log"],
"time_window": "7d",
"output_type": OutputType.NUMERIC,
"placeholder_type": PlaceholderType.INTERPRETED,
"confidence_logic": "datenpunktbasierte Coverage-Logik (calculate_confidence in data layer)",
"missing_value_policy": MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="insufficient_data",
legacy_display="nicht genug Daten"
),
"layer_1_decision": "Data Layer (activity_metrics.py)",
"layer_2a_decision": "Placeholder Resolver (formatting only)",
"architecture_alignment": "Phase 0c Multi-Layer Architecture conform",
"issue_53_alignment": "Layer separation established"
}
# Common evidence for Group 2 placeholders
common_evidence_7d = {
"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,
"confidence_logic": EvidenceType.CODE_DERIVED,
"missing_value_policy": EvidenceType.CODE_DERIVED,
"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,
"minimum_data_requirements": EvidenceType.UNRESOLVED,
"quality_filter_policy": EvidenceType.UNRESOLVED
}
# ── training_minutes_week ─────────────────────────────────────────────────
training_minutes_week_metadata = PlaceholderMetadata(
key="training_minutes_week",
description="Gesamte Trainingsminuten in 7 Tagen",
resolver_function="get_training_minutes_week",
data_layer_function="get_weekly_training_volume",
semantic_contract=(
"Liefert die Summe aller Trainingsminuten über die letzten 7 Tage. "
"Basiert auf activity_log.duration_minutes. Keine Qualitätsfilterung - "
"alle Einheiten werden summiert unabhängig von Dauer oder Intensität."
),
business_meaning=(
"Kernmetrik für Trainingsvolumen und Periodisierung. "
"Erlaubt Erkennung von Übertraining (zu hoch), Detraining (zu niedrig), "
"und Trendanalysen (Volumen steigend/fallend)."
),
unit="Minuten",
format_hint="Ganzzahl",
example_output="315",
known_limitations=(
"Keine Qualitätsfilterung. 1-Minuten-Einheiten zählen gleich wie "
"60-Minuten-Sessions. Keine Intensitätsgewichtung. "
"EDGE CASE: duration_minutes = 0 oder NULL werden als 0 gezählt."
),
layer_2b_reuse_possible=True,
**common_metadata_7d
)
training_minutes_week_metadata.evidence.update(common_evidence_7d)
training_minutes_week_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
training_minutes_week_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
training_minutes_week_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
training_minutes_week_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
training_minutes_week_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)
training_minutes_week_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
training_minutes_week_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
training_minutes_week_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
training_minutes_week_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
training_minutes_week_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
register_placeholder(training_minutes_week_metadata)
# ── training_frequency_7d ─────────────────────────────────────────────────
training_frequency_7d_metadata = PlaceholderMetadata(
key="training_frequency_7d",
description="Anzahl Trainingseinheiten in 7 Tagen",
resolver_function="get_training_frequency_7d",
data_layer_function="get_training_frequency",
semantic_contract=(
"Liefert die Anzahl der Trainingseinheiten über die letzten 7 Tage. "
"Basiert auf COUNT(activity_log.id). Keine Qualitätsfilterung - "
"jede Einheit wird gezählt unabhängig von Dauer oder Intensität."
),
business_meaning=(
"Frequenz-Metrik für Trainingsregelmäßigkeit. "
"Erlaubt Erkennung von Konsistenz (z.B. 4-5 Einheiten/Woche), "
"Pausentagen, und Trainingszyklen."
),
unit="Anzahl Einheiten",
format_hint="Ganzzahl",
example_output="5",
known_limitations=(
"Keine Qualitätsfilterung. 1-Minuten-Einheiten zählen gleich wie "
"60-Minuten-Sessions. "
"EDGE CASE: Mehrere Einheiten am selben Tag werden separat gezählt "
"(kein Tages-Grouping)."
),
layer_2b_reuse_possible=True,
**common_metadata_7d
)
training_frequency_7d_metadata.evidence.update(common_evidence_7d)
training_frequency_7d_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
training_frequency_7d_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
training_frequency_7d_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
training_frequency_7d_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
training_frequency_7d_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)
training_frequency_7d_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
training_frequency_7d_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
training_frequency_7d_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
training_frequency_7d_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
training_frequency_7d_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
register_placeholder(training_frequency_7d_metadata)
# ── quality_sessions_pct ──────────────────────────────────────────────────
# Custom metadata for quality_sessions_pct (28d time window, different source tables)
quality_metadata_custom = {**common_metadata_7d}
quality_metadata_custom["time_window"] = "28d"
quality_metadata_custom["source_tables"] = ["activity_log", "training_types"]
quality_sessions_pct_metadata = PlaceholderMetadata(
key="quality_sessions_pct",
description="Prozentsatz Qualitäts-Sessions in 28 Tagen",
resolver_function="get_quality_sessions_pct",
data_layer_function="calculate_quality_sessions_percentage",
semantic_contract=(
"Liefert den Prozentsatz der Trainingseinheiten, die als 'Qualitäts-Sessions' "
"klassifiziert werden (duration >= 30 min UND intensity >= moderate). "
"Berechnung: (quality_sessions / total_sessions) × 100."
),
business_meaning=(
"Qualitäts-Metrik für Trainingseffektivität. "
"Niedrige Werte (<50%) deuten auf zu viele kurze/low-intensity Sessions. "
"Hohe Werte (>80%) deuten auf konsequent forderndes Training."
),
unit="Prozent",
format_hint="Ganzzahl 0-100",
example_output="68",
known_limitations=(
"CRITICAL TO_VERIFY: Code nutzt quality_label Feld (may not exist). "
"Canonical expects duration >= 30min + intensity >= moderate. "
"MISMATCH: quality_label vs. calculated quality. "
"EDGE CASE: Einheiten ohne training_type_id werden ignoriert."
),
layer_2b_reuse_possible=True,
**quality_metadata_custom
)
quality_sessions_pct_metadata.evidence.update(common_evidence_7d)
quality_sessions_pct_metadata.evidence["time_window"] = EvidenceType.CODE_DERIVED
quality_sessions_pct_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
quality_sessions_pct_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
quality_sessions_pct_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
quality_sessions_pct_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
quality_sessions_pct_metadata.set_evidence("semantic_contract", EvidenceType.TO_VERIFY)
quality_sessions_pct_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
quality_sessions_pct_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
quality_sessions_pct_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
quality_sessions_pct_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
quality_sessions_pct_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
quality_sessions_pct_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
register_placeholder(quality_sessions_pct_metadata)
# ── proxy_internal_load_7d ────────────────────────────────────────────────
# Custom metadata for proxy_internal_load (different source tables)
proxy_load_metadata_custom = {**common_metadata_7d}
proxy_load_metadata_custom["source_tables"] = ["activity_log", "training_types"]
proxy_internal_load_7d_metadata = PlaceholderMetadata(
key="proxy_internal_load_7d",
description="Proxy Internal Load über 7 Tage",
resolver_function="get_proxy_internal_load_7d",
data_layer_function="calculate_proxy_internal_load",
semantic_contract=(
"Liefert einen Proxy-Wert für die interne Trainingsbelastung über 7 Tage. "
"Berechnet als: Summe(duration × intensity_factor × quality_factor). "
"Intensity_factor basiert auf RPE-Mapping (1-10 → 0.1-1.0). "
"Quality_factor basiert auf quality_label (easy/moderate/hard → 0.8/1.0/1.2)."
),
business_meaning=(
"Load-Monitoring-Metrik für Periodisierung und Übertrainings-Prävention. "
"Erlaubt Vergleich interner Belastung über Zeit (nicht nur Volumen). "
"Basis für Monotony und Strain Scores."
),
unit="Load-Punkte",
format_hint="Ganzzahl",
example_output="1850",
known_limitations=(
"PROXY-WERT: Nicht trainings wissenschaftlich validiert. "
"RPE-Mapping vereinfacht (linear 1-10 → 0.1-1.0). "
"CRITICAL BUG: RPE 4-5 maps to 'moderate' quality but 'moderate' "
"not in quality_factors dict (easy/hard only). "
"EDGE CASE: Einheiten ohne RPE bekommen intensity_factor = 0.5 (default)."
),
layer_2b_reuse_possible=True,
**proxy_load_metadata_custom
)
proxy_internal_load_7d_metadata.evidence.update(common_evidence_7d)
proxy_internal_load_7d_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("semantic_contract", EvidenceType.CODE_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("unit", EvidenceType.DRAFT_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
proxy_internal_load_7d_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
register_placeholder(proxy_internal_load_7d_metadata)
# ── monotony_score ────────────────────────────────────────────────────────
monotony_score_metadata = PlaceholderMetadata(
key="monotony_score",
description="Trainings-Monotonie-Score über 7 Tage",
resolver_function="get_monotony_score",
data_layer_function="calculate_monotony_score",
semantic_contract=(
"Liefert einen Monotonie-Score für die Trainingsbelastung über 7 Tage. "
"Berechnet als: mean(daily_duration) / std_dev(daily_duration). "
"Hohe Werte (>2.0) = monotones Training (gleichbleibende Belastung). "
"Niedrige Werte (<1.5) = variable Belastung."
),
business_meaning=(
"Variabilitäts-Metrik für Periodisierung. "
"Hohe Monotonie erhöht Übertrainings-Risiko (Foster et al. 2001). "
"Kombination mit Strain Score für Übertrainings-Warnung."
),
unit="Score (dimensionslos)",
format_hint="Dezimalzahl, typisch 0.5-3.0",
example_output="1.8",
known_limitations=(
"FORMULA: mean / std_dev. "
"EDGE CASE: std_dev = 0 (alle Tage gleich) → division by zero → return mean as fallback. "
"EDGE CASE: < 3 Trainingstage → nicht aussagekräftig, aber wird trotzdem berechnet. "
"ZEITFENSTER: 7 Tage möglicherweise zu kurz für verlässliche Monotonie-Analyse "
"(Foster original: 28 Tage)."
),
layer_2b_reuse_possible=True,
**common_metadata_7d
)
monotony_score_metadata.evidence.update(common_evidence_7d)
monotony_score_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
monotony_score_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
monotony_score_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
monotony_score_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
monotony_score_metadata.set_evidence("semantic_contract", EvidenceType.CODE_DERIVED)
monotony_score_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
monotony_score_metadata.set_evidence("unit", EvidenceType.DRAFT_DERIVED)
monotony_score_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
monotony_score_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
monotony_score_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
register_placeholder(monotony_score_metadata)
# ── strain_score ──────────────────────────────────────────────────────────
strain_score_metadata = PlaceholderMetadata(
key="strain_score",
description="Trainings-Strain-Score über 7 Tage",
resolver_function="get_strain_score",
data_layer_function="calculate_strain_score",
semantic_contract=(
"Liefert einen Strain-Score für die Gesamtbelastung über 7 Tage. "
"Berechnet als: proxy_internal_load_7d × monotony_score. "
"Kombiniert Volumen (Load) mit Variabilität (Monotonie) zu Gesamt-Strain."
),
business_meaning=(
"Übertrainings-Risiko-Metrik (Foster et al. 2001). "
"Hohe Werte = hohes Risiko (hohe Last + monotones Training). "
"Schwellenwerte: <5000 = moderat, 5000-8000 = erhöht, >8000 = kritisch."
),
unit="Strain-Punkte",
format_hint="Ganzzahl, typisch 1000-10000",
example_output="3330",
known_limitations=(
"FORMULA: load × monotony. "
"ABHÄNGIG von proxy_internal_load_7d (bereits Proxy-Wert). "
"SCHWELLENWERTE: Nicht wissenschaftlich validiert für diese App. "
"EDGE CASE: Wenn monotony_score Fallback-Logik greift (std_dev=0), "
"wird Strain = Load × mean (nicht original Formel)."
),
layer_2b_reuse_possible=True,
**common_metadata_7d
)
strain_score_metadata.evidence.update(common_evidence_7d)
strain_score_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
strain_score_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
strain_score_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
strain_score_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
strain_score_metadata.set_evidence("semantic_contract", EvidenceType.CODE_DERIVED)
strain_score_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
strain_score_metadata.set_evidence("unit", EvidenceType.DRAFT_DERIVED)
strain_score_metadata.set_evidence("format_hint", EvidenceType.DRAFT_DERIVED)
strain_score_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
strain_score_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
register_placeholder(strain_score_metadata)
# ── rest_day_compliance ───────────────────────────────────────────────────
# Custom metadata for rest_day_compliance (28d time window, different source tables)
rest_metadata_custom = {**common_metadata_7d}
rest_metadata_custom["time_window"] = "28d"
rest_metadata_custom["source_tables"] = ["rest_days", "activity_log"]
rest_day_compliance_metadata = PlaceholderMetadata(
key="rest_day_compliance",
description="Ruhetag-Compliance über 28 Tage",
resolver_function="get_rest_day_compliance",
data_layer_function="calculate_rest_day_compliance",
semantic_contract=(
"Liefert den Prozentsatz der geplanten Ruhetage, die tatsächlich eingehalten wurden. "
"Berechnung: (eingehaltene_ruhetage / geplante_ruhetage) × 100. "
"Ruhetag = Eintrag in rest_days OHNE korrespondierenden activity_log Eintrag am selben Tag."
),
business_meaning=(
"Regenerations-Compliance-Metrik. "
"Niedrige Werte (<70%) = schlechte Regeneration, Übertrainings-Risiko. "
"Hohe Werte (>90%) = gute Disziplin bei Regeneration."
),
unit="Prozent",
format_hint="Ganzzahl 0-100",
example_output="85",
known_limitations=(
"JSONB DEPENDENCY: Nutzt rest_config->>'focus' JSONB Feld. "
"EDGE CASE: rest_config NULL → Ruhetag wird ignoriert (nicht gezählt). "
"EDGE CASE: Kein activity_log Eintrag am Ruhetag = compliant (korrekt). "
"EDGE CASE: Mehrere activity_log Einträge am Ruhetag = alle zählen als violation."
),
layer_2b_reuse_possible=False,
**rest_metadata_custom
)
rest_day_compliance_metadata.evidence.update(common_evidence_7d)
rest_day_compliance_metadata.evidence["time_window"] = EvidenceType.CODE_DERIVED
rest_day_compliance_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
rest_day_compliance_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
rest_day_compliance_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
rest_day_compliance_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
rest_day_compliance_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)
rest_day_compliance_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
rest_day_compliance_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
rest_day_compliance_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
rest_day_compliance_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
rest_day_compliance_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
rest_day_compliance_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
rest_day_compliance_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.CODE_DERIVED)
register_placeholder(rest_day_compliance_metadata)
# =============================================================================
# GRUPPE 3: Advanced Metrics (7 Placeholders)
# =============================================================================
def register_activity_group_3():
"""
Register Group 3: Advanced Activity Metrics.
Includes ability balance metrics (5), VO2 Max trend, and activity score.
"""
# Common metadata for ability_balance placeholders
common_metadata_ability = {
"category": "Aktivität",
"resolver_module": "backend/placeholder_resolver.py",
"data_layer_module": "backend/data_layer/activity_metrics.py",
"data_layer_function": "calculate_ability_balance",
"source_tables": ["activity_log", "training_types"],
"time_window": "28d",
"output_type": OutputType.NUMERIC,
"placeholder_type": PlaceholderType.SCORE,
"unit": "Score (0-100)",
"format_hint": "Ganzzahl 0-100",
"confidence_logic": "datenpunktbasierte Coverage-Logik (calculate_confidence in data layer)",
"missing_value_policy": MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="insufficient_data",
legacy_display="nicht genug Daten"
),
"layer_1_decision": "Data Layer (activity_metrics.calculate_ability_balance)",
"layer_2a_decision": "Placeholder Resolver (extract specific ability from dict)",
"architecture_alignment": "Phase 0c Multi-Layer Architecture conform",
"issue_53_alignment": "Layer separation established"
}
# Common evidence for ability_balance placeholders
common_evidence_ability = {
"category": EvidenceType.CODE_DERIVED,
"resolver_module": EvidenceType.CODE_DERIVED,
"data_layer_module": EvidenceType.CODE_DERIVED,
"data_layer_function": EvidenceType.CODE_DERIVED,
"source_tables": EvidenceType.CODE_DERIVED,
"time_window": EvidenceType.CODE_DERIVED,
"output_type": EvidenceType.CODE_DERIVED,
"placeholder_type": EvidenceType.MIXED,
"unit": EvidenceType.CODE_DERIVED,
"format_hint": EvidenceType.CODE_DERIVED,
"confidence_logic": EvidenceType.CODE_DERIVED,
"missing_value_policy": EvidenceType.CODE_DERIVED,
"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,
"minimum_data_requirements": EvidenceType.UNRESOLVED,
"quality_filter_policy": EvidenceType.UNRESOLVED
}
# ── ability_balance_strength ──────────────────────────────────────────────
ability_balance_strength_metadata = PlaceholderMetadata(
key="ability_balance_strength",
description="Fähigkeiten-Balance-Score: Kraft (28d)",
resolver_function="get_ability_balance_strength",
semantic_contract=(
"Liefert einen normierten Score (0-100) für die relative Trainingsbelastung "
"der Fähigkeit 'Kraft' über 28 Tage. Berechnung: "
"(kraft_load / max_load_aller_fähigkeiten) × 100. "
"Basiert auf training_types.abilities JSONB Feld (strength_percentage)."
),
business_meaning=(
"Balance-Metrik für Trainingsvielfalt. "
"Zeigt ob Kraft überproportional (>80), balanciert (40-60), oder unterrepräsentiert (<20) trainiert wird. "
"Vergleich mit anderen Fähigkeiten zeigt Imbalancen."
),
example_output="75",
known_limitations=(
"JSONB DEPENDENCY: training_types.abilities JSONB Feld muss populated sein. "
"FORMULA: max-normalized (nicht prozentual vom Total). "
"Höchste Fähigkeit = 100, andere relativ dazu. "
"EDGE CASE: abilities NULL → Trainingstyp wird mit 0% für alle Fähigkeiten gewertet. "
"EDGE CASE: Alle Fähigkeiten = 0 (keine Trainings) → division by zero → return 0."
),
layer_2b_reuse_possible=True,
**common_metadata_ability
)
ability_balance_strength_metadata.evidence.update(common_evidence_ability)
ability_balance_strength_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
ability_balance_strength_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
ability_balance_strength_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
ability_balance_strength_metadata.set_evidence("semantic_contract", EvidenceType.CODE_DERIVED)
ability_balance_strength_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
ability_balance_strength_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
ability_balance_strength_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
register_placeholder(ability_balance_strength_metadata)
# ── ability_balance_endurance ─────────────────────────────────────────────
ability_balance_endurance_metadata = PlaceholderMetadata(
key="ability_balance_endurance",
description="Fähigkeiten-Balance-Score: Ausdauer (28d)",
resolver_function="get_ability_balance_endurance",
semantic_contract=(
"Liefert einen normierten Score (0-100) für die relative Trainingsbelastung "
"der Fähigkeit 'Ausdauer' über 28 Tage. Berechnung: "
"(ausdauer_load / max_load_aller_fähigkeiten) × 100. "
"Basiert auf training_types.abilities JSONB Feld (endurance_percentage)."
),
business_meaning=(
"Balance-Metrik für Trainingsvielfalt. "
"Zeigt ob Ausdauer überproportional (>80), balanciert (40-60), oder unterrepräsentiert (<20) trainiert wird."
),
example_output="92",
known_limitations=(
"JSONB DEPENDENCY: training_types.abilities JSONB Feld muss populated sein. "
"FORMULA: max-normalized (nicht prozentual vom Total). "
"EDGE CASE: abilities NULL → 0% Ausdauer. "
"EDGE CASE: Alle Fähigkeiten = 0 → return 0."
),
layer_2b_reuse_possible=True,
**common_metadata_ability
)
ability_balance_endurance_metadata.evidence.update(common_evidence_ability)
ability_balance_endurance_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
ability_balance_endurance_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
ability_balance_endurance_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
ability_balance_endurance_metadata.set_evidence("semantic_contract", EvidenceType.CODE_DERIVED)
ability_balance_endurance_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
ability_balance_endurance_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
ability_balance_endurance_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
register_placeholder(ability_balance_endurance_metadata)
# ── ability_balance_mental ────────────────────────────────────────────────
ability_balance_mental_metadata = PlaceholderMetadata(
key="ability_balance_mental",
description="Fähigkeiten-Balance-Score: Geist & Meditation (28d)",
resolver_function="get_ability_balance_mental",
semantic_contract=(
"Liefert einen normierten Score (0-100) für die relative Trainingsbelastung "
"der Fähigkeit 'Geist & Meditation' über 28 Tage. Berechnung: "
"(mental_load / max_load_aller_fähigkeiten) × 100. "
"Basiert auf training_types.abilities JSONB Feld (mental_percentage)."
),
business_meaning=(
"Balance-Metrik für ganzheitliches Training. "
"Zeigt ob mentale/meditative Praxis im Verhältnis zu physischem Training steht. "
"Oft niedrig (<20) bei rein physisch orientierten Trainern."
),
example_output="15",
known_limitations=(
"JSONB DEPENDENCY: training_types.abilities JSONB Feld muss populated sein. "
"FORMULA: max-normalized (nicht prozentual vom Total). "
"EDGE CASE: abilities NULL → 0% Mental. "
"EDGE CASE: Alle Fähigkeiten = 0 → return 0."
),
layer_2b_reuse_possible=True,
**common_metadata_ability
)
ability_balance_mental_metadata.evidence.update(common_evidence_ability)
ability_balance_mental_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
ability_balance_mental_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
ability_balance_mental_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
ability_balance_mental_metadata.set_evidence("semantic_contract", EvidenceType.CODE_DERIVED)
ability_balance_mental_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
ability_balance_mental_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
ability_balance_mental_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
register_placeholder(ability_balance_mental_metadata)
# ── ability_balance_coordination ──────────────────────────────────────────
ability_balance_coordination_metadata = PlaceholderMetadata(
key="ability_balance_coordination",
description="Fähigkeiten-Balance-Score: Koordination (28d)",
resolver_function="get_ability_balance_coordination",
semantic_contract=(
"Liefert einen normierten Score (0-100) für die relative Trainingsbelastung "
"der Fähigkeit 'Koordination' über 28 Tage. Berechnung: "
"(koordination_load / max_load_aller_fähigkeiten) × 100. "
"Basiert auf training_types.abilities JSONB Feld (coordination_percentage)."
),
business_meaning=(
"Balance-Metrik für motorische Vielfalt. "
"Zeigt ob Koordinations-Training (Kampfsport, Tanz, komplexe Bewegungsmuster) "
"im Trainingsplan vertreten ist. Oft niedrig (<20) bei reinem Kraft/Ausdauer-Training."
),
example_output="28",
known_limitations=(
"JSONB DEPENDENCY: training_types.abilities JSONB Feld muss populated sein. "
"FORMULA: max-normalized (nicht prozentual vom Total). "
"EDGE CASE: abilities NULL → 0% Koordination. "
"EDGE CASE: Alle Fähigkeiten = 0 → return 0."
),
layer_2b_reuse_possible=True,
**common_metadata_ability
)
ability_balance_coordination_metadata.evidence.update(common_evidence_ability)
ability_balance_coordination_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
ability_balance_coordination_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
ability_balance_coordination_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
ability_balance_coordination_metadata.set_evidence("semantic_contract", EvidenceType.CODE_DERIVED)
ability_balance_coordination_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
ability_balance_coordination_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
ability_balance_coordination_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
register_placeholder(ability_balance_coordination_metadata)
# ── ability_balance_mobility ──────────────────────────────────────────────
ability_balance_mobility_metadata = PlaceholderMetadata(
key="ability_balance_mobility",
description="Fähigkeiten-Balance-Score: Mobilität (28d)",
resolver_function="get_ability_balance_mobility",
semantic_contract=(
"Liefert einen normierten Score (0-100) für die relative Trainingsbelastung "
"der Fähigkeit 'Mobilität' über 28 Tage. Berechnung: "
"(mobilität_load / max_load_aller_fähigkeiten) × 100. "
"Basiert auf training_types.abilities JSONB Feld (mobility_percentage)."
),
business_meaning=(
"Balance-Metrik für Beweglichkeits-Training. "
"Zeigt ob Mobilität (Stretching, Yoga, Fasziendehnung) im Trainingsplan vertreten ist. "
"Wichtig für Verletzungsprävention und Regeneration. Oft vernachlässigt (<20)."
),
example_output="22",
known_limitations=(
"JSONB DEPENDENCY: training_types.abilities JSONB Feld muss populated sein. "
"FORMULA: max-normalized (nicht prozentual vom Total). "
"EDGE CASE: abilities NULL → 0% Mobilität. "
"EDGE CASE: Alle Fähigkeiten = 0 → return 0."
),
layer_2b_reuse_possible=True,
**common_metadata_ability
)
ability_balance_mobility_metadata.evidence.update(common_evidence_ability)
ability_balance_mobility_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
ability_balance_mobility_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
ability_balance_mobility_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
ability_balance_mobility_metadata.set_evidence("semantic_contract", EvidenceType.CODE_DERIVED)
ability_balance_mobility_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
ability_balance_mobility_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
ability_balance_mobility_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
register_placeholder(ability_balance_mobility_metadata)
# ── vo2max_trend_28d ──────────────────────────────────────────────────────
vo2max_trend_28d_metadata = PlaceholderMetadata(
key="vo2max_trend_28d",
description="VO2 Max Trend über 28 Tage",
category="Aktivität",
resolver_module="backend/placeholder_resolver.py",
resolver_function="get_vo2max_trend_28d",
data_layer_module="backend/data_layer/activity_metrics.py",
data_layer_function="calculate_vo2max_trend",
source_tables=["vitals_baseline"],
time_window="28d",
output_type=OutputType.NUMERIC,
placeholder_type=PlaceholderType.INTERPRETED,
semantic_contract=(
"Liefert den Trend der VO2 Max Werte über 28 Tage als Delta (letzter - erster Wert). "
"Positiver Wert = Verbesserung der aeroben Fitness, "
"negativer Wert = Verschlechterung. "
"Basiert auf vitals_baseline.vo2_max (gemessen oder geschätzt)."
),
business_meaning=(
"Fitness-Trend-Metrik für aerobe Kapazität. "
"Indikator für Trainingseffektivität bei Ausdauer-fokussiertem Training. "
"QUESTIONABLE CATEGORY: VO2 Max ist primär Vitalwert/Recovery, nicht Activity."
),
unit="ml/kg/min (Delta)",
format_hint="Dezimalzahl mit Vorzeichen, typisch -3.0 bis +3.0",
example_output="+1.2",
minimum_data_requirements=None,
quality_filter_policy=None,
confidence_logic=(
"datenpunktbasierte Coverage-Logik. "
"Mindestens 2 VO2 Max Messungen in 28d erforderlich für Trend."
),
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="insufficient_data",
legacy_display="nicht genug Daten"
),
known_limitations=(
"QUESTIONABLE CATEGORY: VO2 Max primär Recovery-Cluster, nicht Activity-Cluster. "
"DEPENDENCY: vitals_baseline.vo2_max Feld (oft NULL wenn nicht gemessen). "
"EDGE CASE: Nur 1 Messung → kein Trend → missing_value. "
"EDGE CASE: Große Zeitlücken zwischen Messungen → Trend nicht aussagekräftig."
),
layer_1_decision="Data Layer (activity_metrics.calculate_vo2max_trend) - QUESTIONABLE",
layer_2a_decision="Placeholder Resolver (formatting only)",
layer_2b_reuse_possible=True,
architecture_alignment="Phase 0c Multi-Layer Architecture conform",
issue_53_alignment="Layer separation established"
)
vo2max_trend_28d_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("category", EvidenceType.TO_VERIFY)
vo2max_trend_28d_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
vo2max_trend_28d_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
vo2max_trend_28d_metadata.set_evidence("semantic_contract", EvidenceType.MIXED)
vo2max_trend_28d_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
vo2max_trend_28d_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("format_hint", EvidenceType.DRAFT_DERIVED)
vo2max_trend_28d_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("minimum_data_requirements", EvidenceType.UNRESOLVED)
vo2max_trend_28d_metadata.set_evidence("quality_filter_policy", EvidenceType.UNRESOLVED)
vo2max_trend_28d_metadata.set_evidence("confidence_logic", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("known_limitations", EvidenceType.MIXED)
vo2max_trend_28d_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY)
vo2max_trend_28d_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
vo2max_trend_28d_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)
register_placeholder(vo2max_trend_28d_metadata)
# ── activity_score ────────────────────────────────────────────────────────
activity_score_metadata = PlaceholderMetadata(
key="activity_score",
description="Gesamtaktivitäts-Score (gewichtet)",
category="Aktivität",
resolver_module="backend/placeholder_resolver.py",
resolver_function="get_activity_score",
data_layer_module="backend/data_layer/scores.py",
data_layer_function="calculate_activity_score",
source_tables=["activity_log", "training_types", "rest_days", "vitals_baseline", "user_focus_area_weights"],
time_window="composite (7d, 14d, 28d mixed)",
output_type=OutputType.NUMERIC,
placeholder_type=PlaceholderType.SCORE,
semantic_contract=(
"Liefert einen gewichteten Gesamtscore (0-100) für Trainingsaktivität. "
"Berechnung: Gewichteter Durchschnitt aus 5 Komponenten: "
"1. Volumen (training_minutes_week vs. Ziel), "
"2. Frequenz (training_frequency_7d vs. Ziel), "
"3. Qualität (quality_sessions_pct), "
"4. Balance (Ability Balance Scores), "
"5. Recovery (rest_day_compliance). "
"Gewichtung dynamisch aus user_focus_area_weights."
),
business_meaning=(
"Meta-Score für Gesamtaktivitäts-Qualität. "
"Kombiniert Volumen, Konsistenz, Qualität, Vielfalt und Regeneration. "
"Ziel-abhängig: Gewichtung variiert je nach User-Fokus (Kraft vs. Ausdauer vs. Balance)."
),
unit="Score (0-100)",
format_hint="Ganzzahl",
example_output="78",
minimum_data_requirements=None,
quality_filter_policy=None,
confidence_logic=(
"Composite Confidence basierend auf Komponenten-Confidence. "
"Mindestens 3/5 Komponenten müssen verfügbar sein für Score-Berechnung."
),
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="insufficient_data",
legacy_display="nicht genug Daten"
),
known_limitations=(
"COMPOSITE SCORE: Komplexe Abhängigkeiten zu allen Activity-Metriken. "
"FOCUS-DEPENDENT: Gewichtung variiert je nach user_focus_area_weights (dynamisch). "
"EDGE CASE: Wenn user_focus_area_weights fehlt → default weighting (alle gleich). "
"ZEITFENSTER MIXED: Komponenten nutzen unterschiedliche Zeitfenster (7d, 14d, 28d). "
"QUESTIONABLE: Vermischt Metriken mit unterschiedlicher Verlässlichkeit "
"(z.B. quality_sessions_pct hat TO_VERIFY Issues)."
),
layer_1_decision="Data Layer (scores.calculate_activity_score)",
layer_2a_decision="Placeholder Resolver (formatting only)",
layer_2b_reuse_possible=False,
architecture_alignment="Phase 0c Multi-Layer Architecture conform",
issue_53_alignment="Layer separation established"
)
activity_score_metadata.set_evidence("key", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("category", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("description", EvidenceType.DRAFT_DERIVED)
activity_score_metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("data_layer_function", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("output_type", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("placeholder_type", EvidenceType.MIXED)
activity_score_metadata.set_evidence("semantic_contract", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED)
activity_score_metadata.set_evidence("unit", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("format_hint", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("example_output", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("minimum_data_requirements", EvidenceType.UNRESOLVED)
activity_score_metadata.set_evidence("quality_filter_policy", EvidenceType.UNRESOLVED)
activity_score_metadata.set_evidence("confidence_logic", EvidenceType.MIXED)
activity_score_metadata.set_evidence("missing_value_policy", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("known_limitations", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("layer_1_decision", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("layer_2a_decision", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("architecture_alignment", EvidenceType.CODE_DERIVED)
activity_score_metadata.set_evidence("issue_53_alignment", EvidenceType.CODE_DERIVED)
register_placeholder(activity_score_metadata)
# =============================================================================
# Auto-Registration
# =============================================================================
# Register all groups when module is imported
register_activity_group_1()
register_activity_group_2()
register_activity_group_3()