80 lines
3.0 KiB
Python
80 lines
3.0 KiB
Python
"""Упаковка встречных строк по одному правилу без БД (§5)."""
|
|
|
|
import uuid
|
|
from decimal import Decimal
|
|
|
|
from src.domain.elimination_packing import MAX_SIDE_ROWS, PackRow, best_subsets, pack_rule_rows
|
|
|
|
A = uuid.uuid4()
|
|
B = uuid.uuid4()
|
|
|
|
|
|
def _row(amount: str, *, debit: bool) -> PackRow:
|
|
return PackRow((uuid.uuid4(),), A if debit else B, B if debit else A, Decimal(amount))
|
|
|
|
|
|
def test_knapsack_finds_exact_subset_with_fixed_size_and_keeps_ties() -> None:
|
|
first, second, third = (_row(amount, debit=False) for amount in ("40", "60", "60"))
|
|
|
|
choice = best_subsets([first, second, third], size=2, capacity=Decimal("100"))
|
|
|
|
assert choice is not None
|
|
assert choice.deviation == 0
|
|
assert choice.unique_rows is None
|
|
assert choice.involved_keys == {first.key, second.key, third.key}
|
|
|
|
|
|
def test_knapsack_keeps_both_nearest_sums_and_the_five_percent_lower_boundary() -> None:
|
|
below, above = (_row(amount, debit=False) for amount in ("99.00", "101.00"))
|
|
|
|
tie = best_subsets([below, above], size=1, capacity=Decimal("100.00"))
|
|
assert tie is not None
|
|
assert tie.deviation == Decimal("1.00")
|
|
assert tie.unique_rows is None
|
|
assert tie.involved_keys == {below.key, above.key}
|
|
|
|
boundary = best_subsets([_row("100.00", debit=False)], size=1, capacity=Decimal("105.00"))
|
|
assert boundary is not None
|
|
assert boundary.deviation == Decimal("5.00")
|
|
|
|
|
|
def test_exact_one_to_one_is_reserved_before_one_to_many() -> None:
|
|
debit = [_row("100", debit=True), _row("50", debit=True)]
|
|
credit = [_row("100", debit=False), _row("30", debit=False), _row("20", debit=False)]
|
|
|
|
result = pack_rule_rows(debit, credit)
|
|
|
|
assert [(len(group.debit_rows), len(group.credit_rows)) for group in result.groups] == [(1, 1), (1, 2)]
|
|
assert result.used_keys == {row.key for row in [*debit, *credit]}
|
|
assert result.ambiguous_keys == frozenset()
|
|
|
|
|
|
def test_equal_exact_groups_sharing_a_row_go_to_manual() -> None:
|
|
debit = [_row("100", debit=True)]
|
|
credit = [_row("100", debit=False), _row("100", debit=False)]
|
|
|
|
result = pack_rule_rows(debit, credit)
|
|
|
|
assert result.groups == ()
|
|
assert result.ambiguous_keys == {row.key for row in [*debit, *credit]}
|
|
|
|
|
|
def test_five_percent_boundary_and_no_many_to_many() -> None:
|
|
debit = [_row("100", debit=True)]
|
|
credit = [_row("21", debit=False) for _ in range(5)]
|
|
|
|
result = pack_rule_rows(debit, credit)
|
|
|
|
assert len(result.groups) == 1
|
|
assert len(result.groups[0].credit_rows) == MAX_SIDE_ROWS
|
|
assert result.groups[0].deviation_percent == Decimal("5")
|
|
|
|
many_to_many = pack_rule_rows(
|
|
[_row("40", debit=True), _row("60", debit=True)],
|
|
[_row("30", debit=False), _row("70", debit=False)],
|
|
)
|
|
assert many_to_many.groups == () # суммы сторон равны, но допустимой группы 1:N или N:1 нет
|
|
|
|
over_threshold = pack_rule_rows([_row("100", debit=True)], [_row("106", debit=False)])
|
|
assert over_threshold.groups == ()
|