2026-11-12 23:41:42 +04:00

119 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Состав группы компаний (спека §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 # АО "<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)."""
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,
)