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()