From 056ce7b871070d3251398a71c7c861339fe4e6a0 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 9 Sep 2026 11:57:37 +0200 Subject: [PATCH] release: v2.8.2 fix Ollama amount normalization --- MDfile/RELEASE_NOTES_v2.8.2.md | 13 +++++++ VERSION | 2 +- app/services/ollama_service.py | 45 +++++++++++++++++++++++ tests/test_ollama_amount_normalization.py | 19 ++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 MDfile/RELEASE_NOTES_v2.8.2.md create mode 100644 tests/test_ollama_amount_normalization.py diff --git a/MDfile/RELEASE_NOTES_v2.8.2.md b/MDfile/RELEASE_NOTES_v2.8.2.md new file mode 100644 index 0000000..a3d0757 --- /dev/null +++ b/MDfile/RELEASE_NOTES_v2.8.2.md @@ -0,0 +1,13 @@ +# BMC Hub v2.8.2 + +## AI Template Builder + +- Rettet fejl når Ollama returnerer fakturabeløb som tekst i stedet for numerisk JSON. +- Understøtter nu både danske beløbsformater som `1.471,20 DKK` og engelske formater som `1471.20`. +- Ugyldige eller tomme beløb håndteres uden at afbryde AI-analysen. +- Tilføjet regressionstest for beløbsnormalisering. + +## Verifikation + +- Regressionstest: 2 bestået. +- Python-syntaks og diff-kontrol bestået. diff --git a/VERSION b/VERSION index dbe5900..1817afe 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.1 +2.8.2 diff --git a/app/services/ollama_service.py b/app/services/ollama_service.py index a31dfd3..21a6446 100644 --- a/app/services/ollama_service.py +++ b/app/services/ollama_service.py @@ -459,6 +459,18 @@ REGLER FOR title: # Parse JSON from response extraction = self._parse_json_response(raw_response) + + # Models do not always respect the numeric JSON schema and may + # return Danish/English amount strings. Normalize them before + # applying sign rules so comparisons with zero are type-safe. + for amount_key in ('total_amount', 'vat_amount'): + extraction[amount_key] = self._normalize_amount_value(extraction.get(amount_key)) + if isinstance(extraction.get('lines'), list): + for line in extraction['lines']: + if not isinstance(line, dict): + continue + for amount_key in ('unit_price', 'line_total', 'vat_amount'): + line[amount_key] = self._normalize_amount_value(line.get(amount_key)) # CRITICAL: Fix amount signs based on document_type # LLM sometimes returns negative amounts for invoices - fix this! @@ -527,6 +539,39 @@ REGLER FOR title: "error": error_msg, "confidence": 0.0 } + + @staticmethod + def _normalize_amount_value(value): + """Convert common Danish/English amount representations to numbers.""" + if value is None or value == '': + return None + if isinstance(value, bool): + return None + if isinstance(value, (int, float)): + return value + if not isinstance(value, str): + return None + + cleaned = re.sub(r'[^0-9,.-]', '', value.strip()).strip('.,') + if not cleaned or cleaned in {'-', '.', ',', '-.', '-,'}: + return None + + if ',' in cleaned and '.' in cleaned: + if cleaned.rfind(',') > cleaned.rfind('.'): + cleaned = cleaned.replace('.', '').replace(',', '.') + else: + cleaned = cleaned.replace(',', '') + elif ',' in cleaned: + cleaned = cleaned.replace('.', '').replace(',', '.') + elif cleaned.count('.') > 1: + cleaned = cleaned.replace('.', '') + elif '.' in cleaned and len(cleaned.rsplit('.', 1)[1]) == 3: + cleaned = cleaned.replace('.', '') + + try: + return float(cleaned) + except ValueError: + return None def _parse_json_response(self, response: str) -> Dict: """Parse JSON from LLM response with aggressive fallback strategies""" diff --git a/tests/test_ollama_amount_normalization.py b/tests/test_ollama_amount_normalization.py new file mode 100644 index 0000000..c874011 --- /dev/null +++ b/tests/test_ollama_amount_normalization.py @@ -0,0 +1,19 @@ +from app.services.ollama_service import OllamaService + + +def test_normalizes_numeric_and_danish_amount_values(): + normalize = OllamaService._normalize_amount_value + + assert normalize(1471.2) == 1471.2 + assert normalize("1471.20") == 1471.2 + assert normalize("1.471,20 DKK") == 1471.2 + assert normalize("-1 471,20 kr.") == -1471.2 + + +def test_rejects_non_amount_values_without_type_errors(): + normalize = OllamaService._normalize_amount_value + + assert normalize(None) is None + assert normalize("") is None + assert normalize("ukendt") is None + assert normalize({"value": "10,00"}) is None