From 2a26e4fecf5274937d54b02d8d01a808209ed125 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 16 Apr 2026 12:35:55 +0200 Subject: [PATCH] refactor: Remove source field handling from activity session metrics logic - Eliminated checks for `source_field` in the `replace_activity_session_metrics` function to streamline EAV row replacement, ensuring consistency with existing logic. - Updated frontend logic to simplify the filtering of metrics during payload construction, enhancing maintainability. - Removed outdated unit tests related to `source_field` handling, reflecting the updated logic in the codebase. --- .../data_layer/activity_session_metrics.py | 9 --- .../tests/test_activity_session_metrics.py | 56 ------------------- frontend/src/pages/ActivityPage.jsx | 41 +------------- 3 files changed, 2 insertions(+), 104 deletions(-) diff --git a/backend/data_layer/activity_session_metrics.py b/backend/data_layer/activity_session_metrics.py index af434da..a9fde12 100644 --- a/backend/data_layer/activity_session_metrics.py +++ b/backend/data_layer/activity_session_metrics.py @@ -505,9 +505,6 @@ def replace_activity_session_metrics( ) -> List[Dict[str, Any]]: """ Full replace of EAV rows for this session. metrics: [{ "parameter_key": str, "value": ... }, ...] - - Parameter mit gesetztem ``source_field`` werden nicht in EAV persistiert (kanonisch ``activity_log``), - konsistent zu ``upsert_session_metrics_from_csv_mapped`` — verhindert doppelte Speicherung nach PUT. """ cur.execute( """ @@ -538,9 +535,6 @@ def replace_activity_session_metrics( if not s["required"]: continue itk = s["key"] - sf_req = s.get("source_field") - if sf_req is not None and str(sf_req).strip(): - continue hit = payload_by_key.get(itk) if hit is None or hit.get("value") is None: raise ActivitySessionMetricsError(400, f"Pflichtfeld fehlt: {itk}") @@ -553,9 +547,6 @@ def replace_activity_session_metrics( for item in metrics: k = str(item["parameter_key"]).strip() spec = by_key[k] - sf_raw = spec.get("source_field") - if sf_raw is not None and str(sf_raw).strip(): - continue val = item.get("value") if val is None: if spec["required"]: diff --git a/backend/tests/test_activity_session_metrics.py b/backend/tests/test_activity_session_metrics.py index 69622e5..8f7b652 100644 --- a/backend/tests/test_activity_session_metrics.py +++ b/backend/tests/test_activity_session_metrics.py @@ -10,7 +10,6 @@ from data_layer.activity_session_metrics import ( enrich_sessions_with_metrics, merge_column_backed_and_eav_metrics, merge_parameter_schema_rows, - replace_activity_session_metrics, resolve_activity_attribute_schema, upsert_session_metrics_from_csv_mapped, _row_value_tuple, @@ -378,58 +377,3 @@ def test_upsert_csv_writes_eav_when_no_source_field(mock_schema): 1, ) assert cur.asm_inserts == 1 - - -@patch("data_layer.activity_session_metrics.fetch_activity_session_metrics", return_value=[]) -@patch("data_layer.activity_session_metrics.resolve_activity_attribute_schema") -def test_replace_metrics_skips_inserts_for_source_field_column(mock_schema, mock_fetch): - """PUT /metrics: keine EAV-Zeile für Parameter mit source_field (kanonisch activity_log).""" - mock_schema.return_value = [ - { - "key": "avg_hr", - "training_parameter_id": 1, - "data_type": "integer", - "validation_rules": {}, - "source_field": "hr_avg", - "required": False, - }, - { - "key": "custom_x", - "training_parameter_id": 2, - "data_type": "string", - "validation_rules": {}, - "source_field": None, - "required": False, - }, - ] - - class Cur: - def __init__(self): - self.asm_inserts = 0 - - def execute(self, sql, params=None): - self._sql = sql - if "INSERT INTO activity_session_metrics" in sql: - self.asm_inserts += 1 - - def fetchone(self): - if "FROM activity_log" in getattr(self, "_sql", ""): - return { - "id": "00000000-0000-0000-0000-000000000002", - "profile_id": "00000000-0000-0000-0000-000000000001", - "training_category": "cardio", - "training_type_id": 1, - } - return None - - cur = Cur() - replace_activity_session_metrics( - cur, - "00000000-0000-0000-0000-000000000001", - "00000000-0000-0000-0000-000000000002", - [ - {"parameter_key": "avg_hr", "value": 120}, - {"parameter_key": "custom_x", "value": "y"}, - ], - ) - assert cur.asm_inserts == 1 diff --git a/frontend/src/pages/ActivityPage.jsx b/frontend/src/pages/ActivityPage.jsx index 6ccb5f2..50ea792 100644 --- a/frontend/src/pages/ActivityPage.jsx +++ b/frontend/src/pages/ActivityPage.jsx @@ -96,28 +96,6 @@ const ACTIVITY_LOG_PAYLOAD_KEYS = new Set([ 'training_subcategory', ]) -/** activity_log-Spalten, die bereits im EntryForm-Kopf bearbeitet werden — kein zweites Feld unter „Weitere Kennwerte“. */ -const ENTRY_FORM_SOURCE_COLUMNS = new Set([ - 'date', - 'start_time', - 'end_time', - 'duration_min', - 'kcal_active', - 'kcal_resting', - 'hr_avg', - 'hr_max', - 'rpe', - 'notes', -]) - -function schemaRowsForProfileExtras(schemaList) { - return (Array.isArray(schemaList) ? schemaList : []).filter((s) => { - const sf = s && s.source_field - if (!sf || !String(sf).trim()) return true - return !ENTRY_FORM_SOURCE_COLUMNS.has(String(sf).trim()) - }) -} - function empty() { return { date: dayjs().format('YYYY-MM-DD'), @@ -135,10 +113,6 @@ function empty() { function buildMetricsPayload(schema, draft) { const out = [] for (const s of schema) { - const sf = s.source_field - if (sf && String(sf).trim() && ENTRY_FORM_SOURCE_COLUMNS.has(String(sf).trim())) { - continue - } const raw = draft[s.key] if (s.data_type === 'boolean') { if (raw === '' || raw === null || raw === undefined) { @@ -171,21 +145,10 @@ function buildMetricsPayload(schema, draft) { } function SessionMetricsFields({ schema, values, setValues, metrics }) { - const fullSchema = Array.isArray(schema) ? schema : [] - const schemaList = schemaRowsForProfileExtras(fullSchema) + const schemaList = Array.isArray(schema) ? schema : [] const metricRows = Array.isArray(metrics) ? metrics : [] const schemaKeys = new Set(schemaList.map((s) => s.key)) - const hiddenHeadlineParamKeys = new Set( - fullSchema - .filter((s) => s.source_field && ENTRY_FORM_SOURCE_COLUMNS.has(String(s.source_field).trim())) - .map((s) => s.key) - ) - const orphanMetrics = metricRows.filter((row) => { - if (!row || !row.key) return false - if (schemaKeys.has(row.key)) return false - if (hiddenHeadlineParamKeys.has(row.key)) return false - return true - }) + const orphanMetrics = metricRows.filter((row) => row && row.key && !schemaKeys.has(row.key)) if (schemaList.length === 0 && orphanMetrics.length === 0) return null const set = (k, v) => setValues((prev) => ({ ...prev, [k]: v }))