diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 8d30fab..4374563 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -3,9 +3,13 @@ name: Build Test on: push: branches: [main, develop] + workflow_run: + workflows: ["Deploy Development", "Deploy Production"] + types: [completed] jobs: pytest-backend-csv: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout @@ -13,7 +17,9 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + # Debian 12 ARM64 runner hat fuer 3.12 oft kein setup-python Build. + # 3.11 ist dort breit verfuegbar und fuer unsere Tests ausreichend. + python-version: "3.11" - name: Install dependencies run: | pip install -r backend/requirements.txt -r backend/requirements-dev.txt @@ -23,19 +29,33 @@ jobs: python -m pytest tests/test_csv_parser_core.py tests/test_csv_import_executor.py tests/test_mapping_suggest.py -q --tb=short lint-backend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" - name: Check backend syntax run: | - python3 -m py_compile /home/lars/docker/bodytrack/backend/main.py + python -m py_compile backend/main.py echo "✓ Backend syntax OK" build-frontend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" - name: Build frontend run: | - cd /home/lars/docker/bodytrack/frontend + cd frontend npm install npm run build echo "✓ Frontend build OK" diff --git a/backend/csv_parser/field_units.py b/backend/csv_parser/field_units.py index 33d9a18..f5579fe 100644 --- a/backend/csv_parser/field_units.py +++ b/backend/csv_parser/field_units.py @@ -83,12 +83,17 @@ def factor_source_to_canonical(module: str, db_field: str, source_unit: str | No """ Multiplikator: CSV-Zahl * Faktor → Wert in kanonischer DB-Einheit. Unbekannte/None/leer/Passthrough → 1.0 + + ``source_unit`` ``custom`` / ``none``: kein Registry-Faktor (1.0); freie Skalierung nur über + ``conversion_factor`` in type_conversions (JSON). """ if source_unit is None: return 1.0 su = str(source_unit).strip().lower() if not su: return 1.0 + if su in ("custom", "none"): + return 1.0 cu = get_canonical_unit(module, db_field) if not cu: return 1.0 diff --git a/backend/csv_parser/type_converter.py b/backend/csv_parser/type_converter.py index 75d8057..38fc8e5 100644 --- a/backend/csv_parser/type_converter.py +++ b/backend/csv_parser/type_converter.py @@ -312,8 +312,11 @@ def convert_value( - decimal_separator: ".", ",", "auto" — bei auto Heuristik EU/US-Mischformen. - formats: [ "yyyy-mm-dd", "%d.%m.%y", ... ] — weitere strptime-/Alias-Ketten. - dayfirst: true|false — nur für dateutil-Fallback; Standard: true dann false. - - source_unit: z. B. "kj", "kg" — Quelleinheit; Ziel aus Modul-Feld (unit in module_registry); - Faktor wird serverseitig ermittelt. Optional zusätzlich conversion_factor (legacy/zusätzlich). + - source_unit: Registry-IDs (z. B. "kj", "kg") oder "custom"/"none" — letztere ohne + vordefinierten Faktor; beliebige Skalierung dann nur über conversion_factor. + - conversion_factor: Zusätzlicher Multiplikator nach dem Parsen (und nach source_unit); + für nicht vordefinierte Umrechnungen source_unit weglassen oder "custom" setzen und + hier den Faktor angeben. """ if spec is None: return raw.strip() if raw else None @@ -331,7 +334,7 @@ def convert_value( v = _float_from_spec(raw, spec) if module: su = spec.get("source_unit") - if su is not None: + if su is not None and str(su).strip() != "": v = float(v) * factor_source_to_canonical(module, db_field, str(su)) factor = spec.get("conversion_factor") if factor is not None: diff --git a/backend/tests/test_csv_parser_core.py b/backend/tests/test_csv_parser_core.py index b3efd01..6fae6a9 100644 --- a/backend/tests/test_csv_parser_core.py +++ b/backend/tests/test_csv_parser_core.py @@ -86,6 +86,19 @@ def test_convert_protein_kg_to_g(): assert abs(g - 100.0) < 0.001 +def test_convert_custom_source_unit_only_conversion_factor(): + """Nicht vordefinierte Umrechnung: conversion_factor (optional mit source_unit: custom).""" + spec = {"type": "float", "source_unit": "custom", "conversion_factor": 2.5, "decimal_separator": "."} + k = convert_value("100", "kcal", spec, module="nutrition") + assert abs(k - 250.0) < 0.001 + + +def test_convert_unknown_source_unit_uses_conversion_factor_only(): + spec = {"type": "float", "source_unit": "exotic_unit", "conversion_factor": 0.5, "decimal_separator": "."} + k = convert_value("200", "kcal", spec, module="nutrition") + assert abs(k - 100.0) < 0.001 + + def test_build_row_source_unit_without_module_no_factor(): """Ohne module bleibt source_unit wirkungslos (Abwärtskompatibilität).""" spec = {"type": "float", "source_unit": "kj", "decimal_separator": "."} diff --git a/frontend/src/pages/AdminCsvTemplateEditorPage.jsx b/frontend/src/pages/AdminCsvTemplateEditorPage.jsx index d631966..f04f6aa 100644 --- a/frontend/src/pages/AdminCsvTemplateEditorPage.jsx +++ b/frontend/src/pages/AdminCsvTemplateEditorPage.jsx @@ -620,9 +620,12 @@ export default function AdminCsvTemplateEditorPage() {
- Vom Vorschlag übernommen; Quelleinheiten setzen zusätzlich source_unit (siehe 3b).
- Manuell z. B. Datumsformat oder legacy conversion_factor.
+
+ Vom Vorschlag übernommen; bei Dropdowns 3b wird source_unit ergänzt. Zusätzlich manuell
+ z. B. Datumsformat. Freie Umrechnung (nicht in der Liste 3b):{' '}
+ conversion_factor als Multiplikator nach dem Parsen (und nach Registry-
+ source_unit). Optional im JSON source_unit: 'custom', wenn nur{' '}
+ conversion_factor gelten soll.