Erste Version Platzhalter EAV #86
|
|
@ -174,6 +174,74 @@ def resolve_activity_attribute_schema(
|
||||||
return merge_parameter_schema_rows(category_rows, type_rows)
|
return merge_parameter_schema_rows(category_rows, type_rows)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_activity_attribute_schema_for_csv_import(
|
||||||
|
cur,
|
||||||
|
training_category: Optional[str],
|
||||||
|
training_type_id: Optional[int],
|
||||||
|
mapped: Mapping[str, Any],
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Wie resolve_activity_attribute_schema, plus alle aktiven training_parameters, deren key in
|
||||||
|
``mapped`` vorkommt (nicht Modul-Registry), aber nicht in Kategorie/Typ-Profil — z. B. wenn
|
||||||
|
``activity_type_mappings`` fehlt oder der Parameter nur für andere Typen gebucht ist.
|
||||||
|
|
||||||
|
Damit schreibt der Universal-CSV-Import EAV für gültig gemappte Zielfelder wie bei cd29c7d,
|
||||||
|
sobald der Parameter in ``training_parameters`` existiert.
|
||||||
|
"""
|
||||||
|
base = resolve_activity_attribute_schema(cur, training_category, training_type_id)
|
||||||
|
by_key: Dict[str, Dict[str, Any]] = {s["key"]: s for s in base}
|
||||||
|
|
||||||
|
for k, raw in mapped.items():
|
||||||
|
if raw is None or raw == "":
|
||||||
|
continue
|
||||||
|
if k in by_key:
|
||||||
|
continue
|
||||||
|
if k in ACTIVITY_MODULE_REGISTRY_FIELD_KEYS:
|
||||||
|
continue
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
name_de,
|
||||||
|
name_en,
|
||||||
|
category AS param_category,
|
||||||
|
data_type,
|
||||||
|
unit,
|
||||||
|
validation_rules,
|
||||||
|
source_field
|
||||||
|
FROM training_parameters
|
||||||
|
WHERE key = %s AND is_active = true
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(k,),
|
||||||
|
)
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row:
|
||||||
|
continue
|
||||||
|
pid = int(row["id"])
|
||||||
|
if any(s["training_parameter_id"] == pid for s in by_key.values()):
|
||||||
|
continue
|
||||||
|
by_key[k] = {
|
||||||
|
"training_parameter_id": pid,
|
||||||
|
"key": row["key"],
|
||||||
|
"name_de": row["name_de"],
|
||||||
|
"name_en": row["name_en"],
|
||||||
|
"param_category": row["param_category"],
|
||||||
|
"data_type": row["data_type"],
|
||||||
|
"unit": row["unit"],
|
||||||
|
"validation_rules": row["validation_rules"] or {},
|
||||||
|
"source_field": row["source_field"],
|
||||||
|
"sort_order": 100_000,
|
||||||
|
"required": False,
|
||||||
|
"ui_group": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
out = list(by_key.values())
|
||||||
|
out.sort(key=lambda x: (x.get("sort_order", 0), x["key"]))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _validation_rules_dict(raw: Any) -> Dict[str, Any]:
|
def _validation_rules_dict(raw: Any) -> Dict[str, Any]:
|
||||||
if isinstance(raw, dict):
|
if isinstance(raw, dict):
|
||||||
return raw
|
return raw
|
||||||
|
|
@ -286,7 +354,9 @@ def apply_activity_mapped_column_aliases(
|
||||||
training_category: Optional[str],
|
training_category: Optional[str],
|
||||||
training_type_id: Optional[int],
|
training_type_id: Optional[int],
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
schema = resolve_activity_attribute_schema(cur, training_category, training_type_id)
|
schema = resolve_activity_attribute_schema_for_csv_import(
|
||||||
|
cur, training_category, training_type_id, mapped
|
||||||
|
)
|
||||||
return apply_activity_mapped_column_aliases_from_schema(mapped, schema)
|
return apply_activity_mapped_column_aliases_from_schema(mapped, schema)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -311,7 +381,9 @@ 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
|
||||||
schema = resolve_activity_attribute_schema(cur, training_category, training_type_id)
|
schema = resolve_activity_attribute_schema_for_csv_import(
|
||||||
|
cur, training_category, training_type_id, mapped
|
||||||
|
)
|
||||||
for spec in schema:
|
for spec in schema:
|
||||||
pkey = spec["key"]
|
pkey = spec["key"]
|
||||||
if pkey not in mapped:
|
if pkey not in mapped:
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ from data_layer.activity_session_metrics import (
|
||||||
merge_parameter_schema_rows,
|
merge_parameter_schema_rows,
|
||||||
replace_activity_session_metrics,
|
replace_activity_session_metrics,
|
||||||
resolve_activity_attribute_schema,
|
resolve_activity_attribute_schema,
|
||||||
|
resolve_activity_attribute_schema_for_csv_import,
|
||||||
upsert_session_metrics_from_csv_mapped,
|
upsert_session_metrics_from_csv_mapped,
|
||||||
_row_value_tuple,
|
_row_value_tuple,
|
||||||
_validate_single_value,
|
_validate_single_value,
|
||||||
|
|
@ -323,6 +324,83 @@ def test_apply_mapped_aliases_does_not_overwrite_existing_column():
|
||||||
assert out["hr_avg"] == 120
|
assert out["hr_avg"] == 120
|
||||||
|
|
||||||
|
|
||||||
|
@patch("data_layer.activity_session_metrics.resolve_activity_attribute_schema", return_value=[])
|
||||||
|
def test_resolve_csv_import_adds_mapped_keys_from_training_parameters(mock_resolve):
|
||||||
|
"""Ohne Kategorie/Typ-Profil: gemappte Keys aus training_parameters ergänzen."""
|
||||||
|
|
||||||
|
class Cur:
|
||||||
|
def __init__(self):
|
||||||
|
self.sql = ""
|
||||||
|
|
||||||
|
def execute(self, sql, params=None):
|
||||||
|
self.sql = sql
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
def fetchone(self):
|
||||||
|
if "training_parameters" in self.sql:
|
||||||
|
return {
|
||||||
|
"id": 7,
|
||||||
|
"key": "cadence",
|
||||||
|
"name_de": "Kadenz",
|
||||||
|
"name_en": "Cadence",
|
||||||
|
"param_category": "physical",
|
||||||
|
"data_type": "integer",
|
||||||
|
"unit": "rpm",
|
||||||
|
"validation_rules": {},
|
||||||
|
"source_field": None,
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
cur = Cur()
|
||||||
|
out = resolve_activity_attribute_schema_for_csv_import(cur, None, None, {"cadence": 90})
|
||||||
|
assert len(out) == 1
|
||||||
|
assert out[0]["key"] == "cadence"
|
||||||
|
assert out[0]["training_parameter_id"] == 7
|
||||||
|
mock_resolve.assert_called_once_with(cur, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("data_layer.activity_session_metrics.resolve_activity_attribute_schema", return_value=[])
|
||||||
|
def test_upsert_csv_inserts_eav_when_only_mapped_training_parameter(mock_resolve):
|
||||||
|
class Cur:
|
||||||
|
def __init__(self):
|
||||||
|
self.asm_inserts = 0
|
||||||
|
self.sql = ""
|
||||||
|
|
||||||
|
def execute(self, sql, params=None):
|
||||||
|
self.sql = sql
|
||||||
|
self.params = params
|
||||||
|
if "INSERT INTO activity_session_metrics" in sql:
|
||||||
|
self.asm_inserts += 1
|
||||||
|
|
||||||
|
def fetchone(self):
|
||||||
|
if "activity_log" in self.sql:
|
||||||
|
return {"profile_id": "00000000-0000-0000-0000-000000000001"}
|
||||||
|
if "training_parameters" in self.sql:
|
||||||
|
return {
|
||||||
|
"id": 7,
|
||||||
|
"key": "cadence",
|
||||||
|
"name_de": "K",
|
||||||
|
"name_en": "K",
|
||||||
|
"param_category": "physical",
|
||||||
|
"data_type": "integer",
|
||||||
|
"unit": None,
|
||||||
|
"validation_rules": {"min": 0, "max": 300},
|
||||||
|
"source_field": None,
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
cur = Cur()
|
||||||
|
upsert_session_metrics_from_csv_mapped(
|
||||||
|
cur,
|
||||||
|
"00000000-0000-0000-0000-000000000001",
|
||||||
|
"00000000-0000-0000-0000-000000000002",
|
||||||
|
{"cadence": 90},
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
assert cur.asm_inserts == 1
|
||||||
|
|
||||||
|
|
||||||
@patch("data_layer.activity_session_metrics.resolve_activity_attribute_schema")
|
@patch("data_layer.activity_session_metrics.resolve_activity_attribute_schema")
|
||||||
def test_upsert_csv_skips_parameter_with_source_field(mock_schema):
|
def test_upsert_csv_skips_parameter_with_source_field(mock_schema):
|
||||||
"""Kein INSERT in activity_session_metrics für Spalten-Parameter (avg_hr → hr_avg)."""
|
"""Kein INSERT in activity_session_metrics für Spalten-Parameter (avg_hr → hr_avg)."""
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user