mitai-jinkendo/backend/placeholder_registrations/activity_metrics.py
Lars c3be745efa
All checks were successful
Deploy Development / deploy (push) Successful in 48s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
feat: Enhance activity metrics documentation and registry updates
- Added details for Issue #53 regarding the audit of activity placeholders between Layer 1 and Layer 2a in `CLAUDE.md` and `README.md`.
- Updated the `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` to reflect the new registry checks and dynamic session metrics handling.
- Revised the `placeholder_resolver.py` and `activity_metrics.py` to clarify the registration of activity metrics and session insights, ensuring consistency in the handling of dynamic keys and metrics.
- Improved descriptions and semantic contracts in `activity_session_insights.py` to better outline the structure and limitations of session data.
2026-04-17 20:28:58 +02:00

1114 lines
62 KiB
Python
Raw Permalink 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 17 Aktivitäts-Platzhalter hier; 3 weitere Keys in activity_session_insights.py (**20 gesamt** in PLACEHOLDER_MAP).
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
Resolver: alle Keys gebündelt unter „Training / Aktivität“ in PLACEHOLDER_MAP;
activity_score nicht unter „Meta Scores“.
"""
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="get_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=(
"Letzte 14 Tage: pro Session Kopfzeile (activity_log) plus gemergte Profil-Metriken "
"(dynamische Keys je training_category / training_type_id)"
),
resolver_module="backend/placeholder_resolver.py",
resolver_function="get_activity_detail",
data_layer_module="backend/data_layer/activity_metrics.py",
data_layer_function="get_activity_detail_data",
source_tables=["activity_log", "activity_session_metrics", "training_parameters"],
semantic_contract=(
"Layer 1: get_activity_detail_data lädt Sessions, enrich_sessions_with_metrics fügt "
"session_metrics hinzu — effektive Liste aus merge_column_backed_and_eav_metrics: nur "
"Parameter aus dem Attributschema (tcp/ttp), sortiert nach key. "
"Leseregel Kanon: activity_log-Spalte (source_field, Registry-Feld, Legacy-Spalte für "
"EAV-primäre Keys) schlägt EAV, wenn beide Werte liefern. "
"Layer 2a: Zeilen mit „| EAV: key=value; …“ nur für nicht-leere session_metrics; "
"die Menge der Keys ist admin-/profilabhängig, kein festes Prompt-Schema."
),
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=(
"Pro Zeile: Datum, Typ, Dauer, kcal, optional HF, optional „| EAV: …“ aus Session-Metriken"
),
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=(
"Keine Profil-Qualitätsfilterung in dieser Liste. Max. 20 Zeilen im Prompt-Output "
"(Hard-Limit Resolver). session_metrics kann leer sein (kein Typ, kein Profil, keine EAV-Zeilen). "
"Keys und Anzahl Metriken variieren je Instanz/Admin — nicht von festen Platzhaltern in anderen "
"Prompts ausgehen. Nur im effektiven Merge erscheinende Parameter; keine verwaisten EAV-Keys "
"außerhalb des Schemas."
),
layer_1_decision="activity_metrics.get_activity_detail_data (+ enrich_sessions_with_metrics)",
layer_2a_decision="get_activity_detail (Formatierung)",
layer_2b_reuse_possible=True,
architecture_alignment="Phase 0c Layer 1 + EAV-Anreicherung",
issue_53_alignment="Layer 1"
)
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="Verteilung nach training_category (14 Tage): Top 3 als kompakte Prozent-Textzeile",
resolver_module="backend/placeholder_resolver.py",
resolver_function="get_trainingstyp_verteilung",
data_layer_module="backend/data_layer/activity_metrics.py",
data_layer_function="get_training_type_distribution_data",
source_tables=["activity_log"],
semantic_contract=(
"Layer 1: get_training_type_distribution_data — Anteil je training_category am "
"Gesamt-Session-Count im Fenster (auch unkategorisierte zählen im Nenner). "
"Layer 2a: Top 3 Kategorien als „Name: p%“ kommagetrennt; bei fehlenden Daten Kurz-Hinweis."
),
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="text",
time_window="14d",
output_type=OutputType.TEXT_SUMMARY,
placeholder_type=PlaceholderType.INTERPRETED,
format_hint="Eine Zeile: bis zu drei „Kategorie: Prozent%“, durch Komma getrennt",
example_output="cardio: 45%, strength: 30%, mobility: 15%",
minimum_data_requirements=None,
quality_filter_policy=None,
confidence_logic="Wie get_training_type_distribution_data (calculate_confidence über categorized_count)",
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="no_data",
legacy_display="Keine kategorisierten Trainings"
),
known_limitations=(
"Nur Sessions mit gesetztem training_category fließen in die Verteilungsliste; "
"Prozente beziehen sich auf alle Sessions im Fenster (Nenner = total_sessions). "
"Keine Qualitätsfilterung der Einheiten. Kein drill-down nach training_type_id in diesem Platzhalter."
),
layer_1_decision="activity_metrics.get_training_type_distribution_data",
layer_2a_decision="get_trainingstyp_verteilung (Top 3 als Text)",
layer_2b_reuse_possible=True,
architecture_alignment="Phase 0c — Layer 1 + Formatierung",
issue_53_alignment="Layer 1"
)
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="_safe_float",
data_layer_module="backend/data_layer/activity_metrics.py",
data_layer_function="calculate_vo2max_trend_28d",
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_28d) — Kategorie diskutierbar",
layer_2a_decision="Placeholder Resolver (_safe_float)",
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="_safe_int",
data_layer_module="backend/data_layer/activity_metrics.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 (activity_metrics.calculate_activity_score)",
layer_2a_decision="Placeholder Resolver (_safe_int)",
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()