arm-tabular-parser/api/tests/fixtures/test_classify_real.py
Andrey Epifancev 8b91bcae3d «Кол-во» — количество, «Вал» без точки — не валюта; таблица кончается на первой «Итого» (#47, AURORA-1663, AURORA-1664)
AURORA-1663: словарь строк data_types.yaml — «Кол-во» даёт QUANTITY, «Вал» без
точки больше не CURRENCY (материал «Вал» на счёте 10); метка — «Вал.» и код
валюты.

AURORA-1664: _find_total берёт первую «Итого» на корневом уровне оформления
(раньше — последнюю), вторая итоговая строка сразу за ней по-прежнему часть
таблицы; всё ниже вне документа. Числа в хвосте дают предупреждение «ниже
таблицы (после «Итого») есть числа» без смены статуса. Закрывает tp#7 M-2.
Предупреждение появляется и у реальной фикстуры rb/2024-Q1 (контрольные суммы
правее таблицы под «Итого») — тест property допускает его.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 11:50:39 +04:00

84 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Классификация строк на реальных ОСВ из ../fixtures (spec «Классификация строк», R-13, T4.5).
Свойства на всех файлах: ровно одна строка TOTAL — последняя, уровень 0 без родителя; строки без предупреждений
о числах; подвал после «Итого» не попадает ни в строки, ни в `extra`. Конкретные строки — по сценариям спеки.
"""
from pathlib import Path
import pytest
from real_osv import parse_real
from src.parser.detect import INN_LENGTHS
SCHASTL_62_Q3 = Path("osv/schastl/2024-Q3/Оборотносальдовая ведомость по счету 62 за 3 квартал 2024 г..xls")
RB_66_FY = Path("osv/rb/2024-FY/ОСВ_66 за 2024.xls")
RB_60_Q1_2024 = Path("osv/rb/2024-Q1/сч 60_ 1 кв 2024 г.xlsx") # после «Итого» — числа правее таблицы
@pytest.mark.fixtures
def test_real_osv_has_exactly_one_total_as_last_root_row(osv_file: Path) -> None:
parsed = parse_real(osv_file)
totals = [row for row in parsed.rows if row.group_type == "TOTAL"]
assert len(totals) == 1
total = totals[0]
assert total is parsed.rows[-1]
assert (total.level, total.parent, total.body.row) == (0, None, parsed.total_row)
# Допустимо одно предупреждение: числа в хвосте под «Итого» (контрольные суммы правее таблицы, AURORA-1664)
assert [warning for warning in parsed.warnings if "ниже таблицы" not in warning] == []
@pytest.mark.fixtures
def test_real_osv_rows_are_classified_into_schema(osv_file: Path) -> None:
parsed = parse_real(osv_file)
for row in parsed.data_rows:
assert row.data_type == parsed.doc.data_type, row # в фикстурах нет строк «Вал.»/«Кол.»
# Ни одной строки за схемой группировок: OTHER без имени группы допустим только у строк-показателей
if row.data_type not in ("CURRENCY", "QUANTITY"):
assert not (row.group_type == "OTHER" and row.group_name is None), row
if row.counterparty_inn is not None:
assert row.counterparty_inn.isdigit()
assert len(row.counterparty_inn) in INN_LENGTHS
@pytest.mark.fixtures
def test_schastl_62_department_counterparty_with_and_without_inn(fixtures_dir: Path) -> None:
"""Spec «Подразделение, контрагент с ИНН, физлицо»."""
parsed = parse_real(fixtures_dir / SCHASTL_62_Q3)
assert {row.group_type for row in parsed.by_text("БП Аренда")} == {"DEPARTMENT"}
poultry = parsed.by_text("МАЛЕЧКИНСКАЯ ПТИЦЕФАБРИКА ООО")
assert poultry
for row in poultry:
assert row.group_type == "COUNTERPARTY"
assert row.counterparty_name == "МАЛЕЧКИНСКАЯ ПТИЦЕФАБРИКА ООО"
assert row.counterparty_inn is not None
assert row.counterparty_inn.isdigit()
assert len(row.counterparty_inn) == min(INN_LENGTHS)
(payroll,) = parsed.by_text("Ведомость в счет з/платы")
assert (payroll.group_type, payroll.counterparty_inn) == ("COUNTERPARTY", None)
@pytest.mark.fixtures
def test_rb_66_counterparty_inherits_subaccount(fixtures_dir: Path) -> None:
"""Одноимённый контрагент под 66.03 и 66.04 различается счётом ветки (Review Focus 3)."""
parsed = parse_real(fixtures_dir / RB_66_FY)
yug = parsed.by_text("Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ")
assert [row.account_code_raw for row in yug] == ["66.03", "66.04"]
assert {row.group_type for row in yug} == {"COUNTERPARTY"}
assert yug[0].parent != yug[1].parent
@pytest.mark.fixtures
def test_rb_60_footer_after_total_is_not_data(fixtures_dir: Path) -> None:
"""T4.5: числа правее таблицы после «Итого» не становятся строками и не попадают в `extra`."""
parsed = parse_real(fixtures_dir / RB_60_Q1_2024)
assert parsed.rows[-1].group_type == "TOTAL"
assert parsed.total_row is not None
assert parsed.sheet.n_rows > parsed.total_row # подвал есть
assert all(row.extra is None for row in parsed.rows)