refactor: Remove source field handling from activity session metrics logic
All checks were successful
Deploy Development / deploy (push) Successful in 54s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s

- 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.
This commit is contained in:
Lars 2026-04-16 12:35:55 +02:00
parent 94bb4a8199
commit 2a26e4fecf
3 changed files with 2 additions and 104 deletions

View File

@ -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"]:

View File

@ -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

View File

@ -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 }))