synthgen: словарь без совпадений с реальными именами, усиленный тест, prefix в Member (#38)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b2a89394c9
commit
f215bab9b0
@ -26,7 +26,7 @@ _SECTORS: dict[str, list[tuple[str, str]]] = {
|
||||
"торговля": [("Рынок", "m"), ("Склад", "m"), ("Лавка", "f"), ("Ярмарка", "f"), ("Базар", "m"),
|
||||
("Торг", "m"), ("Магазин", "m"), ("Прилавок", "m"), ("Пассаж", "m"), ("Товар", "m")],
|
||||
"логистика": [("Путь", "m"), ("Маршрут", "m"), ("Фургон", "m"), ("Трасса", "f"), ("Обоз", "m"),
|
||||
("Тягач", "m"), ("Рейс", "m"), ("Перевал", "m"), ("Караван", "m"), ("Магистраль", "f")],
|
||||
("Тягач", "m"), ("Рейс", "m"), ("Перевал", "m"), ("Караван", "m"), ("Эстакада", "f")],
|
||||
"производство": [("Завод", "m"), ("Цех", "m"), ("Станок", "m"), ("Мельница", "f"), ("Пекарня", "f"),
|
||||
("Кузница", "f"), ("Фабрика", "f"), ("Горн", "m"), ("Верфь", "f"), ("Мануфактура", "f")],
|
||||
"услуги": [("Сервис", "m"), ("Бюро", "n"), ("Центр", "m"), ("Консалт", "m"), ("Профи", "m"),
|
||||
@ -40,7 +40,7 @@ _BANK_WORDS = ["Банк", "Кредитный Банк", "Деловой Бан
|
||||
_SURNAMES = [
|
||||
"Авдонина", "Бражников", "Ветрова", "Голубев", "Дроздова", "Ермаков", "Жарова", "Зуев", "Игнатова", "Копылов",
|
||||
"Лаврова", "Мартынов", "Норина", "Овчаров", "Пименова", "Рябов", "Сорокина", "Тарасов", "Устинова", "Фролов",
|
||||
"Хромова", "Цветков", "Чернова", "Шаров", "Щукина", "Юдин", "Яковлева", "Баранов", "Волкова", "Гусев",
|
||||
"Хромова", "Цветаев", "Чернова", "Шаров", "Щукина", "Юдин", "Яковлева", "Баранов", "Волкова", "Гусев",
|
||||
"Денисова", "Елисеев", "Зайцева", "Исаев", "Калинина", "Леонов", "Маслова", "Назаров", "Орлова", "Панов",
|
||||
]
|
||||
_INITIALS = ["Е.И.", "А.П.", "Н.С.", "В.Д.", "О.А.", "М.В.", "И.Н.", "С.К.", "Т.Р.", "Д.Л."]
|
||||
|
||||
@ -145,19 +145,19 @@ name;form;inn;kpp;kind
|
||||
Заревная Арка ООО;ООО;0001001448;004401001;строительство
|
||||
Лазурный Амбар АО;АО;0001001455;004501001;агро
|
||||
Лазурный Товар ООО;ООО;0001001462;004601001;торговля
|
||||
Лазурная Магистраль ООО;ООО;0001001470;004701001;логистика
|
||||
Лазурная Эстакада ООО;ООО;0001001470;004701001;логистика
|
||||
Лазурная Мануфактура ООО;ООО;0001001487;004801001;производство
|
||||
Лазурный Приём ООО;ООО;0001001494;004901001;услуги
|
||||
Лазурный Мост АО;АО;0001001504;005001001;строительство
|
||||
Рубиновый Амбар ООО;ООО;0001001511;005101001;агро
|
||||
Рубиновый Товар ООО;ООО;0001001529;005201001;торговля
|
||||
Рубиновая Магистраль ПАО;ПАО;0001001536;005301001;логистика
|
||||
Рубиновая Эстакада ПАО;ПАО;0001001536;005301001;логистика
|
||||
Рубиновая Мануфактура ООО;ООО;0001001543;005401001;производство
|
||||
Рубиновый Приём АО;АО;0001001550;005501001;услуги
|
||||
Рубиновый Мост ООО;ООО;0001001568;005601001;строительство
|
||||
Лунный Амбар ООО;ООО;0001001575;005701001;агро
|
||||
Лунный Товар ООО;ООО;0001001582;005801001;торговля
|
||||
Лунная Магистраль ООО;ООО;0001001590;005901001;логистика
|
||||
Лунная Эстакада ООО;ООО;0001001590;005901001;логистика
|
||||
Лунная Мануфактура АО;АО;0001001600;006001001;производство
|
||||
Лунный Приём ООО;ООО;0001001617;006101001;услуги
|
||||
Лунный Мост ООО;ООО;0001001624;006201001;строительство
|
||||
@ -307,19 +307,19 @@ name;form;inn;kpp;kind
|
||||
Лунная Арка ПАО;ПАО;0001003068;000601001;строительство
|
||||
Янтарный Амбар ООО;ООО;0001003075;000701001;агро
|
||||
Янтарный Товар ООО;ООО;0001003082;000801001;торговля
|
||||
Янтарная Магистраль ООО;ООО;0001003090;000901001;логистика
|
||||
Янтарная Эстакада ООО;ООО;0001003090;000901001;логистика
|
||||
Янтарная Мануфактура АО;АО;0001003100;001001001;производство
|
||||
Янтарный Приём ООО;ООО;0001003117;001101001;услуги
|
||||
Янтарный Мост ООО;ООО;0001003124;001201001;строительство
|
||||
Серебряный Амбар ООО;ООО;0001003131;001301001;агро
|
||||
Серебряный Товар ООО;ООО;0001003149;001401001;торговля
|
||||
Серебряная Магистраль АО;АО;0001003156;001501001;логистика
|
||||
Серебряная Эстакада АО;АО;0001003156;001501001;логистика
|
||||
Серебряная Мануфактура ООО;ООО;0001003163;001601001;производство
|
||||
Серебряный Приём ООО;ООО;0001003170;001701001;услуги
|
||||
Серебряный Мост ООО;ООО;0001003188;001801001;строительство
|
||||
Северный Амбар ООО;ООО;0001003195;001901001;агро
|
||||
Северный Товар АО;АО;0001003205;002001001;торговля
|
||||
Северная Магистраль ООО;ООО;0001003212;002101001;логистика
|
||||
Северная Эстакада ООО;ООО;0001003212;002101001;логистика
|
||||
Северная Мануфактура ООО;ООО;0001003220;002201001;производство
|
||||
Северный Приём ПАО;ПАО;0001003237;002301001;услуги
|
||||
Северный Мост ООО;ООО;0001003244;002401001;строительство
|
||||
@ -350,7 +350,7 @@ name;form;inn;kpp;kind
|
||||
Устинова Т.Р. ИП;ИП;000010034915;;услуги
|
||||
Фролов Д.Л. ИП;ИП;000010035010;;торговля
|
||||
Хромова Е.И. ИП;ИП;000010035193;;логистика
|
||||
Цветков А.П. ИП;ИП;000010035267;;агро
|
||||
Цветаев А.П. ИП;ИП;000010035267;;агро
|
||||
Чернова Н.С. ИП;ИП;000010035330;;строительство
|
||||
Шаров В.Д. ИП;ИП;000010035404;;производство
|
||||
Щукина О.А. ИП;ИП;000010035588;;услуги
|
||||
|
||||
|
@ -15,6 +15,7 @@ _CENT = Decimal("0.01")
|
||||
@dataclass(frozen=True)
|
||||
class Member:
|
||||
code: str # C01, C02, …
|
||||
prefix: str # префикс ГК
|
||||
org: Org # запись словаря, давшая корень имени
|
||||
name: str # АО "<prefix> <корень>"
|
||||
name_1c: str # <prefix> <корень> АО — как 1С пишет контрагента
|
||||
@ -48,9 +49,7 @@ def member_kpp(i: int) -> str:
|
||||
|
||||
def variant_spelling(member: Member) -> str:
|
||||
"""Вариантное написание члена группы в ОСВ без ИНН: корень, префикс, форма без кавычек (решение 17)."""
|
||||
root = member.org.root
|
||||
prefix = member.name_1c[: -(len(root) + len(member.org.form) + 2)]
|
||||
return f"{root} {prefix} {member.org.form}"
|
||||
return f"{member.org.root} {member.prefix} {member.org.form}"
|
||||
|
||||
|
||||
def build_group(cfg: Config, orgs: list[Org]) -> Group:
|
||||
@ -80,6 +79,7 @@ def build_group(cfg: Config, orgs: list[Org]) -> Group:
|
||||
members.append(
|
||||
Member(
|
||||
code=f"C{i:02d}",
|
||||
prefix=cfg.prefix,
|
||||
org=org,
|
||||
name=f'{org.form} "{cfg.prefix} {org.root}"',
|
||||
name_1c=f"{cfg.prefix} {org.root} {org.form}",
|
||||
|
||||
@ -7,16 +7,57 @@ import pytest
|
||||
from synthgen import dictionary, inn
|
||||
from synthgen.dictionary import build
|
||||
|
||||
REAL_ECP = Path("/Users/andrey.epifancev/projects/awp-ca/fixtures/directories/ecp-export-russkiy-biskvit.xlsx")
|
||||
REAL_OSV_DIR = Path("/Users/andrey.epifancev/projects/awp-ca/fixtures/osv/rb/2024-Q1")
|
||||
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
|
||||
@ -39,30 +80,21 @@ def test_dictionary_inn():
|
||||
assert 100001 <= number <= 109999
|
||||
|
||||
|
||||
def _real_names() -> set[str]:
|
||||
names: set[str] = set()
|
||||
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])
|
||||
for path in sorted(REAL_OSV_DIR.glob("*.xlsx")):
|
||||
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])
|
||||
names.discard("")
|
||||
return names
|
||||
|
||||
|
||||
def test_dictionary_not_real():
|
||||
if not REAL_ECP.exists() or not REAL_OSV_DIR.is_dir():
|
||||
pytest.skip("нет реальных фикстур")
|
||||
real = _real_names()
|
||||
assert len(real) > 50
|
||||
clash = [o.name for o in dictionary.load() if _norm(o.name) in 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 == []
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user