Andrey Epifancev ca7f64c3ef fix(osv): иерархия по отступам — подуровень счёта, строка закрытия, вложенность по суммам (AURORA-1858, #78)
В strategy_indent: счёт, чей код продолжает код соседа того же отступа (68.01 → 68.01.1), — ребёнок;
безымянная строка с оборотами счёта сразу за ним — его строка закрытия; аналитика на одном отступе
вкладывается по суммам только внутри родителя с нарушением и только если он после этого сходится.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 01:03:02 +04:00

75 lines
3.4 KiB
Python
Raw Permalink 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.

"""Генератор синтетики: детерминированность, состав набора и читаемость xlsb-писателя (не требует каталога fixtures)."""
import re
import time
from decimal import Decimal
from pathlib import Path
from src.parser.readers import read_book
from tests.synthetic.generate import FILE_NAMES, generate_all
from tests.synthetic.xlsb_writer import write_xlsb
# Сколько ждать между двумя генерациями: у записей zip разрешение метки времени — 2 с
ZIP_TIME_RESOLUTION_SECONDS = 2.1
LONG_TEXT_LENGTH = 400
BIG_ROW = 70000
def test_generation_is_reproducible_byte_for_byte(tmp_path: Path) -> None:
"""Повторный запуск (позже, чем разрешение меток времени) даёт те же байты для каждого файла набора."""
first = generate_all(tmp_path / "first")
time.sleep(ZIP_TIME_RESOLUTION_SECONDS)
second = generate_all(tmp_path / "second")
assert [path.name for path in first] == [path.name for path in second]
for left, right in zip(first, second, strict=True):
assert left.read_bytes() == right.read_bytes(), left.name
def test_generated_set_is_s01_to_s36_and_s40_to_s47_with_one_xlsb(tmp_path: Path) -> None:
names = [path.name for path in generate_all(tmp_path)]
assert names == list(FILE_NAMES)
assert [re.match(r"s(\d\d)_", name) is not None for name in names] == [True] * len(names)
# s33…s36 — строки-показатели (AURORA-1613, #58), плоская форма ОСВ 1С (#59) — s40…s44
numbers = [*range(1, 37), *range(40, 48)]
assert [name[1:3] for name in names] == [f"{number:02d}" for number in numbers]
assert [name for name in names if name.endswith(".xlsb")] == ["s13_no_outline_indent.xlsb"]
assert sorted(path.name for path in tmp_path.iterdir()) == sorted(names)
def test_xlsb_writer_round_trips_through_the_reader() -> None:
"""Тексты (с ведущими пробелами и кириллицей), числа и несколько листов читаются `src.parser.readers`."""
long_text = "щ" * LONG_TEXT_LENGTH # запись длиннее 127 байт: длина кодируется двумя байтами
data = write_xlsb(
{
"Лист_1": {
(1, 1): "Привет",
(2, 3): Decimal("1234567.89"),
(3, 1): " отступ",
(4, 2): long_text,
(BIG_ROW, 4): 7,
},
"Второй": {(1, 1): "Привет"},
}
)
sheets, warnings = read_book(data, "XLSB")
assert [sheet.name for sheet in sheets] == ["Лист_1", "Второй"]
first = sheets[0]
assert first.text_at(1, 1) == "Привет"
assert first.cell(2, 3).value == Decimal("1234567.89")
assert (first.text_at(3, 1), first.cell(3, 1).indent) == ("отступ", 4)
assert first.text_at(4, 2) == long_text
assert first.cell(BIG_ROW, 4).value == Decimal(7)
assert first.n_rows == BIG_ROW
assert sheets[1].text_at(1, 1) == "Привет"
assert warnings == ["xlsb: outline level недоступен"]
def test_xlsb_writer_is_deterministic() -> None:
cells = {"Лист_1": {(1, 1): "а", (2, 2): Decimal("1.50")}}
assert write_xlsb(cells) == write_xlsb(cells)