diff --git a/backend/data_layer/activity_session_metrics.py b/backend/data_layer/activity_session_metrics.py index 84fe49c..62783ef 100644 --- a/backend/data_layer/activity_session_metrics.py +++ b/backend/data_layer/activity_session_metrics.py @@ -260,15 +260,18 @@ def upsert_session_metrics_from_csv_mapped( nur in ``activity_log``-Kernfeldern). Kernfelder schreibt der Executor nach ``activity_log``; hier keine EAV-Zeilen für Registry-Keys. - Bei gesetztem ``training_parameters.source_field`` ist die Spalte kanonisch — kein EAV-Schreiben. + + Hat ein Parameter ``source_field`` (Semantik aus ``activity_log``), wird EAV nur dann **nicht** + geschrieben, wenn diese Spalte nach dem Import bereits befüllt ist — sonst gäbe es doppelte + Speicherung und der Merge würde ohnehin die Spalte bevorzugen. Ist die Spalte leer (z. B. Feld + nur noch über EAV / Custom-Mapping, ohne Registry-Patch), schreibt der Import den Wert aus + ``mapped`` nach EAV — analog zum Lesepfad (Spalte zuerst, sonst EAV). """ - cur.execute( - "SELECT profile_id FROM activity_log WHERE id = %s", - (activity_log_id,), - ) + cur.execute("SELECT * FROM activity_log WHERE id = %s", (activity_log_id,)) row = cur.fetchone() if not row or str(row["profile_id"]) != str(profile_id): return + header = dict(row) schema = resolve_activity_attribute_schema(cur, training_category, training_type_id) for spec in schema: pkey = spec["key"] @@ -281,7 +284,9 @@ def upsert_session_metrics_from_csv_mapped( continue sf_raw = spec.get("source_field") if sf_raw is not None and str(sf_raw).strip(): - continue + col = str(sf_raw).strip() + if col in header and header[col] is not None: + continue tid = spec["training_parameter_id"] dt = spec["data_type"] rules = _validation_rules_dict(spec["validation_rules"]) diff --git a/backend/tests/test_activity_session_metrics.py b/backend/tests/test_activity_session_metrics.py index 8cb1bdf..21dd2e5 100644 --- a/backend/tests/test_activity_session_metrics.py +++ b/backend/tests/test_activity_session_metrics.py @@ -329,7 +329,10 @@ def test_upsert_csv_skips_eav_when_source_field_maps_activity_log(mock_schema): self.asm_inserts += 1 def fetchone(self): - return {"profile_id": "00000000-0000-0000-0000-000000000001"} + return { + "profile_id": "00000000-0000-0000-0000-000000000001", + "hr_avg": 130, + } cur = Cur() upsert_session_metrics_from_csv_mapped( @@ -343,6 +346,45 @@ def test_upsert_csv_skips_eav_when_source_field_maps_activity_log(mock_schema): assert cur.asm_inserts == 0 +@patch("data_layer.activity_session_metrics.resolve_activity_attribute_schema") +def test_upsert_csv_writes_eav_when_source_field_but_column_empty(mock_schema): + """source_field gesetzt, activity_log-Spalte leer — Wert aus mapped nur in EAV (kein Registry-Patch).""" + mock_schema.return_value = [ + { + "key": "avg_hr", + "training_parameter_id": 42, + "data_type": "integer", + "validation_rules": {"min": 30, "max": 220}, + "source_field": "hr_avg", + } + ] + + class Cur: + def __init__(self): + self.asm_inserts = 0 + + def execute(self, sql, params=None): + if "INSERT INTO activity_session_metrics" in sql: + self.asm_inserts += 1 + + def fetchone(self): + return { + "profile_id": "00000000-0000-0000-0000-000000000001", + "hr_avg": None, + } + + cur = Cur() + upsert_session_metrics_from_csv_mapped( + cur, + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002", + {"avg_hr": 130}, + "cardio", + 1, + ) + assert cur.asm_inserts == 1 + + @patch("data_layer.activity_session_metrics.resolve_activity_attribute_schema") def test_upsert_csv_writes_eav_when_no_source_field(mock_schema): mock_schema.return_value = [ @@ -364,7 +406,7 @@ def test_upsert_csv_writes_eav_when_no_source_field(mock_schema): self.asm_inserts += 1 def fetchone(self): - return {"profile_id": "00000000-0000-0000-0000-000000000001"} + return {"profile_id": "00000000-0000-0000-0000-000000000001", "hr_avg": None} cur = Cur() upsert_session_metrics_from_csv_mapped( @@ -390,7 +432,7 @@ def test_upsert_csv_skips_eav_when_mapped_key_not_in_profile_schema(mock_resolve self.asm_inserts += 1 def fetchone(self): - return {"profile_id": "00000000-0000-0000-0000-000000000001"} + return {"profile_id": "00000000-0000-0000-0000-000000000001", "hr_avg": None} cur = Cur() upsert_session_metrics_from_csv_mapped(