synthgen: словарь компаний, состав группы, выгрузка ЕЦП (#38)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
801d9948ca
commit
b2a89394c9
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@
|
||||
*.tmp
|
||||
.superpowers/
|
||||
out/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
@ -278,3 +278,7 @@ README прогона, точные субсчета внутри перечис
|
||||
| 14 | 12.11.2026 | Скрипт сборки шаблонов ББ/ОФР называется `build_forms_templates.py` (как в плане; §6.2 называл `build_templates.py`) (R-2) | отличать от `build_osv_style.py` | нет |
|
||||
| 15 | 12.11.2026 | Префикс ГК — буквы латиницы и кириллицы + цифры, 2–12 символов (`config._PREFIX_RE`) | §3 говорит «буквы/цифры» без уточнения алфавита; название группы русское | префикс с кириллицей не пройдёт, если ядро ждёт только латиницу — сузить регулярное выражение |
|
||||
| 16 | 12.11.2026 | Зерно `rng_for`/`stable_hash`: первые 8 байт SHA-256 читаются как big-endian | §8/§3 не фиксируют порядок байт; важна только стабильность между запусками | смена порядка меняет все наборы для того же seed одноразово — перегенерировать образцы |
|
||||
| 17 | 12.11.2026 | `synthgen/dictionary/` — пакет с `__init__.py` (там `Org`, `load`), модуля `dictionary.py` нет | `python -m synthgen.dictionary.build` требует пакет; `dictionary.py` рядом с каталогом `dictionary/` давал бы конфликт имён; импорт `synthgen.dictionary` не меняется | нет |
|
||||
| 18 | 12.11.2026 | Вариантное написание члена группы в ОСВ без ИНН: перестановка `<корень> <prefix> <форма>` без кавычек (`group.variant_spelling`); вариант выбирает `rng_for(seed, "group", prefix)` среди остальных членов | нужно написание, которое не совпадёт с `name` и `name_1c` посимвольно, но читается человеком; точный механизм сопоставления ядра по имени не проверялся | если ядро нормализует порядок слов и сопоставит автоматически — ручного сопоставления не будет, поменять правило варианта |
|
||||
| 19 | 12.11.2026 | Доли головной: дочерняя без миноритария — `1.00`; у 1–2 дочерних (при `group_size=2` — у единственной) доля 0.51–0.99, миноритарий — внешняя организация словаря (юрлицо не банк) | §4 тестов требует `head_share ∈ [0.51, 1.00]` и миноритария у 1–2; миноритарий остаётся внешним контрагентом, источник долей — только данные группы | миноритарий не как контрагент в ОСВ — не будет связи, исключить из `externals` |
|
||||
| 20 | 12.11.2026 | В ЕЦП «Базовый РФ ГК в CRM» — `Ростовский региональный филиал`, `Тип` — `ЮЛ`, как в #35; имя группы в колонке «Группа компаний» — `ГК <prefix>` (в #35 было без «ГК») | импорт ядра принимает любые значения (обязательны ИНН, iD ГК, Группа компаний, Организация, Тип); `ГК <prefix>` задано §4.2 | нет |
|
||||
|
||||
34
synthgen/dictionary/__init__.py
Normal file
34
synthgen/dictionary/__init__.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""Словарь вымышленных организаций (спека §4.1): чтение закоммиченного `companies.csv`.
|
||||
|
||||
Это пакет (а не модуль `dictionary.py`), чтобы работал `python -m synthgen.dictionary.build` (решение 17).
|
||||
"""
|
||||
|
||||
import csv
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
CSV_PATH = Path(__file__).with_name("companies.csv")
|
||||
HEADER = ["name", "form", "inn", "kpp", "kind"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Org:
|
||||
"""Организация словаря. `name` — как её пишет 1С: `Северная Нива ООО`, `Аверина Е.И. ИП`."""
|
||||
|
||||
name: str
|
||||
form: str # ООО | АО | ПАО | ИП
|
||||
inn: str
|
||||
kpp: str # у ИП пусто
|
||||
kind: str # отрасль или «банк»
|
||||
|
||||
@property
|
||||
def root(self) -> str:
|
||||
"""Имя без формы: `Северная Нива`."""
|
||||
return self.name.rsplit(" ", 1)[0]
|
||||
|
||||
|
||||
def load(path: Path | None = None) -> list[Org]:
|
||||
"""Организации словаря в порядке файла."""
|
||||
with (path or CSV_PATH).open(encoding="utf-8", newline="") as fh:
|
||||
reader = csv.DictReader(fh, delimiter=";")
|
||||
return [Org(r["name"], r["form"], r["inn"], r["kpp"], r["kind"]) for r in reader]
|
||||
109
synthgen/dictionary/build.py
Normal file
109
synthgen/dictionary/build.py
Normal file
@ -0,0 +1,109 @@
|
||||
"""Сборка `companies.csv` (спека §4.1): 400 вымышленных организаций без случайности.
|
||||
|
||||
Запуск из корня репозитория: `python -m synthgen.dictionary.build` — перезаписывает `companies.csv`.
|
||||
Имена — перебор «прилагательное × существительное» по отраслям с фиксированным шагом, ИНН — подряд от 0100001.
|
||||
"""
|
||||
|
||||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
from synthgen import inn
|
||||
from synthgen.dictionary import CSV_PATH, HEADER, Org
|
||||
|
||||
FIRST_SEQ = 100001 # ИНН-номер 0100001
|
||||
PER_SECTOR = 55
|
||||
IP_COUNT = 40
|
||||
BANK_COUNT = 30
|
||||
|
||||
# (основа, окончания для m/f/n)
|
||||
_ADJ = ["Лазурн", "Янтарн", "Изумрудн", "Рубинов", "Серебрян", "Заревн", "Лунн", "Северн"]
|
||||
_ENDINGS = {"m": "ый", "f": "ая", "n": "ое"}
|
||||
|
||||
# отрасль → существительные (слово, род)
|
||||
_SECTORS: dict[str, list[tuple[str, str]]] = {
|
||||
"агро": [("Нива", "f"), ("Колос", "m"), ("Урожай", "m"), ("Пашня", "f"), ("Зерно", "n"),
|
||||
("Луг", "m"), ("Сад", "m"), ("Борозда", "f"), ("Поле", "n"), ("Амбар", "m")],
|
||||
"торговля": [("Рынок", "m"), ("Склад", "m"), ("Лавка", "f"), ("Ярмарка", "f"), ("Базар", "m"),
|
||||
("Торг", "m"), ("Магазин", "m"), ("Прилавок", "m"), ("Пассаж", "m"), ("Товар", "m")],
|
||||
"логистика": [("Путь", "m"), ("Маршрут", "m"), ("Фургон", "m"), ("Трасса", "f"), ("Обоз", "m"),
|
||||
("Тягач", "m"), ("Рейс", "m"), ("Перевал", "m"), ("Караван", "m"), ("Магистраль", "f")],
|
||||
"производство": [("Завод", "m"), ("Цех", "m"), ("Станок", "m"), ("Мельница", "f"), ("Пекарня", "f"),
|
||||
("Кузница", "f"), ("Фабрика", "f"), ("Горн", "m"), ("Верфь", "f"), ("Мануфактура", "f")],
|
||||
"услуги": [("Сервис", "m"), ("Бюро", "n"), ("Центр", "m"), ("Консалт", "m"), ("Профи", "m"),
|
||||
("Партнёр", "m"), ("Совет", "m"), ("Контора", "f"), ("Мастерская", "f"), ("Приём", "m")],
|
||||
"строительство": [("Фундамент", "m"), ("Кирпич", "m"), ("Монолит", "m"), ("Каркас", "m"), ("Проект", "m"),
|
||||
("Стройка", "f"), ("Бастион", "m"), ("Зодчий", "m"), ("Арка", "f"), ("Мост", "m")],
|
||||
}
|
||||
|
||||
_BANK_WORDS = ["Банк", "Кредитный Банк", "Деловой Банк", "Земельный Банк"]
|
||||
|
||||
_SURNAMES = [
|
||||
"Авдонина", "Бражников", "Ветрова", "Голубев", "Дроздова", "Ермаков", "Жарова", "Зуев", "Игнатова", "Копылов",
|
||||
"Лаврова", "Мартынов", "Норина", "Овчаров", "Пименова", "Рябов", "Сорокина", "Тарасов", "Устинова", "Фролов",
|
||||
"Хромова", "Цветков", "Чернова", "Шаров", "Щукина", "Юдин", "Яковлева", "Баранов", "Волкова", "Гусев",
|
||||
"Денисова", "Елисеев", "Зайцева", "Исаев", "Калинина", "Леонов", "Маслова", "Назаров", "Орлова", "Панов",
|
||||
]
|
||||
_INITIALS = ["Е.И.", "А.П.", "Н.С.", "В.Д.", "О.А.", "М.В.", "И.Н.", "С.К.", "Т.Р.", "Д.Л."]
|
||||
_IP_KINDS = ["услуги", "торговля", "логистика", "агро", "строительство", "производство"]
|
||||
|
||||
|
||||
def _kpp(seq: int) -> str:
|
||||
"""КПП: `00` + двузначный номер + `01001` (как в #35)."""
|
||||
return f"00{seq % 100:02d}01001"
|
||||
|
||||
|
||||
def _legal_form(idx: int) -> str:
|
||||
if idx % 17 == 0:
|
||||
return "ПАО"
|
||||
return "АО" if idx % 5 == 0 else "ООО"
|
||||
|
||||
|
||||
def build_records() -> list[Org]:
|
||||
"""400 записей: 330 юрлиц по отраслям (с шагом 3 по сетке 80 пар), 40 ИП, 30 банков."""
|
||||
orgs: list[Org] = []
|
||||
seq = FIRST_SEQ
|
||||
|
||||
def add(name: str, form: str, kind: str) -> None:
|
||||
nonlocal seq
|
||||
if form == "ИП":
|
||||
orgs.append(Org(name, form, inn.make_inn12(seq), "", kind))
|
||||
else:
|
||||
orgs.append(Org(name, form, inn.make_inn10(seq), _kpp(seq), kind))
|
||||
seq += 1
|
||||
|
||||
legal_idx = 0
|
||||
for j in range(PER_SECTOR):
|
||||
pair = (j * 3) % (len(_ADJ) * 10) # gcd(3, 80) = 1 — пары не повторяются
|
||||
adj_stem = _ADJ[pair % len(_ADJ)]
|
||||
for kind, nouns in _SECTORS.items():
|
||||
noun, gender = nouns[pair // len(_ADJ)]
|
||||
legal_idx += 1
|
||||
form = _legal_form(legal_idx)
|
||||
add(f"{adj_stem}{_ENDINGS[gender]} {noun} {form}", form, kind)
|
||||
for j in range(IP_COUNT):
|
||||
add(f"{_SURNAMES[j]} {_INITIALS[j % len(_INITIALS)]} ИП", "ИП", _IP_KINDS[j % len(_IP_KINDS)])
|
||||
for j in range(BANK_COUNT):
|
||||
stem = _ADJ[j % len(_ADJ)]
|
||||
words = _BANK_WORDS[(j // len(_ADJ)) % len(_BANK_WORDS)]
|
||||
form = "ПАО" if j % 2 else "АО"
|
||||
add(f"{stem}ый {words} {form}", form, "банк")
|
||||
return orgs
|
||||
|
||||
|
||||
def write_csv(path: Path, orgs: list[Org] | None = None) -> None:
|
||||
rows = orgs if orgs is not None else build_records()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8", newline="") as fh:
|
||||
writer = csv.writer(fh, delimiter=";", lineterminator="\n")
|
||||
writer.writerow(HEADER)
|
||||
for o in rows:
|
||||
writer.writerow([o.name, o.form, o.inn, o.kpp, o.kind])
|
||||
|
||||
|
||||
def main() -> None:
|
||||
write_csv(CSV_PATH)
|
||||
print(f"записано {CSV_PATH}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
401
synthgen/dictionary/companies.csv
Normal file
401
synthgen/dictionary/companies.csv
Normal file
@ -0,0 +1,401 @@
|
||||
name;form;inn;kpp;kind
|
||||
Лазурная Нива ООО;ООО;0001000010;000101001;агро
|
||||
Лазурный Рынок ООО;ООО;0001000028;000201001;торговля
|
||||
Лазурный Путь ООО;ООО;0001000035;000301001;логистика
|
||||
Лазурный Завод ООО;ООО;0001000042;000401001;производство
|
||||
Лазурный Сервис АО;АО;0001000050;000501001;услуги
|
||||
Лазурный Фундамент ООО;ООО;0001000067;000601001;строительство
|
||||
Рубиновая Нива ООО;ООО;0001000074;000701001;агро
|
||||
Рубиновый Рынок ООО;ООО;0001000081;000801001;торговля
|
||||
Рубиновый Путь ООО;ООО;0001000099;000901001;логистика
|
||||
Рубиновый Завод АО;АО;0001000109;001001001;производство
|
||||
Рубиновый Сервис ООО;ООО;0001000116;001101001;услуги
|
||||
Рубиновый Фундамент ООО;ООО;0001000123;001201001;строительство
|
||||
Лунная Нива ООО;ООО;0001000130;001301001;агро
|
||||
Лунный Рынок ООО;ООО;0001000148;001401001;торговля
|
||||
Лунный Путь АО;АО;0001000155;001501001;логистика
|
||||
Лунный Завод ООО;ООО;0001000162;001601001;производство
|
||||
Лунный Сервис ПАО;ПАО;0001000170;001701001;услуги
|
||||
Лунный Фундамент ООО;ООО;0001000187;001801001;строительство
|
||||
Янтарный Колос ООО;ООО;0001000194;001901001;агро
|
||||
Янтарный Склад АО;АО;0001000204;002001001;торговля
|
||||
Янтарный Маршрут ООО;ООО;0001000211;002101001;логистика
|
||||
Янтарный Цех ООО;ООО;0001000229;002201001;производство
|
||||
Янтарное Бюро ООО;ООО;0001000236;002301001;услуги
|
||||
Янтарный Кирпич ООО;ООО;0001000243;002401001;строительство
|
||||
Серебряный Колос АО;АО;0001000250;002501001;агро
|
||||
Серебряный Склад ООО;ООО;0001000268;002601001;торговля
|
||||
Серебряный Маршрут ООО;ООО;0001000275;002701001;логистика
|
||||
Серебряный Цех ООО;ООО;0001000282;002801001;производство
|
||||
Серебряное Бюро ООО;ООО;0001000290;002901001;услуги
|
||||
Серебряный Кирпич АО;АО;0001000300;003001001;строительство
|
||||
Северный Колос ООО;ООО;0001000317;003101001;агро
|
||||
Северный Склад ООО;ООО;0001000324;003201001;торговля
|
||||
Северный Маршрут ООО;ООО;0001000331;003301001;логистика
|
||||
Северный Цех ПАО;ПАО;0001000349;003401001;производство
|
||||
Северное Бюро АО;АО;0001000356;003501001;услуги
|
||||
Северный Кирпич ООО;ООО;0001000363;003601001;строительство
|
||||
Изумрудный Урожай ООО;ООО;0001000370;003701001;агро
|
||||
Изумрудная Лавка ООО;ООО;0001000388;003801001;торговля
|
||||
Изумрудный Фургон ООО;ООО;0001000395;003901001;логистика
|
||||
Изумрудный Станок АО;АО;0001000405;004001001;производство
|
||||
Изумрудный Центр ООО;ООО;0001000412;004101001;услуги
|
||||
Изумрудный Монолит ООО;ООО;0001000420;004201001;строительство
|
||||
Заревный Урожай ООО;ООО;0001000437;004301001;агро
|
||||
Заревная Лавка ООО;ООО;0001000444;004401001;торговля
|
||||
Заревный Фургон АО;АО;0001000451;004501001;логистика
|
||||
Заревный Станок ООО;ООО;0001000469;004601001;производство
|
||||
Заревный Центр ООО;ООО;0001000476;004701001;услуги
|
||||
Заревный Монолит ООО;ООО;0001000483;004801001;строительство
|
||||
Лазурная Пашня ООО;ООО;0001000490;004901001;агро
|
||||
Лазурная Ярмарка АО;АО;0001000500;005001001;торговля
|
||||
Лазурная Трасса ПАО;ПАО;0001000518;005101001;логистика
|
||||
Лазурная Мельница ООО;ООО;0001000525;005201001;производство
|
||||
Лазурный Консалт ООО;ООО;0001000532;005301001;услуги
|
||||
Лазурный Каркас ООО;ООО;0001000540;005401001;строительство
|
||||
Рубиновая Пашня АО;АО;0001000557;005501001;агро
|
||||
Рубиновая Ярмарка ООО;ООО;0001000564;005601001;торговля
|
||||
Рубиновая Трасса ООО;ООО;0001000571;005701001;логистика
|
||||
Рубиновая Мельница ООО;ООО;0001000589;005801001;производство
|
||||
Рубиновый Консалт ООО;ООО;0001000596;005901001;услуги
|
||||
Рубиновый Каркас АО;АО;0001000606;006001001;строительство
|
||||
Лунная Пашня ООО;ООО;0001000613;006101001;агро
|
||||
Лунная Ярмарка ООО;ООО;0001000620;006201001;торговля
|
||||
Лунная Трасса ООО;ООО;0001000638;006301001;логистика
|
||||
Лунная Мельница ООО;ООО;0001000645;006401001;производство
|
||||
Лунный Консалт АО;АО;0001000652;006501001;услуги
|
||||
Лунный Каркас ООО;ООО;0001000660;006601001;строительство
|
||||
Янтарное Зерно ООО;ООО;0001000677;006701001;агро
|
||||
Янтарный Базар ПАО;ПАО;0001000684;006801001;торговля
|
||||
Янтарный Обоз ООО;ООО;0001000691;006901001;логистика
|
||||
Янтарная Пекарня АО;АО;0001000701;007001001;производство
|
||||
Янтарный Профи ООО;ООО;0001000719;007101001;услуги
|
||||
Янтарный Проект ООО;ООО;0001000726;007201001;строительство
|
||||
Серебряное Зерно ООО;ООО;0001000733;007301001;агро
|
||||
Серебряный Базар ООО;ООО;0001000740;007401001;торговля
|
||||
Серебряный Обоз АО;АО;0001000758;007501001;логистика
|
||||
Серебряная Пекарня ООО;ООО;0001000765;007601001;производство
|
||||
Серебряный Профи ООО;ООО;0001000772;007701001;услуги
|
||||
Серебряный Проект ООО;ООО;0001000780;007801001;строительство
|
||||
Северное Зерно ООО;ООО;0001000797;007901001;агро
|
||||
Северный Базар АО;АО;0001000807;008001001;торговля
|
||||
Северный Обоз ООО;ООО;0001000814;008101001;логистика
|
||||
Северная Пекарня ООО;ООО;0001000821;008201001;производство
|
||||
Северный Профи ООО;ООО;0001000839;008301001;услуги
|
||||
Северный Проект ООО;ООО;0001000846;008401001;строительство
|
||||
Изумрудный Луг ПАО;ПАО;0001000853;008501001;агро
|
||||
Изумрудный Торг ООО;ООО;0001000860;008601001;торговля
|
||||
Изумрудный Тягач ООО;ООО;0001000878;008701001;логистика
|
||||
Изумрудная Кузница ООО;ООО;0001000885;008801001;производство
|
||||
Изумрудный Партнёр ООО;ООО;0001000892;008901001;услуги
|
||||
Изумрудная Стройка АО;АО;0001000902;009001001;строительство
|
||||
Заревный Луг ООО;ООО;0001000910;009101001;агро
|
||||
Заревный Торг ООО;ООО;0001000927;009201001;торговля
|
||||
Заревный Тягач ООО;ООО;0001000934;009301001;логистика
|
||||
Заревная Кузница ООО;ООО;0001000941;009401001;производство
|
||||
Заревный Партнёр АО;АО;0001000959;009501001;услуги
|
||||
Заревная Стройка ООО;ООО;0001000966;009601001;строительство
|
||||
Лазурный Сад ООО;ООО;0001000973;009701001;агро
|
||||
Лазурный Магазин ООО;ООО;0001000980;009801001;торговля
|
||||
Лазурный Рейс ООО;ООО;0001000998;009901001;логистика
|
||||
Лазурная Фабрика АО;АО;0001001007;000001001;производство
|
||||
Лазурный Совет ООО;ООО;0001001014;000101001;услуги
|
||||
Лазурный Бастион ПАО;ПАО;0001001021;000201001;строительство
|
||||
Рубиновый Сад ООО;ООО;0001001039;000301001;агро
|
||||
Рубиновый Магазин ООО;ООО;0001001046;000401001;торговля
|
||||
Рубиновый Рейс АО;АО;0001001053;000501001;логистика
|
||||
Рубиновая Фабрика ООО;ООО;0001001060;000601001;производство
|
||||
Рубиновый Совет ООО;ООО;0001001078;000701001;услуги
|
||||
Рубиновый Бастион ООО;ООО;0001001085;000801001;строительство
|
||||
Лунный Сад ООО;ООО;0001001092;000901001;агро
|
||||
Лунный Магазин АО;АО;0001001102;001001001;торговля
|
||||
Лунный Рейс ООО;ООО;0001001110;001101001;логистика
|
||||
Лунная Фабрика ООО;ООО;0001001127;001201001;производство
|
||||
Лунный Совет ООО;ООО;0001001134;001301001;услуги
|
||||
Лунный Бастион ООО;ООО;0001001141;001401001;строительство
|
||||
Янтарная Борозда АО;АО;0001001159;001501001;агро
|
||||
Янтарный Прилавок ООО;ООО;0001001166;001601001;торговля
|
||||
Янтарный Перевал ООО;ООО;0001001173;001701001;логистика
|
||||
Янтарный Горн ООО;ООО;0001001180;001801001;производство
|
||||
Янтарная Контора ПАО;ПАО;0001001198;001901001;услуги
|
||||
Янтарный Зодчий АО;АО;0001001208;002001001;строительство
|
||||
Серебряная Борозда ООО;ООО;0001001215;002101001;агро
|
||||
Серебряный Прилавок ООО;ООО;0001001222;002201001;торговля
|
||||
Серебряный Перевал ООО;ООО;0001001230;002301001;логистика
|
||||
Серебряный Горн ООО;ООО;0001001247;002401001;производство
|
||||
Серебряная Контора АО;АО;0001001254;002501001;услуги
|
||||
Серебряный Зодчий ООО;ООО;0001001261;002601001;строительство
|
||||
Северная Борозда ООО;ООО;0001001279;002701001;агро
|
||||
Северный Прилавок ООО;ООО;0001001286;002801001;торговля
|
||||
Северный Перевал ООО;ООО;0001001293;002901001;логистика
|
||||
Северный Горн АО;АО;0001001303;003001001;производство
|
||||
Северная Контора ООО;ООО;0001001310;003101001;услуги
|
||||
Северный Зодчий ООО;ООО;0001001328;003201001;строительство
|
||||
Изумрудное Поле ООО;ООО;0001001335;003301001;агро
|
||||
Изумрудный Пассаж ООО;ООО;0001001342;003401001;торговля
|
||||
Изумрудный Караван АО;АО;0001001350;003501001;логистика
|
||||
Изумрудная Верфь ПАО;ПАО;0001001367;003601001;производство
|
||||
Изумрудная Мастерская ООО;ООО;0001001374;003701001;услуги
|
||||
Изумрудная Арка ООО;ООО;0001001381;003801001;строительство
|
||||
Заревное Поле ООО;ООО;0001001399;003901001;агро
|
||||
Заревный Пассаж АО;АО;0001001409;004001001;торговля
|
||||
Заревный Караван ООО;ООО;0001001416;004101001;логистика
|
||||
Заревная Верфь ООО;ООО;0001001423;004201001;производство
|
||||
Заревная Мастерская ООО;ООО;0001001430;004301001;услуги
|
||||
Заревная Арка ООО;ООО;0001001448;004401001;строительство
|
||||
Лазурный Амбар АО;АО;0001001455;004501001;агро
|
||||
Лазурный Товар ООО;ООО;0001001462;004601001;торговля
|
||||
Лазурная Магистраль ООО;ООО;0001001470;004701001;логистика
|
||||
Лазурная Мануфактура ООО;ООО;0001001487;004801001;производство
|
||||
Лазурный Приём ООО;ООО;0001001494;004901001;услуги
|
||||
Лазурный Мост АО;АО;0001001504;005001001;строительство
|
||||
Рубиновый Амбар ООО;ООО;0001001511;005101001;агро
|
||||
Рубиновый Товар ООО;ООО;0001001529;005201001;торговля
|
||||
Рубиновая Магистраль ПАО;ПАО;0001001536;005301001;логистика
|
||||
Рубиновая Мануфактура ООО;ООО;0001001543;005401001;производство
|
||||
Рубиновый Приём АО;АО;0001001550;005501001;услуги
|
||||
Рубиновый Мост ООО;ООО;0001001568;005601001;строительство
|
||||
Лунный Амбар ООО;ООО;0001001575;005701001;агро
|
||||
Лунный Товар ООО;ООО;0001001582;005801001;торговля
|
||||
Лунная Магистраль ООО;ООО;0001001590;005901001;логистика
|
||||
Лунная Мануфактура АО;АО;0001001600;006001001;производство
|
||||
Лунный Приём ООО;ООО;0001001617;006101001;услуги
|
||||
Лунный Мост ООО;ООО;0001001624;006201001;строительство
|
||||
Янтарная Нива ООО;ООО;0001001631;006301001;агро
|
||||
Янтарный Рынок ООО;ООО;0001001649;006401001;торговля
|
||||
Янтарный Путь АО;АО;0001001656;006501001;логистика
|
||||
Янтарный Завод ООО;ООО;0001001663;006601001;производство
|
||||
Янтарный Сервис ООО;ООО;0001001670;006701001;услуги
|
||||
Янтарный Фундамент ООО;ООО;0001001688;006801001;строительство
|
||||
Серебряная Нива ООО;ООО;0001001695;006901001;агро
|
||||
Серебряный Рынок ПАО;ПАО;0001001705;007001001;торговля
|
||||
Серебряный Путь ООО;ООО;0001001712;007101001;логистика
|
||||
Серебряный Завод ООО;ООО;0001001720;007201001;производство
|
||||
Серебряный Сервис ООО;ООО;0001001737;007301001;услуги
|
||||
Серебряный Фундамент ООО;ООО;0001001744;007401001;строительство
|
||||
Северная Нива АО;АО;0001001751;007501001;агро
|
||||
Северный Рынок ООО;ООО;0001001769;007601001;торговля
|
||||
Северный Путь ООО;ООО;0001001776;007701001;логистика
|
||||
Северный Завод ООО;ООО;0001001783;007801001;производство
|
||||
Северный Сервис ООО;ООО;0001001790;007901001;услуги
|
||||
Северный Фундамент АО;АО;0001001800;008001001;строительство
|
||||
Изумрудный Колос ООО;ООО;0001001818;008101001;агро
|
||||
Изумрудный Склад ООО;ООО;0001001825;008201001;торговля
|
||||
Изумрудный Маршрут ООО;ООО;0001001832;008301001;логистика
|
||||
Изумрудный Цех ООО;ООО;0001001840;008401001;производство
|
||||
Изумрудное Бюро АО;АО;0001001857;008501001;услуги
|
||||
Изумрудный Кирпич ООО;ООО;0001001864;008601001;строительство
|
||||
Заревный Колос ПАО;ПАО;0001001871;008701001;агро
|
||||
Заревный Склад ООО;ООО;0001001889;008801001;торговля
|
||||
Заревный Маршрут ООО;ООО;0001001896;008901001;логистика
|
||||
Заревный Цех АО;АО;0001001906;009001001;производство
|
||||
Заревное Бюро ООО;ООО;0001001913;009101001;услуги
|
||||
Заревный Кирпич ООО;ООО;0001001920;009201001;строительство
|
||||
Лазурный Урожай ООО;ООО;0001001938;009301001;агро
|
||||
Лазурная Лавка ООО;ООО;0001001945;009401001;торговля
|
||||
Лазурный Фургон АО;АО;0001001952;009501001;логистика
|
||||
Лазурный Станок ООО;ООО;0001001960;009601001;производство
|
||||
Лазурный Центр ООО;ООО;0001001977;009701001;услуги
|
||||
Лазурный Монолит ООО;ООО;0001001984;009801001;строительство
|
||||
Рубиновый Урожай ООО;ООО;0001001991;009901001;агро
|
||||
Рубиновая Лавка АО;АО;0001002000;000001001;торговля
|
||||
Рубиновый Фургон ООО;ООО;0001002018;000101001;логистика
|
||||
Рубиновый Станок ООО;ООО;0001002025;000201001;производство
|
||||
Рубиновый Центр ООО;ООО;0001002032;000301001;услуги
|
||||
Рубиновый Монолит ПАО;ПАО;0001002040;000401001;строительство
|
||||
Лунный Урожай АО;АО;0001002057;000501001;агро
|
||||
Лунная Лавка ООО;ООО;0001002064;000601001;торговля
|
||||
Лунный Фургон ООО;ООО;0001002071;000701001;логистика
|
||||
Лунный Станок ООО;ООО;0001002089;000801001;производство
|
||||
Лунный Центр ООО;ООО;0001002096;000901001;услуги
|
||||
Лунный Монолит АО;АО;0001002106;001001001;строительство
|
||||
Янтарная Пашня ООО;ООО;0001002113;001101001;агро
|
||||
Янтарная Ярмарка ООО;ООО;0001002120;001201001;торговля
|
||||
Янтарная Трасса ООО;ООО;0001002138;001301001;логистика
|
||||
Янтарная Мельница ООО;ООО;0001002145;001401001;производство
|
||||
Янтарный Консалт АО;АО;0001002152;001501001;услуги
|
||||
Янтарный Каркас ООО;ООО;0001002160;001601001;строительство
|
||||
Серебряная Пашня ООО;ООО;0001002177;001701001;агро
|
||||
Серебряная Ярмарка ООО;ООО;0001002184;001801001;торговля
|
||||
Серебряная Трасса ООО;ООО;0001002191;001901001;логистика
|
||||
Серебряная Мельница АО;АО;0001002201;002001001;производство
|
||||
Серебряный Консалт ПАО;ПАО;0001002219;002101001;услуги
|
||||
Серебряный Каркас ООО;ООО;0001002226;002201001;строительство
|
||||
Северная Пашня ООО;ООО;0001002233;002301001;агро
|
||||
Северная Ярмарка ООО;ООО;0001002240;002401001;торговля
|
||||
Северная Трасса АО;АО;0001002258;002501001;логистика
|
||||
Северная Мельница ООО;ООО;0001002265;002601001;производство
|
||||
Северный Консалт ООО;ООО;0001002272;002701001;услуги
|
||||
Северный Каркас ООО;ООО;0001002280;002801001;строительство
|
||||
Изумрудное Зерно ООО;ООО;0001002297;002901001;агро
|
||||
Изумрудный Базар АО;АО;0001002307;003001001;торговля
|
||||
Изумрудный Обоз ООО;ООО;0001002314;003101001;логистика
|
||||
Изумрудная Пекарня ООО;ООО;0001002321;003201001;производство
|
||||
Изумрудный Профи ООО;ООО;0001002339;003301001;услуги
|
||||
Изумрудный Проект ООО;ООО;0001002346;003401001;строительство
|
||||
Заревное Зерно АО;АО;0001002353;003501001;агро
|
||||
Заревный Базар ООО;ООО;0001002360;003601001;торговля
|
||||
Заревный Обоз ООО;ООО;0001002378;003701001;логистика
|
||||
Заревная Пекарня ПАО;ПАО;0001002385;003801001;производство
|
||||
Заревный Профи ООО;ООО;0001002392;003901001;услуги
|
||||
Заревный Проект АО;АО;0001002402;004001001;строительство
|
||||
Лазурный Луг ООО;ООО;0001002410;004101001;агро
|
||||
Лазурный Торг ООО;ООО;0001002427;004201001;торговля
|
||||
Лазурный Тягач ООО;ООО;0001002434;004301001;логистика
|
||||
Лазурная Кузница ООО;ООО;0001002441;004401001;производство
|
||||
Лазурный Партнёр АО;АО;0001002459;004501001;услуги
|
||||
Лазурная Стройка ООО;ООО;0001002466;004601001;строительство
|
||||
Рубиновый Луг ООО;ООО;0001002473;004701001;агро
|
||||
Рубиновый Торг ООО;ООО;0001002480;004801001;торговля
|
||||
Рубиновый Тягач ООО;ООО;0001002498;004901001;логистика
|
||||
Рубиновая Кузница АО;АО;0001002508;005001001;производство
|
||||
Рубиновый Партнёр ООО;ООО;0001002515;005101001;услуги
|
||||
Рубиновая Стройка ООО;ООО;0001002522;005201001;строительство
|
||||
Лунный Луг ООО;ООО;0001002530;005301001;агро
|
||||
Лунный Торг ООО;ООО;0001002547;005401001;торговля
|
||||
Лунный Тягач ПАО;ПАО;0001002554;005501001;логистика
|
||||
Лунная Кузница ООО;ООО;0001002561;005601001;производство
|
||||
Лунный Партнёр ООО;ООО;0001002579;005701001;услуги
|
||||
Лунная Стройка ООО;ООО;0001002586;005801001;строительство
|
||||
Янтарный Сад ООО;ООО;0001002593;005901001;агро
|
||||
Янтарный Магазин АО;АО;0001002603;006001001;торговля
|
||||
Янтарный Рейс ООО;ООО;0001002610;006101001;логистика
|
||||
Янтарная Фабрика ООО;ООО;0001002628;006201001;производство
|
||||
Янтарный Совет ООО;ООО;0001002635;006301001;услуги
|
||||
Янтарный Бастион ООО;ООО;0001002642;006401001;строительство
|
||||
Серебряный Сад АО;АО;0001002650;006501001;агро
|
||||
Серебряный Магазин ООО;ООО;0001002667;006601001;торговля
|
||||
Серебряный Рейс ООО;ООО;0001002674;006701001;логистика
|
||||
Серебряная Фабрика ООО;ООО;0001002681;006801001;производство
|
||||
Серебряный Совет ООО;ООО;0001002699;006901001;услуги
|
||||
Серебряный Бастион АО;АО;0001002709;007001001;строительство
|
||||
Северный Сад ООО;ООО;0001002716;007101001;агро
|
||||
Северный Магазин ПАО;ПАО;0001002723;007201001;торговля
|
||||
Северный Рейс ООО;ООО;0001002730;007301001;логистика
|
||||
Северная Фабрика ООО;ООО;0001002748;007401001;производство
|
||||
Северный Совет АО;АО;0001002755;007501001;услуги
|
||||
Северный Бастион ООО;ООО;0001002762;007601001;строительство
|
||||
Изумрудная Борозда ООО;ООО;0001002770;007701001;агро
|
||||
Изумрудный Прилавок ООО;ООО;0001002787;007801001;торговля
|
||||
Изумрудный Перевал ООО;ООО;0001002794;007901001;логистика
|
||||
Изумрудный Горн АО;АО;0001002804;008001001;производство
|
||||
Изумрудная Контора ООО;ООО;0001002811;008101001;услуги
|
||||
Изумрудный Зодчий ООО;ООО;0001002829;008201001;строительство
|
||||
Заревная Борозда ООО;ООО;0001002836;008301001;агро
|
||||
Заревный Прилавок ООО;ООО;0001002843;008401001;торговля
|
||||
Заревный Перевал АО;АО;0001002850;008501001;логистика
|
||||
Заревный Горн ООО;ООО;0001002868;008601001;производство
|
||||
Заревная Контора ООО;ООО;0001002875;008701001;услуги
|
||||
Заревный Зодчий ООО;ООО;0001002882;008801001;строительство
|
||||
Лазурное Поле ПАО;ПАО;0001002890;008901001;агро
|
||||
Лазурный Пассаж АО;АО;0001002900;009001001;торговля
|
||||
Лазурный Караван ООО;ООО;0001002917;009101001;логистика
|
||||
Лазурная Верфь ООО;ООО;0001002924;009201001;производство
|
||||
Лазурная Мастерская ООО;ООО;0001002931;009301001;услуги
|
||||
Лазурная Арка ООО;ООО;0001002949;009401001;строительство
|
||||
Рубиновое Поле АО;АО;0001002956;009501001;агро
|
||||
Рубиновый Пассаж ООО;ООО;0001002963;009601001;торговля
|
||||
Рубиновый Караван ООО;ООО;0001002970;009701001;логистика
|
||||
Рубиновая Верфь ООО;ООО;0001002988;009801001;производство
|
||||
Рубиновая Мастерская ООО;ООО;0001002995;009901001;услуги
|
||||
Рубиновая Арка АО;АО;0001003004;000001001;строительство
|
||||
Лунное Поле ООО;ООО;0001003011;000101001;агро
|
||||
Лунный Пассаж ООО;ООО;0001003029;000201001;торговля
|
||||
Лунный Караван ООО;ООО;0001003036;000301001;логистика
|
||||
Лунная Верфь ООО;ООО;0001003043;000401001;производство
|
||||
Лунная Мастерская АО;АО;0001003050;000501001;услуги
|
||||
Лунная Арка ПАО;ПАО;0001003068;000601001;строительство
|
||||
Янтарный Амбар ООО;ООО;0001003075;000701001;агро
|
||||
Янтарный Товар ООО;ООО;0001003082;000801001;торговля
|
||||
Янтарная Магистраль ООО;ООО;0001003090;000901001;логистика
|
||||
Янтарная Мануфактура АО;АО;0001003100;001001001;производство
|
||||
Янтарный Приём ООО;ООО;0001003117;001101001;услуги
|
||||
Янтарный Мост ООО;ООО;0001003124;001201001;строительство
|
||||
Серебряный Амбар ООО;ООО;0001003131;001301001;агро
|
||||
Серебряный Товар ООО;ООО;0001003149;001401001;торговля
|
||||
Серебряная Магистраль АО;АО;0001003156;001501001;логистика
|
||||
Серебряная Мануфактура ООО;ООО;0001003163;001601001;производство
|
||||
Серебряный Приём ООО;ООО;0001003170;001701001;услуги
|
||||
Серебряный Мост ООО;ООО;0001003188;001801001;строительство
|
||||
Северный Амбар ООО;ООО;0001003195;001901001;агро
|
||||
Северный Товар АО;АО;0001003205;002001001;торговля
|
||||
Северная Магистраль ООО;ООО;0001003212;002101001;логистика
|
||||
Северная Мануфактура ООО;ООО;0001003220;002201001;производство
|
||||
Северный Приём ПАО;ПАО;0001003237;002301001;услуги
|
||||
Северный Мост ООО;ООО;0001003244;002401001;строительство
|
||||
Изумрудная Нива АО;АО;0001003251;002501001;агро
|
||||
Изумрудный Рынок ООО;ООО;0001003269;002601001;торговля
|
||||
Изумрудный Путь ООО;ООО;0001003276;002701001;логистика
|
||||
Изумрудный Завод ООО;ООО;0001003283;002801001;производство
|
||||
Изумрудный Сервис ООО;ООО;0001003290;002901001;услуги
|
||||
Изумрудный Фундамент АО;АО;0001003300;003001001;строительство
|
||||
Авдонина Е.И. ИП;ИП;000010033189;;услуги
|
||||
Бражников А.П. ИП;ИП;000010033252;;торговля
|
||||
Ветрова Н.С. ИП;ИП;000010033326;;логистика
|
||||
Голубев В.Д. ИП;ИП;000010033407;;агро
|
||||
Дроздова О.А. ИП;ИП;000010033573;;строительство
|
||||
Ермаков М.В. ИП;ИП;000010033647;;производство
|
||||
Жарова И.Н. ИП;ИП;000010033710;;услуги
|
||||
Зуев С.К. ИП;ИП;000010033894;;торговля
|
||||
Игнатова Т.Р. ИП;ИП;000010033968;;логистика
|
||||
Копылов Д.Л. ИП;ИП;000010034062;;агро
|
||||
Лаврова Е.И. ИП;ИП;000010034136;;строительство
|
||||
Мартынов А.П. ИП;ИП;000010034200;;производство
|
||||
Норина Н.С. ИП;ИП;000010034383;;услуги
|
||||
Овчаров В.Д. ИП;ИП;000010034457;;торговля
|
||||
Пименова О.А. ИП;ИП;000010034520;;логистика
|
||||
Рябов М.В. ИП;ИП;000010034601;;агро
|
||||
Сорокина И.Н. ИП;ИП;000010034778;;строительство
|
||||
Тарасов С.К. ИП;ИП;000010034841;;производство
|
||||
Устинова Т.Р. ИП;ИП;000010034915;;услуги
|
||||
Фролов Д.Л. ИП;ИП;000010035010;;торговля
|
||||
Хромова Е.И. ИП;ИП;000010035193;;логистика
|
||||
Цветков А.П. ИП;ИП;000010035267;;агро
|
||||
Чернова Н.С. ИП;ИП;000010035330;;строительство
|
||||
Шаров В.Д. ИП;ИП;000010035404;;производство
|
||||
Щукина О.А. ИП;ИП;000010035588;;услуги
|
||||
Юдин М.В. ИП;ИП;000010035651;;торговля
|
||||
Яковлева И.Н. ИП;ИП;000010035725;;логистика
|
||||
Баранов С.К. ИП;ИП;000010035806;;агро
|
||||
Волкова Т.Р. ИП;ИП;000010035972;;строительство
|
||||
Гусев Д.Л. ИП;ИП;000010036077;;производство
|
||||
Денисова Е.И. ИП;ИП;000010036140;;услуги
|
||||
Елисеев А.П. ИП;ИП;000010036214;;торговля
|
||||
Зайцева Н.С. ИП;ИП;000010036398;;логистика
|
||||
Исаев В.Д. ИП;ИП;000010036461;;агро
|
||||
Калинина О.А. ИП;ИП;000010036535;;строительство
|
||||
Леонов М.В. ИП;ИП;000010036609;;производство
|
||||
Маслова И.Н. ИП;ИП;000010036782;;услуги
|
||||
Назаров С.К. ИП;ИП;000010036856;;торговля
|
||||
Орлова Т.Р. ИП;ИП;000010036920;;логистика
|
||||
Панов Д.Л. ИП;ИП;000010037024;;агро
|
||||
Лазурный Банк АО;АО;0001003710;007101001;банк
|
||||
Янтарный Банк ПАО;ПАО;0001003727;007201001;банк
|
||||
Изумрудный Банк АО;АО;0001003734;007301001;банк
|
||||
Рубиновый Банк ПАО;ПАО;0001003741;007401001;банк
|
||||
Серебряный Банк АО;АО;0001003759;007501001;банк
|
||||
Заревный Банк ПАО;ПАО;0001003766;007601001;банк
|
||||
Лунный Банк АО;АО;0001003773;007701001;банк
|
||||
Северный Банк ПАО;ПАО;0001003780;007801001;банк
|
||||
Лазурный Кредитный Банк АО;АО;0001003798;007901001;банк
|
||||
Янтарный Кредитный Банк ПАО;ПАО;0001003808;008001001;банк
|
||||
Изумрудный Кредитный Банк АО;АО;0001003815;008101001;банк
|
||||
Рубиновый Кредитный Банк ПАО;ПАО;0001003822;008201001;банк
|
||||
Серебряный Кредитный Банк АО;АО;0001003830;008301001;банк
|
||||
Заревный Кредитный Банк ПАО;ПАО;0001003847;008401001;банк
|
||||
Лунный Кредитный Банк АО;АО;0001003854;008501001;банк
|
||||
Северный Кредитный Банк ПАО;ПАО;0001003861;008601001;банк
|
||||
Лазурный Деловой Банк АО;АО;0001003879;008701001;банк
|
||||
Янтарный Деловой Банк ПАО;ПАО;0001003886;008801001;банк
|
||||
Изумрудный Деловой Банк АО;АО;0001003893;008901001;банк
|
||||
Рубиновый Деловой Банк ПАО;ПАО;0001003903;009001001;банк
|
||||
Серебряный Деловой Банк АО;АО;0001003910;009101001;банк
|
||||
Заревный Деловой Банк ПАО;ПАО;0001003928;009201001;банк
|
||||
Лунный Деловой Банк АО;АО;0001003935;009301001;банк
|
||||
Северный Деловой Банк ПАО;ПАО;0001003942;009401001;банк
|
||||
Лазурный Земельный Банк АО;АО;0001003950;009501001;банк
|
||||
Янтарный Земельный Банк ПАО;ПАО;0001003967;009601001;банк
|
||||
Изумрудный Земельный Банк АО;АО;0001003974;009701001;банк
|
||||
Рубиновый Земельный Банк ПАО;ПАО;0001003981;009801001;банк
|
||||
Серебряный Земельный Банк АО;АО;0001003999;009901001;банк
|
||||
Заревный Земельный Банк ПАО;ПАО;0001004008;000001001;банк
|
||||
|
29
synthgen/ecp.py
Normal file
29
synthgen/ecp.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""Источник: synthetic-group/gen/ecp.py (#35). Выгрузка ЕЦП (спека §6.3): состав ГК, все компании — «Входит в ГСК».
|
||||
|
||||
Формат сверен с импортом ядра (`arm-core/api/src/domain/directory_import.py`): обязательные колонки ИНН,
|
||||
iD ГК, Группа компаний, Организация, Тип; необязательные Базовый РФ ГК в CRM, ГСК, ГСЗ, КПП.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import openpyxl
|
||||
|
||||
from synthgen.group import Group
|
||||
from synthgen.xlsx import save_deterministic
|
||||
|
||||
HEADER = ["ИНН", "КПП", "iD ГК", "Базовый РФ ГК в CRM", "Группа компаний", "Организация", "ГСК", "ГСЗ", "Тип"]
|
||||
BASE_RF = "Ростовский региональный филиал" # как в #35
|
||||
|
||||
|
||||
def write_ecp(group: Group, path: Path) -> None:
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
assert ws is not None
|
||||
ws.title = "Выписки"
|
||||
ws.append(HEADER)
|
||||
for m in group.members:
|
||||
ws.append([m.inn, m.kpp, group.gk_id, BASE_RF, group.name, m.name, "Входит в ГСК", None, "ЮЛ"])
|
||||
for row in ws.iter_rows(min_row=2, max_col=2):
|
||||
for cell in row:
|
||||
cell.number_format = "@" # ИНН и КПП — текстом
|
||||
save_deterministic(wb, path)
|
||||
118
synthgen/group.py
Normal file
118
synthgen/group.py
Normal file
@ -0,0 +1,118 @@
|
||||
"""Состав группы компаний (спека §4.2): члены из словаря, ИНН по префиксу, доли, компании без ИНН в ОСВ."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from decimal import Decimal
|
||||
|
||||
from synthgen import inn
|
||||
from synthgen.config import Config
|
||||
from synthgen.dictionary import Org
|
||||
from synthgen.rng import rng_for, stable_hash
|
||||
|
||||
_HASH_RANGE = 90000
|
||||
_CENT = Decimal("0.01")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Member:
|
||||
code: str # C01, C02, …
|
||||
org: Org # запись словаря, давшая корень имени
|
||||
name: str # АО "<prefix> <корень>"
|
||||
name_1c: str # <prefix> <корень> АО — как 1С пишет контрагента
|
||||
inn: str
|
||||
kpp: str
|
||||
is_head: bool
|
||||
head_share: Decimal | None # доля головной в дочерней; у головной None
|
||||
minority: Org | None # миноритарий дочерней (внешний держатель остальной доли)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Group:
|
||||
prefix: str
|
||||
name: str # ГК <prefix>
|
||||
gk_id: str # <prefix>-000001
|
||||
members: list[Member]
|
||||
no_inn: frozenset[str] # коды компаний, чья ОСВ записана без ИНН
|
||||
variant_name: dict[str, str] # код ОСВ-компании без ИНН → код члена группы, записанного другим написанием
|
||||
externals: list[Org] # словарь без банков и без выбранных в группу
|
||||
banks: list[Org]
|
||||
|
||||
|
||||
def member_inn(prefix: str, i: int) -> str:
|
||||
"""ИНН i-й компании группы (i от 1, < 100): номер = 1000000 + 100 × h(prefix) + i."""
|
||||
return inn.make_inn10(1000000 + 100 * stable_hash(prefix, _HASH_RANGE) + i)
|
||||
|
||||
|
||||
def member_kpp(i: int) -> str:
|
||||
return f"00{i:02d}01001"
|
||||
|
||||
|
||||
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}"
|
||||
|
||||
|
||||
def build_group(cfg: Config, orgs: list[Org]) -> Group:
|
||||
rng = rng_for(cfg.seed, "group", cfg.prefix)
|
||||
candidates = [o for o in orgs if o.form in ("ООО", "АО") and o.kind != "банк"]
|
||||
heads = [o for o in candidates if o.form == "АО"]
|
||||
head_org = rng.choice(heads)
|
||||
rest = [o for o in candidates if o is not head_org]
|
||||
chosen = [head_org, *rng.sample(rest, cfg.group_size - 1)]
|
||||
|
||||
children = list(range(1, cfg.group_size))
|
||||
with_minority = set(rng.sample(children, min(len(children), rng.randint(1, 2)))) if children else set()
|
||||
chosen_set = {o.inn for o in chosen}
|
||||
pool = [o for o in orgs if o.kind != "банк" and o.form != "ИП" and o.inn not in chosen_set]
|
||||
|
||||
members: list[Member] = []
|
||||
for idx, org in enumerate(chosen):
|
||||
i = idx + 1
|
||||
share: Decimal | None = None
|
||||
minority: Org | None = None
|
||||
if idx > 0:
|
||||
if idx in with_minority:
|
||||
share = (Decimal(rng.randint(51, 99)) / 100).quantize(_CENT)
|
||||
minority = rng.choice(pool)
|
||||
else:
|
||||
share = Decimal("1.00")
|
||||
members.append(
|
||||
Member(
|
||||
code=f"C{i:02d}",
|
||||
org=org,
|
||||
name=f'{org.form} "{cfg.prefix} {org.root}"',
|
||||
name_1c=f"{cfg.prefix} {org.root} {org.form}",
|
||||
inn=member_inn(cfg.prefix, i),
|
||||
kpp=member_kpp(i),
|
||||
is_head=idx == 0,
|
||||
head_share=share,
|
||||
minority=minority,
|
||||
)
|
||||
)
|
||||
|
||||
codes = [m.code for m in members]
|
||||
child_codes = codes[1:]
|
||||
n = cfg.no_inn_companies
|
||||
picked = rng.sample(child_codes, min(n, len(child_codes)))
|
||||
if n > len(child_codes):
|
||||
picked.append(codes[0])
|
||||
no_inn = frozenset(picked)
|
||||
|
||||
variant_name: dict[str, str] = {}
|
||||
for code in sorted(no_inn):
|
||||
others = [c for c in codes if c != code]
|
||||
variant_name[code] = rng.choice(others)
|
||||
|
||||
externals = [o for o in orgs if o.kind != "банк" and o.inn not in chosen_set]
|
||||
banks = [o for o in orgs if o.kind == "банк"]
|
||||
return Group(
|
||||
prefix=cfg.prefix,
|
||||
name=f"ГК {cfg.prefix}",
|
||||
gk_id=f"{cfg.prefix}-000001",
|
||||
members=members,
|
||||
no_inn=no_inn,
|
||||
variant_name=variant_name,
|
||||
externals=externals,
|
||||
banks=banks,
|
||||
)
|
||||
72
synthgen/tests/test_dictionary.py
Normal file
72
synthgen/tests/test_dictionary.py
Normal file
@ -0,0 +1,72 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import openpyxl
|
||||
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")
|
||||
_FORMS = re.compile(r"\b(ООО|АО|ПАО|ЗАО|ОАО|ИП)\b", re.IGNORECASE)
|
||||
|
||||
|
||||
def _norm(text: str) -> str:
|
||||
text = _FORMS.sub(" ", text)
|
||||
return re.sub(r"[^0-9a-zа-яё]+", " ", text.lower().replace("ё", "е")).strip()
|
||||
|
||||
|
||||
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 _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]
|
||||
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()
|
||||
24
synthgen/tests/test_ecp.py
Normal file
24
synthgen/tests/test_ecp.py
Normal file
@ -0,0 +1,24 @@
|
||||
import openpyxl
|
||||
|
||||
from synthgen import dictionary
|
||||
from synthgen.config import Config
|
||||
from synthgen.ecp import write_ecp
|
||||
from synthgen.group import build_group
|
||||
|
||||
|
||||
def test_ecp_format(tmp_path):
|
||||
g = build_group(Config(group_size=6, prefix="DEMO2"), dictionary.load())
|
||||
path = tmp_path / "ecp-DEMO2.xlsx"
|
||||
write_ecp(g, path)
|
||||
wb = openpyxl.load_workbook(path)
|
||||
assert wb.sheetnames == ["Выписки"]
|
||||
ws = wb["Выписки"]
|
||||
rows = list(ws.iter_rows(values_only=True))
|
||||
assert list(rows[0]) == ["ИНН", "КПП", "iD ГК", "Базовый РФ ГК в CRM", "Группа компаний", "Организация", "ГСК", "ГСЗ", "Тип"]
|
||||
assert len(rows) - 1 == 6
|
||||
for row, member in zip(rows[1:], g.members):
|
||||
assert row[0] == member.inn and row[1] == member.kpp and row[5] == member.name
|
||||
assert row[2] == "DEMO2-000001" and row[4] == "ГК DEMO2"
|
||||
assert row[6] == "Входит в ГСК" and row[8] == "ЮЛ"
|
||||
for r in ws.iter_rows(min_row=2, max_col=2):
|
||||
assert all(c.number_format == "@" and isinstance(c.value, str) for c in r)
|
||||
105
synthgen/tests/test_group.py
Normal file
105
synthgen/tests/test_group.py
Normal file
@ -0,0 +1,105 @@
|
||||
from dataclasses import replace
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from synthgen import dictionary, inn
|
||||
from synthgen.config import Config
|
||||
from synthgen.group import build_group, member_inn, variant_spelling
|
||||
from synthgen.ecp import write_ecp
|
||||
from synthgen.rng import stable_hash
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def orgs():
|
||||
return dictionary.load()
|
||||
|
||||
|
||||
def test_group_size_and_head(orgs):
|
||||
g = build_group(Config(group_size=5), orgs)
|
||||
assert len(g.members) == 5
|
||||
assert [m.code for m in g.members] == ["C01", "C02", "C03", "C04", "C05"]
|
||||
assert g.members[0].is_head and g.members[0].org.form == "АО" and g.members[0].head_share is None
|
||||
for m in g.members:
|
||||
assert m.org.form in ("ООО", "АО") and m.org.kind != "банк"
|
||||
assert not m.is_head or m is g.members[0]
|
||||
assert g.name == "ГК SYN" and g.gk_id == "SYN-000001"
|
||||
for m in g.members:
|
||||
assert m.name == f'{m.org.form} "SYN {m.org.root}"'
|
||||
assert m.name_1c == f"SYN {m.org.root} {m.org.form}"
|
||||
assert len(m.kpp) == 9 and inn.is_valid(m.inn)
|
||||
|
||||
|
||||
def test_member_inn_range():
|
||||
value = member_inn("SYN", 3)
|
||||
assert inn.is_valid(value)
|
||||
assert int(value[2:-1]) == 1000000 + 100 * stable_hash("SYN", 90000) + 3
|
||||
|
||||
|
||||
def test_prefixes_do_not_collide(orgs):
|
||||
a = {m.inn for m in build_group(Config(prefix="SYN"), orgs).members}
|
||||
b = {m.inn for m in build_group(Config(prefix="DEMO2"), orgs).members}
|
||||
assert not a & b
|
||||
assert not (a | b) & {o.inn for o in orgs}
|
||||
|
||||
|
||||
def test_externals_exclude_group_and_banks(orgs):
|
||||
g = build_group(Config(group_size=8), orgs)
|
||||
chosen = {m.org.inn for m in g.members}
|
||||
assert all(o.kind != "банк" for o in g.externals)
|
||||
assert not chosen & {o.inn for o in g.externals}
|
||||
assert len(g.externals) == sum(o.kind != "банк" for o in orgs) - 8
|
||||
assert len(g.banks) == 30 and all(o.kind == "банк" for o in g.banks)
|
||||
|
||||
|
||||
def test_no_inn_selection(orgs):
|
||||
g = build_group(Config(group_size=5, no_inn_companies=2), orgs)
|
||||
assert len(g.no_inn) == 2 and "C01" not in g.no_inn
|
||||
g = build_group(Config(group_size=5, no_inn_companies=4), orgs)
|
||||
assert len(g.no_inn) == 4 and "C01" not in g.no_inn
|
||||
g = build_group(Config(group_size=5, no_inn_companies=5), orgs)
|
||||
assert g.no_inn == frozenset(m.code for m in g.members)
|
||||
assert build_group(Config(no_inn_companies=0), orgs).no_inn == frozenset()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("size,n", [(2, 2), (5, 2), (5, 5), (10, 3)])
|
||||
def test_variant_name(orgs, size, n):
|
||||
g = build_group(Config(group_size=size, no_inn_companies=n), orgs)
|
||||
by_code = {m.code: m for m in g.members}
|
||||
assert set(g.variant_name) == set(g.no_inn)
|
||||
for code, other in g.variant_name.items():
|
||||
assert other != code and other in by_code
|
||||
m = by_code[other]
|
||||
variant = variant_spelling(m)
|
||||
assert variant not in (m.name_1c, m.name)
|
||||
assert variant != by_code[code].name_1c
|
||||
|
||||
|
||||
def test_shares(orgs):
|
||||
for size in (2, 5, 12):
|
||||
g = build_group(Config(group_size=size), orgs)
|
||||
children = g.members[1:]
|
||||
for m in children:
|
||||
assert Decimal("0.51") <= m.head_share <= Decimal("1.00")
|
||||
with_min = [m for m in children if m.minority is not None]
|
||||
assert len(with_min) <= 2
|
||||
if size > 2:
|
||||
assert 1 <= len(with_min)
|
||||
for m in with_min:
|
||||
assert m.head_share < 1
|
||||
for m in children:
|
||||
if m.minority is None:
|
||||
assert m.head_share == Decimal("1.00")
|
||||
|
||||
|
||||
def test_prefix_cyrillic_and_digits(orgs, tmp_path):
|
||||
for prefix in ("ДЕМО", "2GK"):
|
||||
g = build_group(Config(prefix=prefix), orgs)
|
||||
assert g.gk_id == f"{prefix}-000001"
|
||||
write_ecp(g, tmp_path / f"ecp-{prefix}.xlsx")
|
||||
|
||||
|
||||
def test_group_deterministic(orgs):
|
||||
cfg = Config(group_size=7, no_inn_companies=2, seed=5)
|
||||
assert build_group(cfg, orgs) == build_group(cfg, orgs)
|
||||
assert build_group(cfg, orgs) != build_group(replace(cfg, seed=6), orgs)
|
||||
Loading…
x
Reference in New Issue
Block a user