arm-ka-fixtures/synthgen/tests/test_osv_parser.py
2026-11-13 01:04:15 +04:00

93 lines
4.3 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.

"""Приёмка ОСВ парсером: CLI tabular-parser из зеркального worktree (`TP_WT`), маркер `parser`.
Запуск: `$UVRUN python -m pytest synthgen/tests -q -m parser`. Без окружения тесты пропускаются.
"""
import json
import os
import subprocess
from decimal import Decimal
from pathlib import Path
import pytest
from synthgen import osv
from synthgen.counterparties import OSV_ACCOUNTS
pytestmark = pytest.mark.parser
TP_WT = Path(os.environ.get("TP_WT") or "/Users/andrey.epifancev/projects/awp-ca/.wt/synthetic-group/tabular-parser")
PY = TP_WT / "api" / ".venv" / "bin" / "python"
ACCOUNTS = OSV_ACCOUNTS # все счета набора (решение 43: приёмка расширена с 60/62/76)
def _run(path: Path) -> dict:
env = {**os.environ, "ENV_FILE": str(TP_WT / "api" / ".env.test"), "PYTHONPATH": str(TP_WT / "api")}
proc = subprocess.run(
[str(PY), "-m", "src.parser.cli", str(path)], cwd=TP_WT / "api", env=env, capture_output=True, text=True, check=False
)
assert proc.stdout.strip(), proc.stderr
return json.loads(proc.stdout)
@pytest.fixture(scope="module")
def parsed(osv_world, tmp_path_factory):
if not PY.exists():
pytest.skip(f"нет окружения парсера: {PY}")
cfg, group, periods, books = osv_world
root = tmp_path_factory.mktemp("osv")
out = []
for member in group.members:
with_inn = member.code not in group.no_inn
for account in ACCOUNTS:
for period in (periods[0], periods[-1]):
data = osv.rows(books[member.code], account, period, member, group)
path = root / f"{member.code}_{period.key}_{osv.file_name(account, period)}"
osv.write(path, member, account, period, data, with_inn=with_inn)
out.append((member, account, period, data, with_inn, _run(path)))
return out
def test_parser_status_and_account(parsed):
bad = []
for member, account, period, _, _, res in parsed:
docs = res.get("documents", [])
if res.get("status") != "done" or len(docs) != 1 or docs[0]["header"]["account_code_raw"] != account:
bad.append((member.code, account, period.key, res.get("status"), res.get("error")))
assert not bad, bad[:10]
def test_parser_inn(parsed, osv_world):
_, group, _, _ = osv_world
group_names = {m.name_1c: m.inn for m in group.members}
bad = []
for member, account, period, data, with_inn, res in parsed:
got = [r for r in res["documents"][0]["rows"] if r["group_type"] == "COUNTERPARTY" and r["data_type"] == "BU"]
assert bool(got) == any(r.is_cp for r in data), (member.code, account, period.key) # 01/80/90 без контрагентов
if with_inn:
for r in got:
if r["counterparty_name"] in group_names and r["counterparty_inn"] != group_names[r["counterparty_name"]]:
bad.append((member.code, account, r["counterparty_name"], r["counterparty_inn"]))
elif any(r["counterparty_inn"] for r in got):
bad.append((member.code, account, "ИНН у ОСВ без ИНН"))
assert not bad, bad[:10]
def test_parser_totals(parsed):
"""Сумма корневой строки счёта в документе = «Итого» листа = суммам `rows`; проверки парсера чистые."""
keys = ("opening_debit", "opening_credit", "turnover_debit", "turnover_credit", "closing_debit", "closing_credit")
cent = Decimal("0.01")
bad = []
for member, account, period, data, _, res in parsed:
doc = res["documents"][0]
checks = doc["checks"]
if not checks["total_row_matches_root"] or checks["parent_sum_violations"] or checks["balance_identity_violations"]:
bad.append((member.code, account, period.key, checks))
if not data: # пустая ОСВ: только «Итого»
continue
root = next(r for r in doc["rows"] if r["group_type"] == "ACCOUNT" and r["data_type"] == "BU")
got = tuple(Decimal(str(root[k] or 0)).quantize(cent) for k in keys)
if got != tuple(v.quantize(cent) for v in osv.total_of(data)):
bad.append((member.code, account, period.key, got))
assert not bad, bad[:10]