"""Состав группы компаний (спека §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, … prefix: str # префикс ГК org: Org # запись словаря, давшая корень имени name: str # АО " <корень>" name_1c: str # <корень> АО — как 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 # ГК gk_id: str # -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).""" return f"{member.org.root} {member.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}", prefix=cfg.prefix, 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, )