mitai-jinkendo/backend/placeholder_registrations/activity_session_insights.py
Lars bc8e9fb7fa
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 training parameters handling and documentation
- Introduced new fields for descriptions in training parameters, improving clarity for AI context in `training_sessions_recent_json`.
- Added a glossary placeholder `{{training_parameters_glossary_md}}` to provide a Markdown table of active training parameters, including names and descriptions.
- Updated the `placeholder_resolver.py` and `activity_metrics.py` to support the new glossary functionality.
- Enhanced the `AdminActivityAttributeProfilesPage` to allow input for descriptions in both German and English, ensuring better context for metrics.
- Revised tests to validate the inclusion of description fields in parameter schema merges and metrics handling.
2026-04-17 20:42:11 +02:00

255 lines
12 KiB
Python

"""
Registry: Trainings-Häufigkeit, Pausen zwischen Einheiten, wöchentliche Session-JSON (KI-Rohkontext).
"""
from placeholder_registry import (
PlaceholderMetadata,
MissingValuePolicy,
EvidenceType,
OutputType,
PlaceholderType,
register_placeholder,
)
def _ev(meta: PlaceholderMetadata, field: str, et: EvidenceType = EvidenceType.CODE_DERIVED):
meta.set_evidence(field, et)
def register_activity_session_insights():
md_freq = PlaceholderMetadata(
key="training_frequency_by_type_md",
category="Aktivität",
description=(
"Markdown-Tabelle: pro Trainingsart (activity_type) Sessions, Ø/Woche, "
"Dauer, kcal, HF, RPE, kcal/min (Intensitätsproxy)"
),
resolver_module="backend/placeholder_resolver.py",
resolver_function="get_training_frequency_by_type_md",
data_layer_module="backend/data_layer/activity_metrics.py",
data_layer_function="get_training_frequency_by_type_data",
source_tables=["activity_log"],
semantic_contract=(
"Aggregat über activity_log gruppiert nach activity_type (Roh-Label). "
"sessions_per_week = count / (days/7). avg_kcal_per_min = Summe kcal / Summe min."
),
business_meaning="KI: Häufigkeit & Belastung pro Sportart, Erholungs-/Überlastungs-Kontext",
unit="Markdown",
time_window="default 28 Tage",
output_type=OutputType.TEXT_SUMMARY,
placeholder_type=PlaceholderType.INTERPRETED,
format_hint="GitHub-Flavored Markdown-Tabelle",
example_output="| Art | n | Ø/Woche | … |",
minimum_data_requirements="Mindestens eine Session im Fenster",
quality_filter_policy=None,
confidence_logic="Wie calculate_confidence anhand Session-Anzahl",
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="no_data",
legacy_display="Keine Trainingsdaten",
),
known_limitations=(
"Gruppierung nach activity_type-String (Import-Namen), nicht nur training_type_id. "
"HF/RPE oft NULL je nach Quelle. Pausen-Analyse separater Platzhalter."
),
layer_1_decision="activity_metrics.get_training_frequency_by_type_data",
layer_2a_decision="get_training_frequency_by_type_md",
layer_2b_reuse_possible=True,
architecture_alignment="Phase 0c",
issue_53_alignment="Layer 1",
evidence={},
)
for f in (
"key", "category", "description", "resolver_module", "resolver_function",
"data_layer_module", "data_layer_function", "source_tables", "semantic_contract",
"unit", "time_window", "output_type", "placeholder_type", "format_hint",
"example_output", "minimum_data_requirements", "confidence_logic",
"missing_value_policy", "layer_1_decision", "layer_2a_decision",
"layer_2b_reuse_possible", "architecture_alignment", "issue_53_alignment",
):
_ev(md_freq, f)
_ev(md_freq, "business_meaning", EvidenceType.DRAFT_DERIVED)
_ev(md_freq, "known_limitations", EvidenceType.MIXED)
register_placeholder(md_freq)
md_gap = PlaceholderMetadata(
key="training_inter_session_gap_md",
category="Aktivität",
description="Median/Mittel/Min der Stunden zwischen aufeinanderfolgenden Trainingseinheiten",
resolver_module="backend/placeholder_resolver.py",
resolver_function="get_training_inter_session_gap_md",
data_layer_module="backend/data_layer/activity_metrics.py",
data_layer_function="get_training_inter_session_gap_data",
source_tables=["activity_log"],
semantic_contract=(
"Sessions chronologisch; Zeitstempel = date + start_time oder 12:00. "
"Lücken in Stunden zwischen aufeinanderfolgenden Starts."
),
business_meaning="KI: ausreichend Erholung zwischen Belastungen? Doppelbelastung?",
unit="Markdown",
time_window="default 28 Tage",
output_type=OutputType.TEXT_SUMMARY,
placeholder_type=PlaceholderType.INTERPRETED,
format_hint="Kurzer Markdown-Fließtext",
example_output="**Pause zwischen Trainings** …",
minimum_data_requirements="Mindestens 2 Sessions",
quality_filter_policy=None,
confidence_logic="calculate_confidence über Session-Anzahl",
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="insufficient_data",
legacy_display="Zu wenige Trainings",
),
known_limitations=(
"Kein Unterscheidung aktiv/passiv außerhalb activity_log. "
"Fehlende Uhrzeit verzerrt Reihenfolge am selben Tag nicht (nur ein künstlicher Mittag)."
),
layer_1_decision="activity_metrics.get_training_inter_session_gap_data",
layer_2a_decision="get_training_inter_session_gap_md",
layer_2b_reuse_possible=True,
architecture_alignment="Phase 0c",
issue_53_alignment="Layer 1",
evidence={},
)
for f in (
"key", "category", "description", "resolver_module", "resolver_function",
"data_layer_module", "data_layer_function", "source_tables", "semantic_contract",
"unit", "time_window", "output_type", "placeholder_type", "format_hint",
"example_output", "minimum_data_requirements", "confidence_logic",
"missing_value_policy", "layer_1_decision", "layer_2a_decision",
"layer_2b_reuse_possible", "architecture_alignment", "issue_53_alignment",
):
_ev(md_gap, f)
_ev(md_gap, "business_meaning", EvidenceType.DRAFT_DERIVED)
_ev(md_gap, "known_limitations", EvidenceType.MIXED)
register_placeholder(md_gap)
pj = PlaceholderMetadata(
key="training_sessions_recent_json",
category="Aktivität",
description=(
"JSON: ISO-Wochen mit Sessions (activity_log-Kopf) plus session_metrics[] — gemergte Profil-Metriken "
"(dynamische Keys)"
),
resolver_module="backend/placeholder_resolver.py",
resolver_function="_safe_json",
data_layer_module="backend/data_layer/activity_metrics.py",
data_layer_function="get_training_sessions_recent_weeks_data",
source_tables=["activity_log", "training_types", "activity_session_metrics", "training_parameters"],
semantic_contract=(
"Root: weeks[] mit week_iso; sessions[] pro Einheit u. a. id, date, activity_type, "
"duration_min, kcal_active, hr_avg, hr_max, rpe, training_category, training_type_name, "
"session_metrics[]. "
"session_metrics: effektive Liste nach merge_column_backed_and_eav_metrics — Einträge mit "
"training_parameter_id, key, data_type, unit, value, name_de/name_en, description_de/description_en; "
"nur Parameter aus Attributschema "
"(training_category_parameter + training_type_parameter Overrides), keys sortiert. "
"Kanon Lesen: activity_log-Spalte vor EAV bei Konflikt. "
"meta: weeks_requested, days_loaded, session_count, confidence. "
"Default ca. 4 ISO-Wochen (28 Tage Rohdatenfenster)."
),
business_meaning="Rohkontext für wochenweise Auswertung (Erholung, Intensität) in der KI",
unit="JSON string",
time_window="4 ISO-Wochen (28 Tage Datenfenster)",
output_type=OutputType.JSON,
placeholder_type=PlaceholderType.RAW_DATA,
format_hint="JSON-Objekt als String",
example_output='{"weeks":[...],"meta":{...}}',
minimum_data_requirements="Optional Sessions; meta.confidence bei leer insufficient",
quality_filter_policy=None,
confidence_logic="meta.confidence aus Session-Anzahl",
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="no_data",
legacy_display="{}",
),
known_limitations=(
"Token-Länge bei vielen Sessions. training_type_name nur bei gesetztem training_type_id. "
"session_metrics oft [] (kein Typ, kein Profil, keine gespeicherten Werte). "
"Anzahl und Namen der Metrik-Keys sind instanz-/adminabhängig — JSON nicht als festes Schema "
"für Downstream-Parsing harter Logik verwenden. "
"Für KI-Semantik zusätzlich {{training_parameters_glossary_md}} (gesamter aktiver Katalog) in den Prompt legen. "
"Composite-Parameter (JSON in EAV) noch nicht im MVP expandiert; ggf. Roh-value_text in späterer Phase."
),
layer_1_decision="activity_metrics.get_training_sessions_recent_weeks_data",
layer_2a_decision="_safe_json('training_sessions_recent_json')",
layer_2b_reuse_possible=True,
architecture_alignment="Phase 0c",
issue_53_alignment="Layer 1",
evidence={},
)
for f in (
"key", "category", "description", "resolver_module", "resolver_function",
"data_layer_module", "data_layer_function", "source_tables", "semantic_contract",
"unit", "time_window", "output_type", "placeholder_type", "format_hint",
"example_output", "minimum_data_requirements", "confidence_logic",
"missing_value_policy", "layer_1_decision", "layer_2a_decision",
"layer_2b_reuse_possible", "architecture_alignment", "issue_53_alignment",
):
_ev(pj, f)
_ev(pj, "business_meaning", EvidenceType.DRAFT_DERIVED)
_ev(pj, "known_limitations", EvidenceType.MIXED)
register_placeholder(pj)
md_gloss = PlaceholderMetadata(
key="training_parameters_glossary_md",
category="Aktivität",
description=(
"Markdown-Tabelle: alle aktiven training_parameters (key, DE/EN, Beschreibungen, Typ, Einheit, Kategorie). "
"Ergänzung zu training_sessions_recent_json für KI (Bedeutung dynamischer Metrik-Keys)."
),
resolver_module="backend/placeholder_resolver.py",
resolver_function="get_training_parameters_glossary_md",
data_layer_module="backend/data_layer/activity_metrics.py",
data_layer_function="get_training_parameters_ki_glossary_data",
source_tables=["training_parameters"],
semantic_contract=(
"SELECT auf training_parameters WHERE is_active; sortiert category, key. "
"profile_id-Parameter im Resolver reserviert, aktuell globaler Katalog."
),
business_meaning="KI: Legende zu session_metrics-Keys und Custom-Parametern",
unit="Markdown",
time_window="n/a (Katalog-Snapshot)",
output_type=OutputType.TEXT_SUMMARY,
placeholder_type=PlaceholderType.INTERPRETED,
format_hint="GitHub-Flavored Markdown-Tabelle",
example_output="| Feld (key) | DE | EN | Beschreibung DE | … |",
minimum_data_requirements="Optional leer → Kurztext statt Tabelle",
quality_filter_policy=None,
confidence_logic="Immer verfügbar wenn DB erreichbar",
missing_value_policy=MissingValuePolicy(
available=False,
value_raw=None,
missing_reason="no_data",
legacy_display="Keine aktiven Trainingsparameter im Katalog.",
),
known_limitations=(
"Keine profil-spezifische Einschränkung auf tatsächlich genutzte Keys (V2). "
"Tabellen können bei großem Katalog lang werden."
),
layer_1_decision="activity_metrics.get_training_parameters_ki_glossary_data",
layer_2a_decision="get_training_parameters_glossary_md",
layer_2b_reuse_possible=True,
architecture_alignment="Phase 0c",
issue_53_alignment="Layer 2a",
evidence={},
)
for f in (
"key", "category", "description", "resolver_module", "resolver_function",
"data_layer_module", "data_layer_function", "source_tables", "semantic_contract",
"unit", "time_window", "output_type", "placeholder_type", "format_hint",
"example_output", "minimum_data_requirements", "confidence_logic",
"missing_value_policy", "layer_1_decision", "layer_2a_decision",
"layer_2b_reuse_possible", "architecture_alignment", "issue_53_alignment",
):
_ev(md_gloss, f)
_ev(md_gloss, "business_meaning", EvidenceType.DRAFT_DERIVED)
_ev(md_gloss, "known_limitations", EvidenceType.MIXED)
register_placeholder(md_gloss)
register_activity_session_insights()