arm-ka-fixtures/synthgen/tests/test_dictionary.py
2026-11-12 23:41:42 +04:00

105 lines
3.9 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.

import re
from pathlib import Path
import openpyxl
import pytest
from synthgen import dictionary, inn
from synthgen.dictionary import build
FIXTURES = Path("/Users/andrey.epifancev/projects/awp-ca/fixtures")
REAL_ECP = FIXTURES / "directories/ecp-export-russkiy-biskvit.xlsx"
REAL_OSV_DIRS = [FIXTURES / "osv/rb", FIXTURES / "osv/schastl"]
_FORMS = re.compile(r"\b(ООО|АО|ПАО|ЗАО|ОАО|ИП)\b", re.IGNORECASE)
_PARENS = re.compile(r"\(.*?(\)|$)")
def _norm(text: str) -> str:
"""Имя без формы, скобочного хвоста, регистра, ё и пунктуации."""
text = _PARENS.sub(" ", text)
text = _FORMS.sub(" ", text)
return re.sub(r"[^0-9a-zа-яё]+", " ", text.lower().replace("ё", "е")).strip()
def _real_names() -> tuple[set[str], int]:
"""Нормализованные имена из ЕЦП и колонки A первых листов всех ОСВ; второе значение — число прочитанных файлов."""
names: set[str] = set()
files = 0
wb = openpyxl.load_workbook(REAL_ECP, read_only=True)
for ws in wb.worksheets:
rows = ws.iter_rows(values_only=True)
header = next(rows, None)
if not header or "Организация" not in header:
continue
col = list(header).index("Организация")
names.update(_norm(str(r[col])) for r in rows if r[col])
files += 1
for root_dir in REAL_OSV_DIRS:
for path in sorted(root_dir.rglob("*.xlsx")):
if path.name.startswith("~$") or path.name.startswith("chart-of-accounts"):
continue
wb = openpyxl.load_workbook(path, read_only=True)
ws = wb.worksheets[0]
names.update(_norm(str(r[0])) for r in ws.iter_rows(max_col=1, values_only=True) if r and r[0])
files += 1
names.discard("")
return names, files
def _fio_keys(real: set[str]) -> set[str]:
"""Из «фамилия имя отчество» — «фамилия и о» (как в словаре: `Цветков А.П.`)."""
keys = set()
for name in real:
tokens = name.split()
if len(tokens) == 3 and all(len(t) > 1 for t in tokens):
keys.add(f"{tokens[0]} {tokens[1][0]} {tokens[2][0]}")
elif len(tokens) == 3 and len(tokens[1]) == 1 and len(tokens[2]) == 1:
keys.add(name)
return keys
def test_dictionary_size_and_kinds():
orgs = dictionary.load()
assert len(orgs) == 400
assert sum(o.kind == "банк" for o in orgs) == 30
assert 30 <= sum(o.form == "ИП" for o in orgs) <= 50
names = [o.name.lower() for o in orgs]
assert len(set(names)) == len(names)
def test_dictionary_inn():
for o in dictionary.load():
assert inn.is_valid(o.inn)
if o.form == "ИП":
assert len(o.inn) == 12 and o.kpp == ""
number = int(o.inn[2:-2])
else:
assert len(o.inn) == 10
assert re.fullmatch(r"\d{9}", o.kpp)
number = int(o.inn[2:-1])
assert 100001 <= number <= 109999
def test_dictionary_not_real():
if not FIXTURES.is_dir():
pytest.skip("нет каталога fixtures основного checkout")
real, files = _real_names()
assert files > 10 and len(real) > 500, "реальные фикстуры прочитаны не полностью"
fio = _fio_keys(real)
padded = [f" {n} " for n in real]
clash = []
for o in dictionary.load():
key = _norm(o.name)
if o.form == "ИП":
if key in fio or key in real:
clash.append(o.name)
elif key in real or any(f" {_norm(o.root)} " in p for p in padded):
clash.append(o.name)
assert clash == []
def test_build_is_reproducible(tmp_path):
out = tmp_path / "companies.csv"
build.write_csv(out)
assert out.read_bytes() == dictionary.CSV_PATH.read_bytes()