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