feat(statements): парсер ББ и ОФР из Excel, контракт v1.2 (Срез 2b) #16

Merged
aep merged 43 commits from feat/14-slice-2b-statement-parser into main 2026-10-06 21:38:00 +03:00
Showing only changes of commit cb0bd062d4 - Show all commits

View File

@ -184,3 +184,47 @@ def test_inn_from_auditor_block_only_is_none(rules: Rules) -> None:
header, _ = parse_header(sheet, rules, 1, 9, _block(sheet, rules))
assert header.inn is None, f"Should not pick up auditor INN 9999999999, got {header.inn}"
def test_inn_not_taken_from_auditor_block_above_organization_inn(rules: Rules) -> None:
"""Guard test (b): auditor block ABOVE org INN; guard excludes auditor INN.
Layout (intentionally reversed from real files):
- Auditor block starts at row 3 (auditor_from=3)
- Auditor INN at row 4: value 7716021332
- Organization INN at row 8: value 3249004697
With guard (search 1..auditor_from-1=1..2): Neither INN found → header.inn is None ✓
Without guard (search 1..last_row=1..9): Auditor INN at row 4 found first → **FAIL** ✗
This test verifies auditor INN is never taken, even when it appears before org INN.
"""
sheet = sheet_from_rows(
[
[*at({3: "Бухгалтерский баланс"})],
[*at({3: "на 31 декабря 2024 г."})],
[*at({2: "Наименование аудиторской организации"})],
[*at({2: 'АО "ЮНИКОН"'})],
[
*at(
{
2: "Идентификационный номер налогоплательщика аудиторской организации",
33: "ИНН",
34: "7716021332",
}
)
],
[*at({7: 'ООО "ЗАВОД"'})],
[*at({3: "Организация", 7: 'ООО "ЗАВОД"', 13: "по ОКПО", 14: "16098811"})],
[*at({2: "Идентификационный номер налогоплательщика", 33: "ИНН", 34: "3249004697"})],
[*at({2: "Единица измерения", 7: "в тыс. рублей", 33: "по ОКЕИ", 34: "384"})],
]
)
header, _ = parse_header(sheet, rules, 1, 9, _block(sheet, rules))
assert header.inn is None, (
f"Auditor INN 7716021332 at row 4 is above auditor_from boundary; "
f"org INN 3249004697 at row 8 is below. Guard excludes both. "
f"Got {header.inn}"
)