release: v2.8.2 fix Ollama amount normalization

This commit is contained in:
Christian 2026-09-09 11:57:37 +02:00
parent 4bf592cf0c
commit 6218e743ef
4 changed files with 78 additions and 1 deletions

View File

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

View File

@ -1 +1 @@
2.8.1 2.8.2

View File

@ -460,6 +460,18 @@ REGLER FOR title:
# Parse JSON from response # Parse JSON from response
extraction = self._parse_json_response(raw_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 # CRITICAL: Fix amount signs based on document_type
# LLM sometimes returns negative amounts for invoices - fix this! # LLM sometimes returns negative amounts for invoices - fix this!
document_type = extraction.get('document_type', 'invoice') document_type = extraction.get('document_type', 'invoice')
@ -528,6 +540,39 @@ REGLER FOR title:
"confidence": 0.0 "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: def _parse_json_response(self, response: str) -> Dict:
"""Parse JSON from LLM response with aggressive fallback strategies""" """Parse JSON from LLM response with aggressive fallback strategies"""
logger.info(f"🔍 Response length: {len(response)}, preview: {response[:200]}") logger.info(f"🔍 Response length: {len(response)}, preview: {response[:200]}")

View File

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