This commit is contained in:
Yaojia Wang
2026-02-11 23:40:38 +01:00
parent f1a7bfe6b7
commit ad5ed46b4c
117 changed files with 5741 additions and 7669 deletions

View File

@@ -1,556 +1,170 @@
"""
Tests for expand_bbox function.
Tests for expand_bbox function with uniform pixel padding.
Tests verify that bbox expansion works correctly with center-point scaling,
directional compensation, max padding clamping, and image boundary handling.
Verifies that bbox expansion adds uniform padding on all sides,
clamps to image boundaries, and returns integer tuples.
"""
import pytest
from shared.bbox import (
expand_bbox,
ScaleStrategy,
FIELD_SCALE_STRATEGIES,
DEFAULT_STRATEGY,
)
from shared.bbox import expand_bbox
from shared.bbox.scale_strategy import UNIFORM_PAD
class TestExpandBboxCenterScaling:
"""Tests for center-point based scaling."""
class TestExpandBboxUniformPadding:
"""Tests for uniform padding on all sides."""
def test_center_scaling_expands_symmetrically(self):
"""Verify bbox expands symmetrically around center when no extra ratios."""
# 100x50 bbox at (100, 200)
bbox = (100, 200, 200, 250)
strategy = ScaleStrategy(
scale_x=1.2, # 20% wider
scale_y=1.4, # 40% taller
max_pad_x=1000, # Large to avoid clamping
max_pad_y=1000,
)
def test_adds_uniform_pad_on_all_sides(self):
"""Verify default pad is applied equally on all four sides."""
bbox = (100, 200, 300, 250)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
# Original: width=100, height=50
# New: width=120, height=70
# Center: (150, 225)
# Expected: x0=150-60=90, x1=150+60=210, y0=225-35=190, y1=225+35=260
assert result[0] == 90 # x0
assert result[1] == 190 # y0
assert result[2] == 210 # x1
assert result[3] == 260 # y1
def test_no_scaling_returns_original(self):
"""Verify scale=1.0 with no extras returns original bbox."""
bbox = (100, 200, 200, 250)
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
max_pad_x=1000,
max_pad_y=1000,
assert result == (
100 - UNIFORM_PAD,
200 - UNIFORM_PAD,
300 + UNIFORM_PAD,
250 + UNIFORM_PAD,
)
def test_custom_pad_value(self):
"""Verify custom pad overrides default."""
bbox = (100, 200, 300, 250)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
pad=20,
)
assert result == (100, 200, 200, 250)
assert result == (80, 180, 320, 270)
class TestExpandBboxDirectionalCompensation:
"""Tests for directional compensation (extra ratios)."""
def test_extra_top_expands_upward(self):
"""Verify extra_top_ratio adds expansion toward top."""
bbox = (100, 200, 200, 250) # width=100, height=50
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
extra_top_ratio=0.5, # Add 50% of height to top
max_pad_x=1000,
max_pad_y=1000,
)
def test_zero_pad_returns_original(self):
"""Verify pad=0 returns original bbox as integers."""
bbox = (100, 200, 300, 250)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
pad=0,
)
# extra_top = 50 * 0.5 = 25
assert result[0] == 100 # x0 unchanged
assert result[1] == 175 # y0 = 200 - 25
assert result[2] == 200 # x1 unchanged
assert result[3] == 250 # y1 unchanged
assert result == (100, 200, 300, 250)
def test_extra_left_expands_leftward(self):
"""Verify extra_left_ratio adds expansion toward left."""
bbox = (100, 200, 200, 250) # width=100
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
extra_left_ratio=0.8, # Add 80% of width to left
max_pad_x=1000,
max_pad_y=1000,
)
def test_all_field_types_get_same_padding(self):
"""Verify no field-specific expansion -- same result regardless of field."""
bbox = (100, 200, 300, 250)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
result_a = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
result_b = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
# extra_left = 100 * 0.8 = 80
assert result[0] == 20 # x0 = 100 - 80
assert result[1] == 200 # y0 unchanged
assert result[2] == 200 # x1 unchanged
assert result[3] == 250 # y1 unchanged
def test_extra_right_expands_rightward(self):
"""Verify extra_right_ratio adds expansion toward right."""
bbox = (100, 200, 200, 250) # width=100
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
extra_right_ratio=0.3, # Add 30% of width to right
max_pad_x=1000,
max_pad_y=1000,
)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
# extra_right = 100 * 0.3 = 30
assert result[0] == 100 # x0 unchanged
assert result[1] == 200 # y0 unchanged
assert result[2] == 230 # x1 = 200 + 30
assert result[3] == 250 # y1 unchanged
def test_extra_bottom_expands_downward(self):
"""Verify extra_bottom_ratio adds expansion toward bottom."""
bbox = (100, 200, 200, 250) # height=50
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
extra_bottom_ratio=0.4, # Add 40% of height to bottom
max_pad_x=1000,
max_pad_y=1000,
)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
# extra_bottom = 50 * 0.4 = 20
assert result[0] == 100 # x0 unchanged
assert result[1] == 200 # y0 unchanged
assert result[2] == 200 # x1 unchanged
assert result[3] == 270 # y1 = 250 + 20
def test_combined_scaling_and_directional(self):
"""Verify scale + directional compensation work together."""
bbox = (100, 200, 200, 250) # width=100, height=50
strategy = ScaleStrategy(
scale_x=1.2, # 20% wider -> 120 width
scale_y=1.0, # no height change
extra_left_ratio=0.5, # Add 50% of width to left
max_pad_x=1000,
max_pad_y=1000,
)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
# Center: x=150
# After scale: width=120 -> x0=150-60=90, x1=150+60=210
# After extra_left: x0 = 90 - (100 * 0.5) = 40
assert result[0] == 40 # x0
assert result[2] == 210 # x1
class TestExpandBboxMaxPadClamping:
"""Tests for max padding clamping."""
def test_max_pad_x_limits_horizontal_expansion(self):
"""Verify max_pad_x limits expansion on left and right."""
bbox = (100, 200, 200, 250) # width=100
strategy = ScaleStrategy(
scale_x=2.0, # Double width (would add 50 each side)
scale_y=1.0,
max_pad_x=30, # Limit to 30 pixels each side
max_pad_y=1000,
)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
# Scale would make: x0=100, x1=200 -> x0=50, x1=250 (50px each side)
# But max_pad_x=30 limits to: x0=70, x1=230
assert result[0] == 70 # x0 = 100 - 30
assert result[2] == 230 # x1 = 200 + 30
def test_max_pad_y_limits_vertical_expansion(self):
"""Verify max_pad_y limits expansion on top and bottom."""
bbox = (100, 200, 200, 250) # height=50
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=3.0, # Triple height (would add 50 each side)
max_pad_x=1000,
max_pad_y=20, # Limit to 20 pixels each side
)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
# Scale would make: y0=175, y1=275 (50px each side)
# But max_pad_y=20 limits to: y0=180, y1=270
assert result[1] == 180 # y0 = 200 - 20
assert result[3] == 270 # y1 = 250 + 20
def test_max_pad_preserves_asymmetry(self):
"""Verify max_pad clamping preserves asymmetric expansion."""
bbox = (100, 200, 200, 250) # width=100
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
extra_left_ratio=1.0, # 100px left expansion
extra_right_ratio=0.0, # No right expansion
max_pad_x=50, # Limit to 50 pixels
max_pad_y=1000,
)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
# Left would expand 100, clamped to 50
# Right stays at 0
assert result[0] == 50 # x0 = 100 - 50
assert result[2] == 200 # x1 unchanged
assert result_a == result_b
class TestExpandBboxImageBoundaryClamping:
"""Tests for image boundary clamping."""
"""Tests for clamping to image boundaries."""
def test_clamps_to_left_boundary(self):
"""Verify x0 is clamped to 0."""
bbox = (10, 200, 110, 250) # Close to left edge
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
extra_left_ratio=0.5, # Would push x0 below 0
max_pad_x=1000,
max_pad_y=1000,
)
def test_clamps_x0_to_zero(self):
bbox = (5, 200, 100, 250)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
assert result[0] == 0 # Clamped to 0
assert result[0] == 0
def test_clamps_to_top_boundary(self):
"""Verify y0 is clamped to 0."""
bbox = (100, 10, 200, 60) # Close to top edge
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
extra_top_ratio=0.5, # Would push y0 below 0
max_pad_x=1000,
max_pad_y=1000,
)
def test_clamps_y0_to_zero(self):
bbox = (100, 3, 300, 50)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
assert result[1] == 0 # Clamped to 0
assert result[1] == 0
def test_clamps_to_right_boundary(self):
"""Verify x1 is clamped to image_width."""
bbox = (900, 200, 990, 250) # Close to right edge
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
extra_right_ratio=0.5, # Would push x1 beyond image_width
max_pad_x=1000,
max_pad_y=1000,
)
def test_clamps_x1_to_image_width(self):
bbox = (900, 200, 995, 250)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
assert result[2] == 1000 # Clamped to image_width
assert result[2] == 1000
def test_clamps_to_bottom_boundary(self):
"""Verify y1 is clamped to image_height."""
bbox = (100, 940, 200, 990) # Close to bottom edge
strategy = ScaleStrategy(
scale_x=1.0,
scale_y=1.0,
extra_bottom_ratio=0.5, # Would push y1 beyond image_height
max_pad_x=1000,
max_pad_y=1000,
)
def test_clamps_y1_to_image_height(self):
bbox = (100, 900, 300, 995)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test_field",
strategies={"test_field": strategy},
)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
assert result[3] == 1000 # Clamped to image_height
assert result[3] == 1000
def test_corner_bbox_clamps_multiple_sides(self):
"""Bbox near top-left corner clamps both x0 and y0."""
bbox = (2, 3, 50, 60)
class TestExpandBboxUnknownField:
"""Tests for unknown field handling."""
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
def test_unknown_field_uses_default_strategy(self):
"""Verify unknown field types use DEFAULT_STRATEGY."""
bbox = (100, 200, 200, 250)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="unknown_field_xyz",
)
# DEFAULT_STRATEGY: scale_x=1.15, scale_y=1.15
# Original: width=100, height=50
# New: width=115, height=57.5
# Center: (150, 225)
# x0 = 150 - 57.5 = 92.5 -> 92
# x1 = 150 + 57.5 = 207.5 -> 207
# y0 = 225 - 28.75 = 196.25 -> 196
# y1 = 225 + 28.75 = 253.75 -> 253
# But max_pad_x=50 may clamp...
# Left pad = 100 - 92.5 = 7.5 (< 50, ok)
# Right pad = 207.5 - 200 = 7.5 (< 50, ok)
assert result[0] == 92
assert result[2] == 207
class TestExpandBboxWithRealStrategies:
"""Tests using actual FIELD_SCALE_STRATEGIES."""
def test_ocr_number_expands_significantly_upward(self):
"""Verify ocr_number field gets significant upward expansion."""
bbox = (100, 200, 200, 230) # Small height=30
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="ocr_number",
)
# extra_top_ratio=0.60 -> 30 * 0.6 = 18 extra top
# y0 should decrease significantly
assert result[1] < 200 - 10 # At least 10px upward expansion
def test_bankgiro_expands_significantly_leftward(self):
"""Verify bankgiro field gets significant leftward expansion."""
bbox = (200, 200, 300, 230) # width=100
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="bankgiro",
)
# extra_left_ratio=0.80 -> 100 * 0.8 = 80 extra left
# x0 should decrease significantly
assert result[0] < 200 - 30 # At least 30px leftward expansion
def test_amount_expands_rightward(self):
"""Verify amount field gets rightward expansion for currency."""
bbox = (100, 200, 200, 230) # width=100
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="amount",
)
# extra_right_ratio=0.30 -> 100 * 0.3 = 30 extra right
# x1 should increase
assert result[2] > 200 + 10 # At least 10px rightward expansion
assert result[0] == 0
assert result[1] == 0
assert result[2] == 50 + UNIFORM_PAD
assert result[3] == 60 + UNIFORM_PAD
class TestExpandBboxReturnType:
"""Tests for return type and value format."""
def test_returns_tuple_of_four_ints(self):
"""Verify return type is tuple of 4 integers."""
bbox = (100.5, 200.3, 200.7, 250.9)
bbox = (100.5, 200.3, 300.7, 250.9)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="invoice_number",
)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
assert isinstance(result, tuple)
assert len(result) == 4
assert all(isinstance(v, int) for v in result)
def test_returns_valid_bbox_format(self):
"""Verify returned bbox has x0 < x1 and y0 < y1."""
bbox = (100, 200, 200, 250)
def test_float_bbox_floors_correctly(self):
"""Verify float coordinates are converted to int properly."""
bbox = (100.7, 200.3, 300.2, 250.8)
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="invoice_number",
)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000, pad=0)
# int() truncates toward zero
assert result == (100, 200, 300, 250)
def test_returns_valid_bbox_ordering(self):
"""Verify x0 < x1 and y0 < y1."""
bbox = (100, 200, 300, 250)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
x0, y0, x1, y1 = result
assert x0 < x1, "x0 should be less than x1"
assert y0 < y1, "y0 should be less than y1"
assert x0 < x1
assert y0 < y1
class TestManualLabelMode:
"""Tests for manual_mode parameter."""
class TestExpandBboxEdgeCases:
"""Tests for edge cases."""
def test_manual_mode_uses_minimal_padding(self):
"""Verify manual_mode uses MANUAL_LABEL_STRATEGY with minimal padding."""
bbox = (100, 200, 200, 250) # width=100, height=50
def test_small_bbox_with_large_pad(self):
"""Pad larger than bbox still works correctly."""
bbox = (100, 200, 105, 203) # 5x3 pixel bbox
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="bankgiro", # Would normally expand left significantly
manual_mode=True,
)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000, pad=50)
# MANUAL_LABEL_STRATEGY: scale=1.0, max_pad=10
# Should only add 10px padding each side (but scale=1.0 means no scaling)
# Actually with scale=1.0, no extra ratios, we get 0 expansion from scaling
# Only max_pad=10 applies as a limit, but there's no expansion to limit
# So result should be same as original
assert result == (100, 200, 200, 250)
assert result == (50, 150, 155, 253)
def test_manual_mode_ignores_field_type(self):
"""Verify manual_mode ignores field-specific strategies."""
bbox = (100, 200, 200, 250)
def test_bbox_at_origin(self):
bbox = (0, 0, 50, 30)
# Different fields should give same result in manual_mode
result_bankgiro = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="bankgiro",
manual_mode=True,
)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
result_ocr = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="ocr_number",
manual_mode=True,
)
assert result[0] == 0
assert result[1] == 0
assert result_bankgiro == result_ocr
def test_bbox_at_image_edge(self):
bbox = (950, 970, 1000, 1000)
def test_manual_mode_vs_auto_mode_different(self):
"""Verify manual_mode produces different results than auto mode."""
bbox = (100, 200, 200, 250)
result = expand_bbox(bbox=bbox, image_width=1000, image_height=1000)
auto_result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="bankgiro", # Has extra_left_ratio=0.80
manual_mode=False,
)
manual_result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="bankgiro",
manual_mode=True,
)
# Auto mode should expand more (especially to the left for bankgiro)
assert auto_result[0] < manual_result[0] # Auto x0 is more left
def test_manual_mode_clamps_to_image_bounds(self):
"""Verify manual_mode still respects image boundaries."""
bbox = (5, 5, 50, 50) # Close to top-left corner
result = expand_bbox(
bbox=bbox,
image_width=1000,
image_height=1000,
field_type="test",
manual_mode=True,
)
# Should clamp to 0
assert result[0] >= 0
assert result[1] >= 0
assert result[2] == 1000
assert result[3] == 1000

View File

@@ -1,192 +1,24 @@
"""
Tests for ScaleStrategy configuration.
Tests for simplified scale strategy configuration.
Tests verify that scale strategies are properly defined, immutable,
and cover all required fields.
Verifies that UNIFORM_PAD constant is properly defined
and replaces the old field-specific strategies.
"""
import pytest
from shared.bbox import (
ScaleStrategy,
DEFAULT_STRATEGY,
MANUAL_LABEL_STRATEGY,
FIELD_SCALE_STRATEGIES,
)
from shared.fields import CLASS_NAMES
from shared.bbox.scale_strategy import UNIFORM_PAD
class TestScaleStrategyDataclass:
"""Tests for ScaleStrategy dataclass behavior."""
class TestUniformPad:
"""Tests for UNIFORM_PAD constant."""
def test_default_strategy_values(self):
"""Verify default strategy has expected default values."""
strategy = ScaleStrategy()
assert strategy.scale_x == 1.15
assert strategy.scale_y == 1.15
assert strategy.extra_top_ratio == 0.0
assert strategy.extra_bottom_ratio == 0.0
assert strategy.extra_left_ratio == 0.0
assert strategy.extra_right_ratio == 0.0
assert strategy.max_pad_x == 50
assert strategy.max_pad_y == 50
def test_uniform_pad_is_integer(self):
assert isinstance(UNIFORM_PAD, int)
def test_scale_strategy_immutability(self):
"""Verify ScaleStrategy is frozen (immutable)."""
strategy = ScaleStrategy()
with pytest.raises(AttributeError):
strategy.scale_x = 2.0 # type: ignore
def test_uniform_pad_value_is_15(self):
"""15px at 150 DPI provides ~2.5mm real-world padding."""
assert UNIFORM_PAD == 15
def test_custom_strategy_values(self):
"""Verify custom values are properly set."""
strategy = ScaleStrategy(
scale_x=1.5,
scale_y=1.8,
extra_top_ratio=0.6,
extra_left_ratio=0.8,
max_pad_x=100,
max_pad_y=150,
)
assert strategy.scale_x == 1.5
assert strategy.scale_y == 1.8
assert strategy.extra_top_ratio == 0.6
assert strategy.extra_left_ratio == 0.8
assert strategy.max_pad_x == 100
assert strategy.max_pad_y == 150
class TestDefaultStrategy:
"""Tests for DEFAULT_STRATEGY constant."""
def test_default_strategy_is_scale_strategy(self):
"""Verify DEFAULT_STRATEGY is a ScaleStrategy instance."""
assert isinstance(DEFAULT_STRATEGY, ScaleStrategy)
def test_default_strategy_matches_default_values(self):
"""Verify DEFAULT_STRATEGY has same values as ScaleStrategy()."""
expected = ScaleStrategy()
assert DEFAULT_STRATEGY == expected
class TestManualLabelStrategy:
"""Tests for MANUAL_LABEL_STRATEGY constant."""
def test_manual_label_strategy_is_scale_strategy(self):
"""Verify MANUAL_LABEL_STRATEGY is a ScaleStrategy instance."""
assert isinstance(MANUAL_LABEL_STRATEGY, ScaleStrategy)
def test_manual_label_strategy_has_no_scaling(self):
"""Verify MANUAL_LABEL_STRATEGY has scale factors of 1.0."""
assert MANUAL_LABEL_STRATEGY.scale_x == 1.0
assert MANUAL_LABEL_STRATEGY.scale_y == 1.0
def test_manual_label_strategy_has_no_directional_expansion(self):
"""Verify MANUAL_LABEL_STRATEGY has no directional expansion."""
assert MANUAL_LABEL_STRATEGY.extra_top_ratio == 0.0
assert MANUAL_LABEL_STRATEGY.extra_bottom_ratio == 0.0
assert MANUAL_LABEL_STRATEGY.extra_left_ratio == 0.0
assert MANUAL_LABEL_STRATEGY.extra_right_ratio == 0.0
def test_manual_label_strategy_has_small_max_pad(self):
"""Verify MANUAL_LABEL_STRATEGY has small max padding."""
assert MANUAL_LABEL_STRATEGY.max_pad_x <= 15
assert MANUAL_LABEL_STRATEGY.max_pad_y <= 15
class TestFieldScaleStrategies:
"""Tests for FIELD_SCALE_STRATEGIES dictionary."""
def test_all_class_names_have_strategies(self):
"""Verify all field class names have defined strategies."""
for class_name in CLASS_NAMES:
assert class_name in FIELD_SCALE_STRATEGIES, (
f"Missing strategy for field: {class_name}"
)
def test_strategies_are_scale_strategy_instances(self):
"""Verify all strategies are ScaleStrategy instances."""
for field_name, strategy in FIELD_SCALE_STRATEGIES.items():
assert isinstance(strategy, ScaleStrategy), (
f"Strategy for {field_name} is not a ScaleStrategy"
)
def test_scale_values_are_greater_than_one(self):
"""Verify all scale values are >= 1.0 (expansion, not contraction)."""
for field_name, strategy in FIELD_SCALE_STRATEGIES.items():
assert strategy.scale_x >= 1.0, (
f"{field_name} scale_x should be >= 1.0"
)
assert strategy.scale_y >= 1.0, (
f"{field_name} scale_y should be >= 1.0"
)
def test_extra_ratios_are_non_negative(self):
"""Verify all extra ratios are >= 0."""
for field_name, strategy in FIELD_SCALE_STRATEGIES.items():
assert strategy.extra_top_ratio >= 0, (
f"{field_name} extra_top_ratio should be >= 0"
)
assert strategy.extra_bottom_ratio >= 0, (
f"{field_name} extra_bottom_ratio should be >= 0"
)
assert strategy.extra_left_ratio >= 0, (
f"{field_name} extra_left_ratio should be >= 0"
)
assert strategy.extra_right_ratio >= 0, (
f"{field_name} extra_right_ratio should be >= 0"
)
def test_max_pad_values_are_positive(self):
"""Verify all max_pad values are > 0."""
for field_name, strategy in FIELD_SCALE_STRATEGIES.items():
assert strategy.max_pad_x > 0, (
f"{field_name} max_pad_x should be > 0"
)
assert strategy.max_pad_y > 0, (
f"{field_name} max_pad_y should be > 0"
)
class TestSpecificFieldStrategies:
"""Tests for specific field strategy configurations."""
def test_ocr_number_expands_upward(self):
"""Verify ocr_number strategy expands upward to capture label."""
strategy = FIELD_SCALE_STRATEGIES["ocr_number"]
assert strategy.extra_top_ratio > 0.0
assert strategy.extra_top_ratio >= 0.5 # Significant upward expansion
def test_bankgiro_expands_leftward(self):
"""Verify bankgiro strategy expands leftward to capture prefix."""
strategy = FIELD_SCALE_STRATEGIES["bankgiro"]
assert strategy.extra_left_ratio > 0.0
assert strategy.extra_left_ratio >= 0.5 # Significant leftward expansion
def test_plusgiro_expands_leftward(self):
"""Verify plusgiro strategy expands leftward to capture prefix."""
strategy = FIELD_SCALE_STRATEGIES["plusgiro"]
assert strategy.extra_left_ratio > 0.0
assert strategy.extra_left_ratio >= 0.5
def test_amount_expands_rightward(self):
"""Verify amount strategy expands rightward for currency symbol."""
strategy = FIELD_SCALE_STRATEGIES["amount"]
assert strategy.extra_right_ratio > 0.0
def test_invoice_date_expands_upward(self):
"""Verify invoice_date strategy expands upward to capture label."""
strategy = FIELD_SCALE_STRATEGIES["invoice_date"]
assert strategy.extra_top_ratio > 0.0
def test_invoice_due_date_expands_upward_and_leftward(self):
"""Verify invoice_due_date strategy expands both up and left."""
strategy = FIELD_SCALE_STRATEGIES["invoice_due_date"]
assert strategy.extra_top_ratio > 0.0
assert strategy.extra_left_ratio > 0.0
def test_payment_line_has_minimal_expansion(self):
"""Verify payment_line has conservative expansion (machine code)."""
strategy = FIELD_SCALE_STRATEGIES["payment_line"]
# Payment line is machine-readable, needs minimal expansion
assert strategy.scale_x <= 1.2
assert strategy.scale_y <= 1.3
def test_uniform_pad_is_positive(self):
assert UNIFORM_PAD > 0