feat: Refactor activity data handling to use dynamic registry fields
All checks were successful
Deploy Development / deploy (push) Successful in 54s
Build Test / pytest-backend (push) Successful in 5s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s

- Replaced hardcoded keys in `activity_data_canon.py` with a dynamic retrieval method from the module registry, ensuring that `ACTIVITY_MODULE_REGISTRY_FIELD_KEYS` reflects the current configuration.
- Updated `activity_persistence_orchestrator.py` to utilize the new dynamic field retrieval function, enhancing consistency across the data layer.
- Modified `activity_session_metrics.py` to reference the dynamic field keys, improving maintainability and reducing redundancy in the codebase.
This commit is contained in:
Lars 2026-04-16 12:14:39 +02:00
parent 06f83e2ffc
commit 8d0a6dd487
3 changed files with 28 additions and 47 deletions

View File

@ -1,8 +1,10 @@
""" """
Kanonische Aufteilung activity_log vs. EAV für Aktivitätssessions. 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, - **Kern / Mapping-Ziele für activity_log:** ausschließlich die Keys aus
und welche training_parameters primär über EAV laufen (mit optionalem Lesefallback auf Legacy-Spalten). ``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 Normative Doku: .claude/docs/technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md
""" """
@ -10,25 +12,22 @@ from __future__ import annotations
from typing import Dict, Final from typing import Dict, Final
# ── activity_log: Modul „activity“ (Universal-CSV-Kern) ─────────────────────── from csv_parser.module_registry import get_module_definition
# 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( def get_activity_module_registry_field_keys() -> frozenset[str]:
{ """Keys des Universal-CSV-Moduls ``activity`` (= feste activity_log-Kernfelder / Mapping-Ziele)."""
"date", mod = get_module_definition("activity")
"start_time", if not mod:
"end_time", return frozenset()
"activity_type", return frozenset((mod.get("fields") or {}).keys())
"duration_min",
"kcal_active",
"kcal_resting", # Gleiche Menge wie ``MODULE_DEFINITIONS["activity"].fields`` — zur Laufzeit aus der Registry abgeleitet.
"distance_km", ACTIVITY_MODULE_REGISTRY_FIELD_KEYS: Final[frozenset[str]] = get_activity_module_registry_field_keys()
"hr_avg",
"hr_max", # Teil-UPDATEs (Import): alle Kernfelder außer ``date`` (Identität / Duplikat-Key).
"rpe", ACTIVITY_LOG_PATCHABLE_COLUMNS: Final[frozenset[str]] = ACTIVITY_MODULE_REGISTRY_FIELD_KEYS - {"date"}
"notes",
}
)
# Parameter-Keys (training_parameters.key), die primär in EAV geführt werden; source_field nach Migration 057 NULL. # 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. # 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", "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",
}
)

View File

@ -15,6 +15,7 @@ from typing import Any, Dict, List, Mapping, Optional
from models import ActivityEntry from models import ActivityEntry
from csv_parser.module_registry import get_module_definition 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__) 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]: def activity_registry_field_keys() -> frozenset[str]:
mod = get_module_definition("activity") """Gleiche Menge wie ``ACTIVITY_MODULE_REGISTRY_FIELD_KEYS`` (Registry als Single Source)."""
if not mod: return get_activity_module_registry_field_keys()
return frozenset()
return frozenset((mod.get("fields") or {}).keys())
def activity_csv_registry_updates_from_mapped(mapped: Mapping[str, Any]) -> Dict[str, Any]: def activity_csv_registry_updates_from_mapped(mapped: Mapping[str, Any]) -> Dict[str, Any]:

View File

@ -9,8 +9,10 @@ import logging
from decimal import Decimal from decimal import Decimal
from typing import Any, Dict, List, Mapping, Optional, Sequence 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 (
from data_layer.activity_data_canon import ACTIVITY_LOG_LEGACY_COLUMN_FOR_EAV_PRIMARY_PARAM ACTIVITY_LOG_LEGACY_COLUMN_FOR_EAV_PRIMARY_PARAM,
ACTIVITY_MODULE_REGISTRY_FIELD_KEYS,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -262,8 +264,6 @@ def upsert_session_metrics_from_csv_mapped(
row = cur.fetchone() row = cur.fetchone()
if not row or str(row["profile_id"]) != str(profile_id): if not row or str(row["profile_id"]) != str(profile_id):
return 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) schema = resolve_activity_attribute_schema(cur, training_category, training_type_id)
for spec in schema: for spec in schema:
pkey = spec["key"] pkey = spec["key"]
@ -272,7 +272,7 @@ def upsert_session_metrics_from_csv_mapped(
raw = mapped[pkey] raw = mapped[pkey]
if raw is None or raw == "": if raw is None or raw == "":
continue continue
if pkey in activity_registry_keys: if pkey in ACTIVITY_MODULE_REGISTRY_FIELD_KEYS:
continue continue
tid = spec["training_parameter_id"] tid = spec["training_parameter_id"]
dt = spec["data_type"] dt = spec["data_type"]