304 lines
53 KiB
Markdown
304 lines
53 KiB
Markdown
# Настраиваемый генератор синтетики ГСК — проект
|
||
|
||
Issue: RSHB/arm-ka-fixtures#38. Ветка `feat/38-synthgen`. Дата: 12.11.2026.
|
||
|
||
## 1. Зачем
|
||
|
||
Для ручного тестирования, демонстрации и проверки на объёме нужен набор первички группы компаний, который
|
||
**выглядит как выгрузки из 1С** и **внутренне согласован**: ОСВ по счетам, ББ и ОФР каждой компании построены из одних и
|
||
тех же проводок, поэтому сходятся между собой, а операции между компаниями группы дают пары ВГО и шахматку, на которые
|
||
есть что посмотреть. Набор должен генерироваться скриптом под нужный размер, а не собираться руками.
|
||
|
||
Реальные фикстуры (`osv/rb`, `osv/schastl`, `osv-dkb/`) — две-три компании, неполные периоды, реальные ИНН. Контрольный
|
||
набор #35 (`synthetic-group/`, 7 компаний, ручные сценарии, оракул) решает другую задачу — проверку алгоритмов на
|
||
известных случаях — и для объёма не годится: состав фиксирован, контрагентов единицы, оформление не похоже на 1С.
|
||
|
||
## 2. Цели, не-цели, анти-цели
|
||
|
||
**Цели**
|
||
- Один скрипт генерирует ОСВ, ББ, ОФР по всем компаниям и периодам и файл состава ГК для импорта ЕЦП.
|
||
- Семь настроек (§3) из конфигурационного файла с переопределением из командной строки.
|
||
- Файлы по виду — выгрузки 1С (§6).
|
||
- Данные согласованы (§5.6) и детерминированы: одинаковые настройки и `seed` → побайтно одинаковый набор.
|
||
|
||
**Не-цели (в этой задаче)**
|
||
- Оракул, ожидаемые результаты (`expected/`), ручные сценарии вида R-/P-/V-/G-/S- из #35.
|
||
- Сканы PDF и ответы ИС Сканирование.
|
||
- ББ/ОФР по ФСБУ 4/2023 и упрощённые формы — только 66н.
|
||
- Компании ГК вне ГСК, несколько ГК в одном прогоне, расшифровки, КФО клиента.
|
||
- Загрузка на стенд (для этого есть `load.py` в #35; приспособить его — отдельная задача).
|
||
|
||
**Анти-цели (провал, даже если «работает»)**
|
||
- Реальные ИНН или имена реальных организаций в наборе (в том числе остатки реквизитов из образцов 1С, §6.2).
|
||
- ОСВ, не сходящаяся с ББ/ОФР той же компании; ББ, где 1600 ≠ 1700.
|
||
- Набор, который парсер `tabular-parser` (ОСВ) или разбор ББ/ОФР в Excel не принимает из-за оформления генератора.
|
||
- Пересечение компаний двух прогонов с разными префиксами на одном стенде (имя или ИНН).
|
||
|
||
## 3. Настройки
|
||
|
||
Файл YAML (`--config`), любой ключ переопределяется `--set ключ=значение`. Неизвестный ключ или значение вне диапазона —
|
||
ошибка до начала генерации.
|
||
|
||
| # | Ключ | Смысл | По умолчанию | Диапазон |
|
||
|---|---|---|---|---|
|
||
| 1 | `group_size` | число компаний в ГК (все входят в ГСК) | 5 | 2–40 |
|
||
| 2 | `prefix` | префикс ГК: добавляется к названию группы и её компаний (§4.2), задаёт диапазон ИНН компаний группы | `SYN` | 2–12 символов, буквы/цифры, без пробелов |
|
||
| 3 | `periods` | число отчётных периодов | 4 | 1–12 |
|
||
| 4 | `start` | первый период, `ГГГГ-Qn` | `2025-Q1` | 2020-Q1 … 2030-Q4 |
|
||
| 5 | `osv_counterparties_mean` | среднее число **внешних** контрагентов в одной ОСВ (матожидание, §5.3) | 60 | 0–150 (ограничено словарём, решение 9) |
|
||
| 6 | `no_inn_companies` | сколько компаний группы выгружают ОСВ без ИНН контрагентов | 1 | 0–`group_size` |
|
||
| 7 | `vgo_mismatch_share` | доля пар ВГО с расхождением сторон | 0.1 | 0–1 |
|
||
| — | `seed` | зерно случайности | 1 | целое |
|
||
| — | `out` | каталог результата (или `--out`) | `./out/<prefix>` | — |
|
||
|
||
Пример: `uv run python -m synthgen --config synthgen/examples/default.yaml --set group_size=12 --set prefix=DEMO2 --out /tmp/demo2`.
|
||
|
||
## 4. Словарь компаний и состав группы
|
||
|
||
### 4.1 Словарь
|
||
Закоммиченный файл `synthgen/dictionary/companies.csv`: **400** вымышленных организаций —
|
||
`name;form;inn;kpp;kind` (`form` ∈ ООО/АО/ПАО/ИП; `kind` — отрасль для правдоподобия: агро, торговля, логистика,
|
||
производство, услуги, финансы, строительство). Около 10 % — ИП (ФИО + «ИП», ИНН 12 знаков, КПП пусто). Имена
|
||
собираются один раз детерминированным скриптом `synthgen/dictionary/build.py` из списков корней/отраслевых слов и
|
||
проверяются на совпадение с реальными: имена не должны совпадать с организациями из `fixtures/directories/` и фикстур ОСВ.
|
||
Из 400 записей **30 — банки** (`kind=банк`, АО/ПАО): они — кредиторы по 66/67 и только там.
|
||
ИНН — как в #35: регион `00` (не существует), верная контрольная сумма; номера словаря — диапазон `0100001–0109999`
|
||
(**не пересекается** с ИНН #35, где номера 1–99, решение 10).
|
||
|
||
Внешние контрагенты всех ОСВ берутся из словаря. Компании группы — тоже из словаря (§4.2), и **такая запись в этом
|
||
прогоне внешним контрагентом не бывает**.
|
||
|
||
### 4.2 Компании группы и префикс
|
||
- Из словаря выбираются `group_size` записей с `form` ∈ ООО/АО (не ИП); головная — АО.
|
||
- Имя компании группы: `<форма> "<prefix> <имя из словаря>"`, например `АО "DEMO2 Северная Нива"`. Название ГК: `ГК <prefix>`.
|
||
- ИНН компании группы — **не из словаря**, а из диапазона префикса: номер = `1000000 + 100 × h(prefix) + i`, где `h` —
|
||
детерминированный хеш префикса (SHA-256 → целое) в `0…89999`, `i` — порядковый номер компании (< 100). Так ИНН компаний
|
||
группы не совпадают с ИНН словаря, #35 и групп с другими префиксами (коллизия хеша ~1/90000; README её оговаривает).
|
||
- КПП — 9 цифр, как в #35: `00` + двузначный номер компании + `01001` (у ИП КПП нет).
|
||
|
||
## 5. Модель данных
|
||
|
||
### 5.1 Периоды
|
||
Периоды — `periods` кварталов подряд начиная с `start`. Каждый отчётный период — **нарастающим итогом с начала года**,
|
||
как выгружает 1С: Q1 → «1 квартал», Q2 → «1 полугодие», Q3 → «9 месяцев», Q4 → «год» (`ГГГГ-FY`). Пример: `start=2025-Q3`,
|
||
`periods=4` → `2025-Q3`, `2025-FY`, `2026-Q1`, `2026-Q2`.
|
||
|
||
Книга проводок компании ведётся с 1 января года первого периода (входящие остатки датированы **31 декабря предыдущего
|
||
года** проводкой против вспомогательного счёта `000`, как в #35) до конца последнего периода. ОСВ показывают `000` только
|
||
как источник входящих остатков — в файлы ОСВ счёт `000` не выгружается. Переход года: сальдо на 31.12 становится входящим остатком следующего
|
||
года, ОФР обнуляется закрытием 90/91 на 99 и 99 на 84 (реформация) в последний день года.
|
||
|
||
### 5.2 Экономика компании
|
||
Каждой компании назначается профиль из отрасли словаря и масштаб (выручка за год — логнормально, медиана ~1 млрд руб.,
|
||
разброс ×10 в обе стороны; головная — холдинг с небольшой выручкой и крупными вложениями 58.01). По профилю генерируются
|
||
помесячные операции:
|
||
- продажи (62/90), закупки (60/10/41/20), себестоимость (90.02), коммерческие и управленческие расходы (44/26);
|
||
- ОС и амортизация (01/02/08), денежные средства (51), НДС (19/68), налог на прибыль (68.04/99), зарплата (70/69) —
|
||
минимально, чтобы ББ был правдоподобным;
|
||
- банковские кредиты (66/67 с внешним банком из словаря), прочие расчёты (76), прочие доходы/расходы (91).
|
||
|
||
Деньги — `Decimal`, `ROUND_HALF_UP`, копейки в ОСВ; float только при записи в ячейку.
|
||
|
||
### 5.3 Внешние контрагенты в ОСВ
|
||
ОСВ генерируются по счетам **01, 58, 60, 62, 66, 67, 76, 80, 90** с субсчетами (§6.1): чек-лист `BASIC`
|
||
(01, 58, 60, 62, 66, 67, 76, 90 — `docs/context/01-project-overview.md`) плюс 80 (уставный капитал —
|
||
нужен правилу гудвилла 58.01 ↔ 80, ДП §4) (решение 7). Счета 01 и 90 по контрагентам не разворачиваются (01 — по
|
||
субсчетам, 90 — по субсчетам 90.01/90.02/…/90.09; у компании группы выручка от других компаний группы видна в ОСВ 62
|
||
продавца). 80 — по участникам (головная и миноритарий). Для каждой пары
|
||
(компания, счёт с контрагентами) генератор выбирает число внешних контрагентов `n` и набор `n` записей словаря; этот
|
||
набор постоянен по всем периодам компании (контрагент может иметь нулевое сальдо и обороты в отдельном периоде — тогда
|
||
строки нет, как в 1С).
|
||
|
||
`n` — отрицательное биномиальное с матожиданием `mean × w(счёт)` (дисперсия ≈ 2 × матожидание), где веса
|
||
`w`: 60 — 1.6, 62 — 1.6, 76 — 1.0, 66 — 0.1, 67 — 0.05, 58 — 0.1, 80 — 0 (только участники). Веса нормируются так,
|
||
что **среднее `n` по всем файлам ОСВ счетов 58, 60, 62, 66, 67, 76 = `osv_counterparties_mean`**. Потолки: 60/62/76 —
|
||
словарь без банков и компаний группы (≈ 370 − `group_size`), 66/67 — 30 банков. Если потолок срезал выборку, недобор
|
||
перераспределяется на остальные файлы того же счёта; тест среднего (допуск ±15 % при числе файлов ≥ 20) применяется,
|
||
когда потолок не сработал ни разу, иначе README прогона пишет «среднее ограничено словарём» (решение 9).
|
||
|
||
Обороты распределяются по контрагентам по Парето (несколько крупных, длинный хвост мелких), сумма = обороты счёта из
|
||
экономики §5.2. У части контрагентов (~20 %) только обороты без сальдо, у ~5 % — сальдо без оборотов (старая
|
||
задолженность). Знак сальдо по контрагенту — по смыслу счёта, у ~3 % в 60/62 — противоположный (аванс/переплата),
|
||
как в реальных ОСВ.
|
||
|
||
### 5.4 Операции внутри группы
|
||
- Головная компания владеет долями всех остальных: 58.01 у головной по каждой дочерней ↔ 80 у дочерней по головной
|
||
(уставный капитал; доля головной = её часть в 80). Доли 51–100 %, у одной-двух дочерних — миноритарий из словаря
|
||
(внешний участник, только в 80). Вклады — входящие остатки, движения в периодах нет.
|
||
- Торговля: каждая компания с вероятностью 0.5 продаёт каждой другой (62 у продавца ↔ 60 у покупателя); объём —
|
||
5–20 % выручки продавца; расчёты гасятся частично, на конец периода остаётся сальдо.
|
||
- Займы: головная выдаёт займы дочерним (58.03 ↔ 66.03/67.03), с процентами (76 ↔ 76, 91).
|
||
- Дивиденды: дочерние объявляют головной раз в год во 2 квартале (Дт 84 Кт 76.03 у плательщика ↔ Дт 76.03 Кт 91 у получателя — стороны правила ОФР 2310, ДП §4)
|
||
и **выплачивают в том же месяце** — на конец периода остатка нет, обороты видны в ОСВ 76 обеих сторон. Так дивиденды не
|
||
дают пар ВГО по сальдо и не создают ложных односторонних пар; дивиденды в расхождения §5.5 не попадают (решение 11).
|
||
- Каждая операция проводится **в двух книгах одновременно** симметрично (Дт у одной, Кт у другой — по смыслу счетов).
|
||
|
||
### 5.5 Расхождения ВГО
|
||
Пара ВГО — (компания A, компания B, вид расчёта: торговля / заём). Доля `vgo_mismatch_share` пар (округление вниз, но
|
||
не меньше 1, если доля > 0 и пар ≥ 1) получает расхождение: в последнем месяце **первого** периода года, где у пары есть
|
||
сальдо, одна сторона проводит накладную (или начисление процентов), которой нет у другой («в пути»). Величина — доля от меньшей стороны сальдо пары на конец этого периода, **вперемешку** (решение владельца 12.11.2026):
|
||
половина пар с расхождением (чётные по порядку выбора) — 1–4 % (ниже порога 5 % ДП §4: ядро сводит по `min`
|
||
автоматически), другая половина — 8–25 % (выше порога: пара подсвечивается и ждёт корректировки аналитика). При
|
||
нечётном числе пар лишняя — крупная. README прогона пишет для каждой пары процент и сторону порога. Расхождение держится до конца
|
||
года и снимается в январе следующего года проводкой второй стороны (накладная дошла) — в следующем году пара снова
|
||
выбирается заново. Остальные пары сходятся точно.
|
||
|
||
### 5.6 Согласованность (инварианты, проверяются тестами)
|
||
1. В каждой книге Σ Дт = Σ Кт по каждой проводке; сальдо `000` после входящих остатков — ноль.
|
||
2. ОСВ компании за период = свёртка её книги (сальдо на начало, обороты, сальдо на конец) по счёту/субсчёту/контрагенту;
|
||
строка родителя = сумма детей.
|
||
3. ББ = сальдо книги на конец периода по правилам свёртки счетов в строки 66н (из `forms.py` #35, §8); ОФР = обороты
|
||
90/91/99 за период нарастающим итогом. В тыс. руб. с `ROUND_HALF_UP` построчно, итоги — суммы округлённых строк;
|
||
1600 = 1700 обеспечивается подгонкой ±1 тыс. в одной строке (как `_plug` в #35). ОСВ — в рублях.
|
||
4. Пары ВГО без расхождения: сальдо у A по B = −(сальдо у B по A) по соответствующим счетам (62↔60, 58.03↔66.03/67.03,
|
||
76↔76); с расхождением — разница ровно на сумму накладной «в пути» §5.5 во всех периодах от её проводки до конца
|
||
года и ноль после января.
|
||
6. Колонка ББ «На 31 декабря» года, предшествующего первому периоду, = входящие остатки книги (= «Сальдо на начало»
|
||
ОСВ первого года); колонки ранее этого (ББ «на 31 декабря» позапрошлого года, ОФР за прошлый год, если прошлого года
|
||
нет в книге) — масштабированные значения профиля без требования согласованности (решение 5).
|
||
5. Компания группы в ОСВ другой компании группы записана тем же именем, что в ЕЦП, и тем же ИНН, кроме §6.1 (без ИНН).
|
||
|
||
## 6. Вид файлов
|
||
|
||
### 6.1 ОСВ
|
||
Формат «ОСВ по счёту» 1С 8.3 (БП 3.0) по образцу `fixtures/osv/rb/2024-Q1/сч 60_ 1 кв 2024 г.xlsx`: лист `Лист_1`,
|
||
строки 1–10 — шапка (организация, «Оборотно-сальдовая ведомость по счету NN за <период>», «Выводимые данные…»,
|
||
«Параметры: Валюта БУ, НУ: RUB», двухуровневый заголовок «Счет / Контрагенты», «Сальдо на начало периода / Обороты за
|
||
период / Сальдо на конец периода», «Дебет / Кредит»), строка 12 — счёт, далее субсчета (уровень группировки 1, отступ 1)
|
||
и контрагенты (уровень 2, отступ 2), внизу «Итого». Оформление — **как в образце**: Arial 8 (данные) / 9–10 (шапка) /
|
||
12 полужирный (заголовок), заливка шапки `F4ECC5`, строк счетов и субсчетов `F8F2D8`, тонкие рамки у всех ячеек
|
||
таблицы, фиксированные высоты строк, сетка узких колонок A…W с объединением ячеек (колонка «Счет» — A:D, суммы —
|
||
E:G, H:I, J:K, L:N, O:P, Q:… по образцу), формат `#,##0.00`, нулевые суммы — пустые ячейки, итог над детьми
|
||
(`summaryBelow=False`). Сетку колонок, ширины и объединения генератор берёт из образца программно (читает образец при
|
||
сборке шаблона), а не набирает вручную.
|
||
|
||
ИНН контрагента у компаний **с ИНН** — отдельная колонка после наименования (решение 3), у компаний из
|
||
`no_inn_companies` — колонки нет. Парсер ищет колонку ИНН строго между колонкой текста и первой колонкой сумм
|
||
(`tabular-parser/api/src/parser/detect.py`, `_inn_col`), поэтому в ОСВ с ИНН сетка образца **сдвигается**: наименование
|
||
A:D, ИНН — новая колонка E (ширина ~11), суммы — со следующей колонки с той же внутренней сеткой объединений, что в
|
||
образце. Шаблон хранит сетку как «блок текста + опциональная колонка ИНН + блок сумм», а не как фиксированные буквы. В ОСВ без ИНН компании группы записаны: большинство — тем же именем, что в ЕЦП
|
||
(автоматическое сопоставление по имени), **одна** компания группы на каждую такую ОСВ-компанию — немного другим
|
||
написанием (без кавычек и формы в конце, «Северная Нива DEMO2 ООО», как в 1С у реальных контрагентов) — её аналитик
|
||
сопоставит вручную (решение 4).
|
||
|
||
Имя файла: `сч NN_<период 1С>.xlsx`, например `сч 60_9 месяцев 2025 г.xlsx`, `сч 62_2025 г.xlsx`.
|
||
|
||
### 6.2 ББ и ОФР
|
||
Шаблон — реальная выгрузка 1С на листе `TDSheet` (форма 66н): `fixtures/statements-xlsx/mezheninovskaya/2025-Q3/
|
||
1_Форма1_бух_09.xlsx` (ББ) и `1_Форма2_бух_09.xlsx` (ОФР). Генератор **копирует книгу образца** (стили, объединения,
|
||
шрифты Times New Roman, высоты строк, рамки, формат нулей `[=0]"-";General`, блок «КОДЫ») и заменяет:
|
||
- реквизиты: наименование, ИНН, ОКПО, ОКВЭД, ОКОПФ/ОКФС, вид деятельности, адрес — вымышленные; **все** прочие ячейки
|
||
шапки с данными образца (аудитор, его ИНН/ОГРН, подписи, даты подписания) очищаются; тест проверяет, что ни одна строка
|
||
из образца, кроме неизменяемых текстов формы, не попала в файл;
|
||
- даты и заголовки колонок периодов («За 9 месяцев 2025 года», «На 31 декабря 2024 года», «На 31 декабря 2023 года» —
|
||
для ББ; текущий и прошлый год — для ОФР);
|
||
- значения — по кодам строк (колонка «Коды»), а не по номерам строк образца.
|
||
|
||
Образец правили в Excel, поэтому при сборке шаблона (`synthgen/templates/build_templates.py`, один раз, результат
|
||
коммитится) генератор: заменяет **все** формулы значениями (итоги 1100/1200/1600/1300/1400/1500/1700, 2100/…/2500 и
|
||
заголовки второй страницы — генератор пишет числа сам); удаляет рабочие формулы и значения вне формы (ячейки правее
|
||
последней колонки формы); очищает ФИО в подписях и ставит вымышленные; оставляет в ОФР строки 2530, 2900, 2910 (2530 —
|
||
из книги, если есть, иначе «-»; 2900/2910 — «-»). Значения в файле — в тыс. руб. **целыми** (решение 12), расходы ОФР —
|
||
**отрицательными** числами, как в образце (в скобках их не пишет и 1С). Набор строк — 66н из `forms.py` #35 плюс
|
||
2530/2900/2910; `edition_of` из #35 не копируется (всегда 66н).
|
||
|
||
Колонки сравнительных периодов (прошлый год / позапрошлый год), если книга их не покрывает (период до начала книги),
|
||
заполняются масштабированными значениями того же профиля (решение 5) — так у файла нет пустых колонок, но и
|
||
согласованность для них не требуется.
|
||
|
||
Имена файлов: `ББ_<период 1С>.xlsx`, `ОФР_<период 1С>.xlsx`.
|
||
|
||
### 6.3 Состав ГК (импорт ЕЦП)
|
||
`directories/ecp-<prefix>.xlsx` в формате файла #35 `synthetic-group/directories/ecp-synthetic-group.xlsx` (он
|
||
прошёл импорт в ядро; реальная выгрузка `fixtures/directories/ecp-export-russkiy-biskvit.xlsx` отличается — лист
|
||
`Выписки_01.04.26`, без КПП, имена «ЭКО ВОЛОГДА, АО» — импорт ядра принимает оба вида): лист `Выписки`, колонки
|
||
`ИНН, КПП, iD ГК, Базовый РФ ГК в CRM, Группа компаний, Организация, ГСК, ГСЗ, Тип`; все компании — «Входит в ГСК»;
|
||
`iD ГК` = `<prefix>-000001`; ИНН/КПП текстом. Перед реализацией исполнитель сверяет формат с эндпоинтом импорта
|
||
(`arm-core`, `POST /api/v1/directories/import`) и с `ecp.py` #35.
|
||
|
||
## 7. Результат прогона
|
||
|
||
```
|
||
<out>/
|
||
README.md — настройки прогона, состав группы (имя, ИНН, доля головной), список пар ВГО с
|
||
расхождениями (стороны, вид, сумма), какие компании без ИНН и какой контрагент
|
||
записан «другим именем», число контрагентов по файлам — не оракул, а журнал генератора
|
||
config.yaml — фактические настройки (с seed)
|
||
directories/ecp-<prefix>.xlsx
|
||
osv/<КОД>/<период>/сч NN_….xlsx
|
||
statements/<КОД>/<период>/ББ_….xlsx, ОФР_….xlsx
|
||
```
|
||
`<КОД>` — короткий латинский код компании (`C01`…`Cnn`, головная — `C01`); полное имя — в README и ЕЦП.
|
||
|
||
## 8. Код и переиспользование
|
||
|
||
- Пакет `synthgen/` в корне fixtures (рядом с `synthetic-group/`), запуск `uv run --with openpyxl==3.1.5 --with pyyaml
|
||
python -m synthgen …`. Тесты — `synthgen/tests/`, pytest.
|
||
- Ветка #35 ещё не слита, поэтому нужные модули **копируются** в `synthgen/` (с пометкой источника в docstring), не
|
||
импортируются из `synthetic-group/`: `inn.py`, `ledger.py`, `xlsx.py` (детерминированное сохранение), `chart.py`
|
||
(субсчета из seed arm-core; добавить недостающие — 80.09, 84, 91.09, 99 — сверив с
|
||
`arm-core` seed `chart_of_accounts.csv`), правила свёртки счетов в строки ББ/ОФР из `forms.py`, запись ЕЦП из `ecp.py`.
|
||
Источник — worktree `/Users/andrey.epifancev/projects/awp-ca/.wt/synthetic-group/fixtures/synthetic-group/gen/`.
|
||
Дедупликация — после слияния обоих (follow-up).
|
||
- Сгенерированные наборы **в git не коммитятся** (`out/` в `.gitignore`); коммитятся код, словарь, пример конфигурации и
|
||
шаблоны, собранные из образцов (без реквизитов образца).
|
||
|
||
## 9. Приёмка
|
||
|
||
- `pytest` зелёный: инварианты §5.6, распределение §5.3 (среднее в допуске), доля расхождений §5.5, число ОСВ без ИНН,
|
||
ИНН из диапазона префикса и с верной контрольной суммой, отсутствие реквизитов образцов, детерминированность
|
||
(два прогона с одним `seed` → побайтно равны; разный `seed` → различаются), ошибки конфигурации.
|
||
- Прогон по умолчанию и прогон «крупный» (`group_size=15, periods=6, osv_counterparties_mean=150`) проходят парсер ОСВ
|
||
`tabular-parser` (зеркальный worktree, как в #35) без ошибок; разбор ББ/ОФР в Excel находит все коды и сходит
|
||
1600 = 1700.
|
||
- Время крупного прогона — до 5 минут на ноутбуке разработчика.
|
||
|
||
## 10. Что оставлено исполнителю
|
||
|
||
Выбор распределений внутри указанных рамок, алгоритм генерации имён словаря, внутренняя структура пакета, формат
|
||
README прогона, точные субсчета внутри перечисленных счетов (из `chart.py`).
|
||
|
||
## Decisions made without discussion
|
||
|
||
| № | Дата | Решение | Обоснование | Цена ошибки |
|
||
|---|---|---|---|---|
|
||
| 1 | 12.11.2026 | Новый пакет `synthgen/` отдельно от `synthetic-group/` (#35), модули #35 копируются | #35 не слита; разные задачи (контрольный набор vs объём); импорт между каталогами связал бы их ветки | дублирование кода до follow-up |
|
||
| 2 | 12.11.2026 | Все компании ГК входят в ГСК; `group_size` — одно число | владелец назвал «размер ГСК/ГК» одной настройкой | нет сценария «член ГК вне ГСК» — добавить настройку позже |
|
||
| 3 | 12.11.2026 | ИНН — отдельная колонка после наименования | в образцах 1С ИНН нет; колонка уже принимается парсером (#35, приёмка tabular-parser); в имени ИНН сломал бы сопоставление по имени | если в реальных выгрузках ИНН иначе — поменять запись ОСВ |
|
||
| 4 | 12.11.2026 | В ОСВ без ИНН одна компания группы — с другим написанием имени | владелец: «аналитик сопоставит малую часть контрагентов» | если нужно больше ручных сопоставлений — сделать настройкой |
|
||
| 5 | 12.11.2026 | ББ «на 31.12» года перед первым периодом — из книги (входящие остатки); более ранние сравнительные колонки — масштабированные значения профиля | первая колонка должна совпадать с «Сальдо на начало» ОСВ того же набора; глубже книги нет, пустые колонки нетипичны для 1С | аналитик может заметить несоответствие позапрошлогодних цифр |
|
||
| 6 | 12.11.2026 | Только форма 66н | образец 1С с 66н есть (Межениновская, 9 мес. 2025); ФСБУ 4/2023 — не-цель | годовая отчётность 2025+ по новой форме не покрыта |
|
||
| 7 | 12.11.2026 | Счета ОСВ: 01, 58, 60, 62, 66, 67, 76, 80, 90 | чек-лист `BASIC` (01-project-overview) + 80 (правило гудвилла 58.01↔80); 02 не входит в `BASIC` | версия `EXTENDED` покажет пробелы чек-листа — добавить счета позже |
|
||
| 9 | 12.11.2026 | `osv_counterparties_mean` ≤ 150, потолки по словарю, перераспределение недобора | владелец задал словарь 200–400; при среднем 300 потолок 60/62 срезает выборку и среднее не выполняется | для большего объёма нужен словарь крупнее — расширить `build.py` |
|
||
| 10 | 12.11.2026 | ИНН словаря — номера 0100001–0109999, компании группы — 1000000+ по хешу префикса | не пересекаться с #35 (номера 1–99) и между префиксами на одном стенде | коллизия хеша префиксов ~1/90000 |
|
||
| 11 | 12.11.2026 | Дивиденды выплачиваются в месяце объявления, в пары ВГО по сальдо и расхождения не попадают | дивиденды проводятся через 76.03 у обеих сторон (стороны правила ОФР 2310) и гасятся в месяце объявления; остаток на дату ББ дал бы пару по 76.03 вне доли расхождений (ДП §4, правила 5–8) — остаток дал бы ложную одностороннюю пару | пары по дивидендам не видны в шахматке по сальдо (обороты видны в ОСВ 75/76) |
|
||
| 12 | 12.11.2026 | ББ/ОФР в тыс. руб. целыми, расходы ОФР отрицательными | так выглядит образец 1С; дробные тысячи в образце — следствие ручной правки | если ядро ждёт копейки в тысячах — поменять запись |
|
||
| 8 | 12.11.2026 | Значения по умолчанию §3 (5 компаний, `SYN`, 4 периода с 2025-Q1, 60 контрагентов, 1 без ИНН, доля 0.1) | разумный стартовый объём: «есть что смотреть», прогон быстрый | никакой — меняется настройкой |
|
||
| 13 | 12.11.2026 | Тесты жизненного цикла расхождений ВГО проверяют снятие 31 января, только если январь следующего года внутри периодов книги; иначе — только удержание до конца года (R-1) | книга кончается последним периодом, января после него нет | непроверенное снятие расхождения в хвосте набора |
|
||
| 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 | нет |
|
||
| 21 | 12.11.2026 | Веса контрагентов — детерминированная кривая Лоренца Парето (доля верхних p = p^(1−1/α), α=1.2, шум ±15 %), а не выборка `paretovariate` | выборка Парето на 43 контрагентах давала долю верхних 10 % от 23 % до 97 % по зерну; тест формы (≥ 50 %) стал бы случайным | распределение чуть «глаже» настоящей выборки Парето |
|
||
| 22 | 12.11.2026 | `Ledger`: индекс накопленных итогов по (счёт, аналитика) для `balance` на дату не раньше последней проводки; `Posting`/`An` — `slots=True` | закрытие месяца спрашивает сальдо по 90/91/68/99 после каждого месяца; полный проход по проводкам давал O(N²) на крупном прогоне (600 тыс. проводок при 15 компаниях, 6 периодах, 150 контрагентах) | запрос сальдо на прошлую дату всё ещё идёт проходом по проводкам (кэшируется) |
|
||
| 23 | 12.11.2026 | В `chart.ACCOUNTS` добавлены 01.01, 67.01, 67.02; названия всех субсчетов сверены с seed arm-core (`api/seed/core/chart_of_accounts.csv`) — расхождений нет (T1.7) | экономика использует 01.01 и кредиты/проценты 67.01/67.02, которых не было в копии #35 | нет |
|
||
| 24 | 12.11.2026 | Поведение контрагентов: ~72 % «обычные» (оплата в следующем месяце), ~20 % «только обороты» (оплата в месяце), ~5 % «только сальдо» (входящий остаток), ~3 % «аванс» (62.02/60.02 обратного знака, не меньше одного при n ≥ 8); трое крупнейших — всегда обычные. Для 58 «обычные» и «только сальдо» — остаток займа 58.03, «только обороты» — выдан и возвращён в месяце. Без внешних контрагентов (mean = 0) операции идут без аналитики контрагента | §5.3 задаёт доли, но не привязку к субсчетам; механика — из #35 | доли в ОСВ могут отличаться от реальной выгрузки банка — поменять константы в `economy._classes` |
|
||
| 25 | 12.11.2026 | Головная в `economy` — холдинг: выручка ~50 млн ₽/год, ОС 1.5× выручки, закупок 10 %; вложений 58.01 и участников в 80 нет — 80 проводится без аналитики на 2 % годовой выручки (их добавит Task 4 вместе с операциями внутри группы) | вложения 58.01 по смыслу — операции внутри группы (гудвилл 58.01 ↔ 80 дочерних), не внешняя экономика | Task 4 должен перепровести 80 по участникам и 58.01 головной |
|
||
| 26 | 12.11.2026 | Входящие остатки групповых операций (Task 4): капитал дочерней — 80.09 по участникам (головная `An(cp=<код>)`, миноритарий `An(cp=<ИНН>)`, доли по `head_share`), у головной 58.01 по каждой дочерней на ту же сумму; займы головной — 58.03 ↔ 66.03 или 67.03 (по зерну `loan-term`) на 3–6 % годовой выручки дочерней; 84 пересчитывается из итога строк после хука (`economy.open_books(opening_hook=…)`). Собственный 80 головной остаётся без аналитики | §5.4 говорит «80», бриф — «80.09»; по ДП §4 правило гудвилла берёт 80/83, 80.09 входит; участники головной в группе неизвестны | если ядру нужен 80 (без .09) — сменить субсчёт в `_opening_hook` |
|
||
| 27 | 12.11.2026 | `VgoPair` помимо `mismatch` получил `mismatches` (по одному на год, где пара выбрана); `mismatch` — расхождение первого года | §5.5 выбирает пары заново каждый год, одним полем не выразить | потребители читают `mismatches`; `mismatch` — только для удобства |
|
||
| 28 | 12.11.2026 | Торговля: объём пары 5–20 % выручки продавца (с НДС), но суммарные закупки покупателя у группы ≤ 10 % его годовой выручки (пропорциональное сжатие); счёт в день 10, оплата 50–90 % в месяце счёта, остаток — в следующем; себестоимость продавца по ВГО-реализации не проводится, покупатель списывает товар (90.02 Кт 41) | без ограничения покупатель с 7 поставщиками уходил в минус по 51 (проверяется тестом); себестоимость продавца потребовала бы остатков 41 | прибыль продавца по ВГО завышена на себестоимость; объём при многих продавцах меньше 5 % |
|
||
| 29 | 12.11.2026 | Расхождение: процент малых — 1.05–3.95, крупных — 8.05–24.95 (первые `k//2` выбранных малые); проводка «в пути» — день 25, снятие — 31 января; у торговли — накладная продавца (Дт 62.01 Кт 90.01) или покупателя (Дт 41 Кт 60.01), у займа — транш по основному долгу (Дт 58.03 Кт 51 у займодавца либо Дт 51 Кт 66.03/67.03 у заёмщика; R-3, решение 32). `pct` считается от меньшей стороны сальдо на конец периода создания; в следующих периодах сальдо торговли меняется, сумма разницы остаётся | границы с запасом от 1/4/8/25 %, чтобы округление суммы не выводило процент из диапазона | пересчёт процента ядром по другому правилу даст иной pct |
|
||
| 30 | 12.11.2026 | Дивиденды: май каждого года книги, 3 % годовой выручки дочерней; доля головной по `head_share` (Дт 84 Кт 76.03 `An(cp=<код>)` ↔ Дт 76.03 Кт 91.01 item «Дивиденды»), остаток — миноритарию через `An(cp=<ИНН>)`; выплата в день 20 того же месяца | решение 11 требует нулевого сальдо 76.03; миноритарий получает свою долю | если нужны дивиденды из прибыли года — привязать к 84 |
|
||
| 31 | 12.11.2026 | Проценты по займам группы — 10 % годовых, начисляются и уплачиваются 28-го числа каждого месяца по субсчёту 76.09 обеих сторон | пары 76↔76 по правилу 6 ДП §4, остаток 76.09 нулевой | нет |
|
||
| 32 | 12.11.2026 | R-3 (рулинг контроллера по ревью Task 4): расхождение по займу — по основному долгу (правило 4 ДП §4), проценты 76.09 ↔ 76.09 начисляются симметрично и гасятся в месяце начисления, в расхождения не попадают; если у займодавца на конец периода денег меньше 2 × суммы транша, транш «в пути» проводит заёмщик. ВГО-выручка продавца за год ≤ 40 % его годовой выручки с НДС (`SELLER_CAP`, симметрично `BUYER_CAP`; пропорциональное сжатие) | расхождение в 76↔76 дало бы ядру правило 6 с меньшей стороной 0 (процент не определён, OQ-175); без потолка продавца ВГО-выручка могла превысить годовую выручку | при другом проценте ядра у займов оракул Task 5 расходится с генератором; потолок 40 % сжимает объём пар |
|
||
| 33 | 12.11.2026 | Свёртка ОСВ (`osv.rows`) идёт по индексу книги с накопленными по дням итогами (счёт, субсчёт, контрагент), а не через `Ledger.balance/turnover` на каждый период; результат совпадает с ними (тест) | на каждый период и счёт проходить по всем проводкам книги слишком долго | нет: расхождение ловится `test_rows_match_ledger` |
|
||
| 34 | 12.11.2026 | Строки ОСВ без сальдо и оборотов не пишутся; у счёта 80 без аналитики (головной) строка счёта без раскрытия; «Итого» — сумма строк счёта; колонка ИНН пишется только у контрагентов с ИНН | как в выгрузке 1С; ДП не требует нулевых строк | нет: парсер принимает, тест `-m parser` |
|
||
| 35 | 12.11.2026 | T3.8: закрывающие проводки различаются полем `Posting.kind` (`KIND_CLOSE_90` — 90.01..90.08 → 90.09, `KIND_CLOSE_99` — 90.09 и 91 → 99, `KIND_CLOSE_COST` — 26/44 → 90.08/90.07), memo остаётся подписью. В ОФР не входят `close90` и `close99`; закрытие затрат входит; обороты считаются как «нетто» Дт − Кт по счёту, а не только по одной стороне | разбор memo хрупок; закрытие на 90.09 удваивало бы обороты 90.01/90.02 | проводка без `kind` в закрывающем коде попадёт в ОФР: ловит `test_net_profit_equals_99_balance` |
|
||
| 36 | 12.11.2026 | Масштабированные колонки ББ (до начала книги): множитель 0.8–1.1 на строку из `rng_for(seed, "prev", код, вид, код компании, период, номер колонки)` (спека называла только код и вид — добавлены компания, период и колонка, чтобы колонки не совпадали между компаниями); баланс уравнивает строка 1370 (нераспределённая прибыль), а не ±1 подгонка | независимые множители ломают 1600 = 1700 на величину, не лечащуюся подгонкой ±1; прошлые колонки по спеке согласованности не требуют | 1370 прошлых колонок может выйти нетипичной (в т. ч. убыток) |
|
||
| 37 | 12.11.2026 | Подгонка округления ББ: ±1 тыс. строкам с наименьшей потерей точности, каждой строке не более одного раза; подогнанные строки записаны в `Form.plugged` (по колонкам) | «в одной строке» из §5.6 п. 3 не всегда хватает: при ~20 строках расхождение округлений достигает нескольких тыс. | строка подгонки может отличаться от книги на 1 тыс. (тест допускает ±1 только для `plugged`) |
|
||
| 38 | 12.11.2026 | ОФР: 2411 = 2410 (налог текущий), 2412 = 0, 2460/2510/2520/2530/2900/2910 = «-»; 2500 = 2400 + 2510 + 2520 + 2530; подписи колонок: «За 3 месяца / 6 месяцев / 9 месяцев\n<год> года», для года — «За <год> год» (ОФР) и «На 31 декабря\n<год> года» (ББ); дата шапки — конец периода | отложенных налогов и прочих результатов модель не создаёт; подписи по образцу 1С, парсер определяет даты колонок | OPEN-QUESTION: форма подписей «За 3 месяца» для 1 квартала у реальных выгрузок не проверена |
|
||
| 39 | 12.11.2026 | В шаблонах ББ/ОФР сохранены флаги образца (`M15`/`R15` ББ, нумерация колонок ОФР); реквизиты (ОКПО, ОКВЭД по отрасли, ОКОПФ 65/47, адрес, ФИО подписантов) — детерминированные вымышленные из `stable_hash` по ИНН/компании/периоду | формы 66н требуют реквизитов; ничего из образца не должно попасть в набор | ОКПО/ОКОПФ вымышлены, не проверяются классификаторами |
|