- Updated the CSV import architecture to clarify the distinction between import and data layer responsibilities, as outlined in the new section of ARCHITECTURE.md. - Enhanced the build_row_after_mapping function to include module-specific context for improved data processing. - Introduced source unit options in the admin CSV template editor to facilitate user-defined conversions, improving flexibility in handling various data formats. - Added new tests to validate the handling of source units and ensure accurate conversions during CSV imports. - Updated module definitions to include unit specifications for nutritional and activity data fields, enhancing data integrity.
102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
"""
|
|
Kanonische Speichereinheiten pro CSV-Zielfeld (module_registry: field.unit) und
|
|
abwählbare Quelleinheiten → Faktor für type_conversions.source_unit.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from csv_parser.module_registry import get_module_definition
|
|
|
|
# 1 kcal = 4.184 kJ (IEC/ISO 80000)
|
|
_KJ_TO_KCAL = 1.0 / 4.184
|
|
|
|
# — Energie (Ziel kcal) —
|
|
_ENERGY: list[dict[str, Any]] = [
|
|
{"id": "kcal", "label": "Kilokalorien (kcal), wie in DB", "factor": 1.0},
|
|
{"id": "kj", "label": "Kilojoule (kJ) → kcal", "factor": _KJ_TO_KCAL},
|
|
{"id": "j", "label": "Joule (J) → kcal", "factor": _KJ_TO_KCAL / 1000.0},
|
|
]
|
|
|
|
# — Masse klein (Ziel g): Makronährstoffe —
|
|
_GRAM: list[dict[str, Any]] = [
|
|
{"id": "g", "label": "Gramm (g), wie in DB", "factor": 1.0},
|
|
{"id": "kg", "label": "Kilogramm (kg) → g", "factor": 1000.0},
|
|
{"id": "mg", "label": "Milligramm (mg) → g", "factor": 0.001},
|
|
]
|
|
|
|
# — Körpergewicht (Ziel kg) —
|
|
_KG: list[dict[str, Any]] = [
|
|
{"id": "kg", "label": "Kilogramm (kg), wie in DB", "factor": 1.0},
|
|
{"id": "g", "label": "Gramm (g) → kg", "factor": 0.001},
|
|
{"id": "lb", "label": "Pfund / lb → kg", "factor": 0.45359237},
|
|
{"id": "oz", "label": "Unze / oz → kg", "factor": 0.028349523125},
|
|
]
|
|
|
|
# — Strecke (Ziel km) —
|
|
_KM: list[dict[str, Any]] = [
|
|
{"id": "km", "label": "Kilometer (km), wie in DB", "factor": 1.0},
|
|
{"id": "m", "label": "Meter (m) → km", "factor": 0.001},
|
|
{"id": "mi", "label": "Meilen (mi) → km", "factor": 1.609344},
|
|
]
|
|
|
|
_UNIT_FAMILY: dict[str, list[dict[str, Any]]] = {
|
|
"kcal": _ENERGY,
|
|
"g": _GRAM,
|
|
"kg": _KG,
|
|
"km": _KM,
|
|
}
|
|
|
|
|
|
def get_canonical_unit(module: str, db_field: str) -> str | None:
|
|
mod = get_module_definition(module)
|
|
if not mod:
|
|
return None
|
|
finfo: dict[str, Any] | None = mod.get("fields", {}).get(db_field)
|
|
if not finfo:
|
|
return None
|
|
u = finfo.get("unit")
|
|
return str(u) if u else None
|
|
|
|
|
|
def source_unit_choices_for_field(module: str, db_field: str) -> list[dict[str, Any]]:
|
|
"""Optionen für GUI: id, label, canonical_unit, is_canonical (Umrechnung serverseitig)."""
|
|
cu = get_canonical_unit(module, db_field)
|
|
if not cu:
|
|
return []
|
|
choices = _UNIT_FAMILY.get(cu)
|
|
if not choices:
|
|
return []
|
|
return [
|
|
{
|
|
"id": c["id"],
|
|
"label": c["label"],
|
|
"canonical_unit": cu,
|
|
"is_canonical": c["id"] == cu,
|
|
}
|
|
for c in choices
|
|
]
|
|
|
|
|
|
def factor_source_to_canonical(module: str, db_field: str, source_unit: str | None) -> float:
|
|
"""
|
|
Multiplikator: CSV-Zahl * Faktor → Wert in kanonischer DB-Einheit.
|
|
Unbekannte/None/leer/Passthrough → 1.0
|
|
"""
|
|
if source_unit is None:
|
|
return 1.0
|
|
su = str(source_unit).strip().lower()
|
|
if not su:
|
|
return 1.0
|
|
cu = get_canonical_unit(module, db_field)
|
|
if not cu:
|
|
return 1.0
|
|
choices = _UNIT_FAMILY.get(cu)
|
|
if not choices:
|
|
return 1.0
|
|
for c in choices:
|
|
if str(c["id"]).lower() == su:
|
|
return float(c["factor"])
|
|
return 1.0
|