265 lines
8.0 KiB
Python
265 lines
8.0 KiB
Python
"""
|
|
Tests for VAT Extractor
|
|
|
|
Tests extraction of VAT (Moms) information from Swedish invoice text.
|
|
"""
|
|
|
|
import pytest
|
|
from backend.vat.vat_extractor import (
|
|
VATBreakdown,
|
|
VATSummary,
|
|
VATExtractor,
|
|
AmountParser,
|
|
)
|
|
|
|
|
|
class TestAmountParser:
|
|
"""Tests for Swedish amount parsing."""
|
|
|
|
def test_parse_swedish_format(self):
|
|
"""Test parsing Swedish number format (1 234,56)."""
|
|
parser = AmountParser()
|
|
assert parser.parse("1 234,56") == 1234.56
|
|
assert parser.parse("100,00") == 100.0
|
|
assert parser.parse("1 000 000,00") == 1000000.0
|
|
|
|
def test_parse_with_currency(self):
|
|
"""Test parsing amounts with currency suffix."""
|
|
parser = AmountParser()
|
|
assert parser.parse("1 234,56 SEK") == 1234.56
|
|
assert parser.parse("100,00 kr") == 100.0
|
|
assert parser.parse("SEK 500,00") == 500.0
|
|
|
|
def test_parse_european_format(self):
|
|
"""Test parsing European format (1.234,56)."""
|
|
parser = AmountParser()
|
|
assert parser.parse("1.234,56") == 1234.56
|
|
|
|
def test_parse_us_format(self):
|
|
"""Test parsing US format (1,234.56)."""
|
|
parser = AmountParser()
|
|
assert parser.parse("1,234.56") == 1234.56
|
|
|
|
def test_parse_invalid_returns_none(self):
|
|
"""Test that invalid amounts return None."""
|
|
parser = AmountParser()
|
|
assert parser.parse("") is None
|
|
assert parser.parse("abc") is None
|
|
assert parser.parse("N/A") is None
|
|
|
|
def test_parse_negative_amount(self):
|
|
"""Test parsing negative amounts."""
|
|
parser = AmountParser()
|
|
assert parser.parse("-100,00") == -100.0
|
|
assert parser.parse("-1 234,56") == -1234.56
|
|
|
|
|
|
class TestVATBreakdown:
|
|
"""Tests for VATBreakdown dataclass."""
|
|
|
|
def test_create_breakdown(self):
|
|
"""Test creating a VAT breakdown."""
|
|
breakdown = VATBreakdown(
|
|
rate=25.0,
|
|
base_amount="10 000,00",
|
|
vat_amount="2 500,00",
|
|
source="regex",
|
|
)
|
|
assert breakdown.rate == 25.0
|
|
assert breakdown.base_amount == "10 000,00"
|
|
assert breakdown.vat_amount == "2 500,00"
|
|
assert breakdown.source == "regex"
|
|
|
|
def test_breakdown_with_optional_base(self):
|
|
"""Test breakdown without base amount."""
|
|
breakdown = VATBreakdown(
|
|
rate=25.0,
|
|
base_amount=None,
|
|
vat_amount="2 500,00",
|
|
source="regex",
|
|
)
|
|
assert breakdown.base_amount is None
|
|
|
|
|
|
class TestVATSummary:
|
|
"""Tests for VATSummary dataclass."""
|
|
|
|
def test_create_summary(self):
|
|
"""Test creating a VAT summary."""
|
|
breakdowns = [
|
|
VATBreakdown(rate=25.0, base_amount="8 000,00", vat_amount="2 000,00", source="regex"),
|
|
VATBreakdown(rate=12.0, base_amount="2 000,00", vat_amount="240,00", source="regex"),
|
|
]
|
|
summary = VATSummary(
|
|
breakdowns=breakdowns,
|
|
total_excl_vat="10 000,00",
|
|
total_vat="2 240,00",
|
|
total_incl_vat="12 240,00",
|
|
confidence=0.95,
|
|
)
|
|
assert len(summary.breakdowns) == 2
|
|
assert summary.total_excl_vat == "10 000,00"
|
|
|
|
def test_empty_summary(self):
|
|
"""Test empty VAT summary."""
|
|
summary = VATSummary(
|
|
breakdowns=[],
|
|
total_excl_vat=None,
|
|
total_vat=None,
|
|
total_incl_vat=None,
|
|
confidence=0.0,
|
|
)
|
|
assert summary.breakdowns == []
|
|
|
|
|
|
class TestVATExtractor:
|
|
"""Tests for VAT extraction from text."""
|
|
|
|
def test_extract_single_vat_rate(self):
|
|
"""Test extracting single VAT rate from text."""
|
|
text = """
|
|
Summa exkl. moms: 10 000,00
|
|
Moms 25%: 2 500,00
|
|
Summa inkl. moms: 12 500,00
|
|
"""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract(text)
|
|
|
|
assert len(summary.breakdowns) == 1
|
|
assert summary.breakdowns[0].rate == 25.0
|
|
assert summary.breakdowns[0].vat_amount == "2 500,00"
|
|
|
|
def test_extract_multiple_vat_rates(self):
|
|
"""Test extracting multiple VAT rates."""
|
|
text = """
|
|
Moms 25%: 2 000,00
|
|
Moms 12%: 240,00
|
|
Moms 6%: 60,00
|
|
Summa moms: 2 300,00
|
|
"""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract(text)
|
|
|
|
assert len(summary.breakdowns) == 3
|
|
rates = [b.rate for b in summary.breakdowns]
|
|
assert 25.0 in rates
|
|
assert 12.0 in rates
|
|
assert 6.0 in rates
|
|
|
|
def test_extract_varav_moms_format(self):
|
|
"""Test extracting 'Varav moms' format."""
|
|
text = """
|
|
Totalt: 12 500,00
|
|
Varav moms 25% 2 500,00
|
|
"""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract(text)
|
|
|
|
assert len(summary.breakdowns) == 1
|
|
assert summary.breakdowns[0].rate == 25.0
|
|
assert summary.breakdowns[0].vat_amount == "2 500,00"
|
|
|
|
def test_extract_percentage_moms_format(self):
|
|
"""Test extracting '25% moms:' format."""
|
|
text = """
|
|
25% moms: 2 500,00
|
|
12% moms: 240,00
|
|
"""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract(text)
|
|
|
|
assert len(summary.breakdowns) == 2
|
|
|
|
def test_extract_totals(self):
|
|
"""Test extracting total amounts."""
|
|
text = """
|
|
Summa exkl. moms: 10 000,00
|
|
Summa moms: 2 500,00
|
|
Totalt att betala: 12 500,00
|
|
"""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract(text)
|
|
|
|
assert summary.total_excl_vat == "10 000,00"
|
|
assert summary.total_vat == "2 500,00"
|
|
|
|
def test_extract_with_underlag(self):
|
|
"""Test extracting VAT with base amount (Underlag)."""
|
|
text = """
|
|
Moms 25%: 2 500,00 (Underlag 10 000,00)
|
|
"""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract(text)
|
|
|
|
assert len(summary.breakdowns) == 1
|
|
assert summary.breakdowns[0].rate == 25.0
|
|
assert summary.breakdowns[0].vat_amount == "2 500,00"
|
|
assert summary.breakdowns[0].base_amount == "10 000,00"
|
|
|
|
def test_extract_from_empty_text(self):
|
|
"""Test extraction from empty text."""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract("")
|
|
|
|
assert summary.breakdowns == []
|
|
assert summary.confidence == 0.0
|
|
|
|
def test_extract_zero_vat(self):
|
|
"""Test extracting 0% VAT."""
|
|
text = """
|
|
Moms 0%: 0,00
|
|
Summa exkl. moms: 1 000,00
|
|
"""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract(text)
|
|
|
|
rates = [b.rate for b in summary.breakdowns]
|
|
assert 0.0 in rates
|
|
|
|
def test_extract_netto_brutto_format(self):
|
|
"""Test extracting Netto/Brutto format."""
|
|
text = """
|
|
Netto: 10 000,00
|
|
Moms: 2 500,00
|
|
Brutto: 12 500,00
|
|
"""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract(text)
|
|
|
|
assert summary.total_excl_vat == "10 000,00"
|
|
# Should detect implicit 25% rate from math
|
|
|
|
def test_confidence_calculation(self):
|
|
"""Test confidence score calculation."""
|
|
extractor = VATExtractor()
|
|
|
|
# High confidence - multiple sources agree (including Summa moms)
|
|
text_high = """
|
|
Summa exkl. moms: 10 000,00
|
|
Moms 25%: 2 500,00
|
|
Summa moms: 2 500,00
|
|
Summa inkl. moms: 12 500,00
|
|
"""
|
|
summary_high = extractor.extract(text_high)
|
|
assert summary_high.confidence >= 0.8
|
|
|
|
# Lower confidence - only partial info
|
|
text_low = """
|
|
Moms: 2 500,00
|
|
"""
|
|
summary_low = extractor.extract(text_low)
|
|
assert summary_low.confidence < summary_high.confidence
|
|
|
|
def test_handles_ocr_noise(self):
|
|
"""Test handling OCR noise in text."""
|
|
text = """
|
|
Summa exkl moms: 10 000,00
|
|
Mams 25%: 2 500,00
|
|
Sum ma inkl. moms: 12 500,00
|
|
"""
|
|
extractor = VATExtractor()
|
|
summary = extractor.extract(text)
|
|
|
|
# Should still extract some information despite noise
|
|
assert summary.total_excl_vat is not None or len(summary.breakdowns) > 0
|