diff --git a/backend/data_layer/activity_data_canon.py b/backend/data_layer/activity_data_canon.py index 17ec223..1582173 100644 --- a/backend/data_layer/activity_data_canon.py +++ b/backend/data_layer/activity_data_canon.py @@ -1,8 +1,10 @@ """ Kanonische Aufteilung activity_log vs. EAV für Aktivitätssessions. -Single Source für: welche Felder das CSV-/Registry-Modul „activity“ direkt in activity_log schreibt, -und welche training_parameters primär über EAV laufen (mit optionalem Lesefallback auf Legacy-Spalten). +- **Kern / Mapping-Ziele für activity_log:** ausschließlich die Keys aus + ``csv_parser.module_registry.MODULE_DEFINITIONS["activity"].fields`` (keine zweite hartcodierte Liste). +- **Alle anderen Attribute:** ``training_parameters`` + Attributprofil (Kategorie/Typ) → EAV; + Lesefallback für bekannte Legacy-Spalten siehe unten. Normative Doku: .claude/docs/technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md """ @@ -10,25 +12,22 @@ from __future__ import annotations from typing import Dict, Final -# ── activity_log: Modul „activity“ (Universal-CSV-Kern) ─────────────────────── -# Nur diese Keys erscheinen in csv_parser.module_registry MODULE_DEFINITIONS["activity"].fields. -# Alles Weitere: training_parameters + EAV (Import über upsert_session_metrics_from_csv_mapped). -ACTIVITY_MODULE_REGISTRY_FIELD_KEYS: Final[frozenset[str]] = frozenset( - { - "date", - "start_time", - "end_time", - "activity_type", - "duration_min", - "kcal_active", - "kcal_resting", - "distance_km", - "hr_avg", - "hr_max", - "rpe", - "notes", - } -) +from csv_parser.module_registry import get_module_definition + + +def get_activity_module_registry_field_keys() -> frozenset[str]: + """Keys des Universal-CSV-Moduls ``activity`` (= feste activity_log-Kernfelder / Mapping-Ziele).""" + mod = get_module_definition("activity") + if not mod: + return frozenset() + return frozenset((mod.get("fields") or {}).keys()) + + +# Gleiche Menge wie ``MODULE_DEFINITIONS["activity"].fields`` — zur Laufzeit aus der Registry abgeleitet. +ACTIVITY_MODULE_REGISTRY_FIELD_KEYS: Final[frozenset[str]] = get_activity_module_registry_field_keys() + +# Teil-UPDATEs (Import): alle Kernfelder außer ``date`` (Identität / Duplikat-Key). +ACTIVITY_LOG_PATCHABLE_COLUMNS: Final[frozenset[str]] = ACTIVITY_MODULE_REGISTRY_FIELD_KEYS - {"date"} # Parameter-Keys (training_parameters.key), die primär in EAV geführt werden; source_field nach Migration 057 NULL. # Lesefallback: activity_log-Spalte unter ACTIVITY_LOG_LEGACY_COLUMN_FOR_EAV_PRIMARY_PARAM, falls EAV leer. @@ -59,20 +58,3 @@ ACTIVITY_LOG_LEGACY_COLUMN_FOR_EAV_PRIMARY_PARAM: Final[Dict[str, str]] = { "kcal_per_km": "kcal_per_km", } -# Spalten, die mit training_parameters.source_field (nach Migration 057) noch activity_log abbilden. -# Erweiterte Metriken sind EAV-primär — nicht hier auflisten. -ACTIVITY_LOG_PATCHABLE_COLUMNS: Final[frozenset[str]] = frozenset( - { - "start_time", - "end_time", - "activity_type", - "duration_min", - "kcal_active", - "kcal_resting", - "hr_avg", - "hr_max", - "distance_km", - "rpe", - "notes", - } -) diff --git a/backend/data_layer/activity_persistence_orchestrator.py b/backend/data_layer/activity_persistence_orchestrator.py index 6085550..6a390dd 100644 --- a/backend/data_layer/activity_persistence_orchestrator.py +++ b/backend/data_layer/activity_persistence_orchestrator.py @@ -15,6 +15,7 @@ from typing import Any, Dict, List, Mapping, Optional from models import ActivityEntry from csv_parser.module_registry import get_module_definition +from data_layer.activity_data_canon import get_activity_module_registry_field_keys logger = logging.getLogger(__name__) @@ -50,10 +51,8 @@ _ACTIVITY_CSV_REGISTRY_EXCLUDE = frozenset({"date", "start_time", "end_time", "a def activity_registry_field_keys() -> frozenset[str]: - mod = get_module_definition("activity") - if not mod: - return frozenset() - return frozenset((mod.get("fields") or {}).keys()) + """Gleiche Menge wie ``ACTIVITY_MODULE_REGISTRY_FIELD_KEYS`` (Registry als Single Source).""" + return get_activity_module_registry_field_keys() def activity_csv_registry_updates_from_mapped(mapped: Mapping[str, Any]) -> Dict[str, Any]: diff --git a/backend/data_layer/activity_session_metrics.py b/backend/data_layer/activity_session_metrics.py index b5ef19d..3b79734 100644 --- a/backend/data_layer/activity_session_metrics.py +++ b/backend/data_layer/activity_session_metrics.py @@ -9,8 +9,10 @@ import logging from decimal import Decimal from typing import Any, Dict, List, Mapping, Optional, Sequence -from csv_parser.module_registry import get_module_definition -from data_layer.activity_data_canon import ACTIVITY_LOG_LEGACY_COLUMN_FOR_EAV_PRIMARY_PARAM +from data_layer.activity_data_canon import ( + ACTIVITY_LOG_LEGACY_COLUMN_FOR_EAV_PRIMARY_PARAM, + ACTIVITY_MODULE_REGISTRY_FIELD_KEYS, +) logger = logging.getLogger(__name__) @@ -262,8 +264,6 @@ def upsert_session_metrics_from_csv_mapped( row = cur.fetchone() if not row or str(row["profile_id"]) != str(profile_id): return - mod = get_module_definition("activity") or {} - activity_registry_keys = frozenset((mod.get("fields") or {}).keys()) schema = resolve_activity_attribute_schema(cur, training_category, training_type_id) for spec in schema: pkey = spec["key"] @@ -272,7 +272,7 @@ def upsert_session_metrics_from_csv_mapped( raw = mapped[pkey] if raw is None or raw == "": continue - if pkey in activity_registry_keys: + if pkey in ACTIVITY_MODULE_REGISTRY_FIELD_KEYS: continue tid = spec["training_parameter_id"] dt = spec["data_type"]