Initial import NDC_1C

This commit is contained in:
2026-03-26 10:38:25 +03:00
commit a162d77ef7
2943 changed files with 3615871 additions and 0 deletions
@@ -0,0 +1,289 @@
Да, теперь уже видно не просто то, что “онтология просажена”, а **почему именно она просажена и как именно её чинить**.
Главный вывод после догрузки: у вас ломается не “граф вообще”, а **семаническая нормализация ссылок**. Текущий mapping-слой построен слишком грубой эвристикой: все ссылки превращаются в один relation `reference`, а `target_entity` определяется почти только по подстроке в имени поля. Из-за этого система формально строит links, но содержательно не понимает, кто есть кто. Отсюда и 1016 `Unknown`-relations из 2618, при semantic coverage всего 61.19%, хотя link coverage 100%.
## Где именно источник поломки
Сейчас правило такое:
* если поле оканчивается на `_Key`, `ref`, или выглядит как GUID — оно становится link;
* relation всегда один: `reference`;
* target_entity угадывается по имени поля;
* если не угадали по нескольким словам вроде `контраг`, `договор`, `счет`, `организ`, `документ`, то ставится `Unknown`.
Это и есть корневая ошибка архитектуры. Для бухгалтерского контура такой подход слишком бедный, потому что:
* `Recorder` — это не просто “reference”, а **источник движения / документ-регистратор**;
* `Ref` в журнале — это не просто “reference”, а **journal entry points to concrete document**;
* `Поставщик_Key` и `Покупатель_Key` — не generic refs, а **роли контрагентов**;
* `Ответственный_Key` — не generic ref, а **actor / responsible person**;
* `Валюта_Key` — это вообще не `Document`, а отдельная сущность валюты;
* `Склад_Key`, `ПодразделениеДт_Key`, `ФизЛицо_Key`, `СтатьяДвиженияДенежныхСредств_Key` требуют собственных классов, а не падения в `Unknown`.
## Что ломается на реальных примерах
### 1) `Recorder` в регистрах НДС
В НДС-регистрах `Recorder` приходит вместе с `Recorder_Type`, например:
* `Recorder_Type = StandardODATA.Document_ПоступлениеТоваровУслуг`
* либо `Recorder_Type = StandardODATA.Document_СчетФактураПолученный`
* либо `Recorder_Type = StandardODATA.Document_РеализацияТоваровУслуг`.
Но в links это всё равно уходит как `target_entity = Unknown`.
Это критично, потому что для бухгалтерской аналитики регистр без корректного регистратора — почти полуслепая запись. Вы теряете причинную цепочку:
**движение регистра → документ-регистратор → контрагент/договор/товар/сумма**.
### 2) `СчетФактура` ошибочно уезжает в `Account`
В НДС-регистрах поле `СчетФактура` по смыслу ссылается на документ, а не на бухгалтерский счёт. Но из-за эвристики “если в имени есть `счет` → `Account`” оно типизируется как `Account`. Это уже не просто unknown, а **ложноположительное сопоставление**.
То есть у вас часть связей не просто потеряна, а неверно искажена.
### 3) `Ref` в журналах документов
В `DocumentJournal_БанковскиеВыписки` есть:
* `Ref`
* `Ref_Type = StandardODATA.Document_СписаниеСРасчетногоСчета`
или `...ПоступлениеНаРасчетныйСчет`.
Но link по `Ref` уходит в `Unknown`, хотя это должен быть прямой указатель на конкретный документ. Именно поэтому журналы дают много unknown и source_id=unknown.
### 4) Валюта маппится неверно
В `СписаниеСРасчетногоСчета` поле `ВалютаДокумента_Key` в одном из образцов уходит в `Document`, просто потому что в имени есть слово `документа`, хотя это ссылка на валюту. Аналогичная проблема в журналах по `Валюта_Key`, где поле уходит в `Unknown`.
### 5) Ролевые контрагенты не разведены
В регистрах есть `Поставщик_Key`, `Покупатель_Key`, `ДоговорКонтрагента_Key`.
Сейчас:
* `Поставщик_Key` и `Покупатель_Key` часто падают в `Unknown`,
* `ДоговорКонтрагента_Key` обычно распознаётся как `Contract`,
но без явной роли в relation.
В результате граф знает, что “что-то связано с контрагентом/договором”, но не знает:
* это supplier или buyer,
* это основной контрагент документа или договор расчётов,
* это связь документа, журнала или регистра.
### 6) `Ответственный_Key`, `ФизЛицо_Key`, `Склад_Key`, `СтатьяДвиженияДенежныхСредств_Key`
Эти поля в топе проблемных, потому что для них вообще нет соответствующих сущностей в базовой canonical-модели. В текущем ядре у вас classes: `Organization`, `Counterparty`, `Contract`, `Account`, `Subconto`, `Document`, `Posting`, `RegisterMovement`, `Period`, плюс `CanonicalEntity`. Но нет нормальных классов для people/employee, warehouse, currency, cashflow article, department, item/product. Поэтому всё это закономерно валится в `Unknown`.
## Значит ли это, что надо “подкручивать только relation rules”?
Нет. Тут нужно чинить **сразу три слоя**:
### A. Расширять canonical classes
Минимально надо добавить:
* `EmployeeOrUser` / `ResponsiblePerson`
* `Currency`
* `Warehouse`
* `CashflowArticle`
* `Department`
* `Individual`
* `Item` / `Nomenclature`
* `BankAccount`
* `TaxRegisterRecord` или более общий `RegisterRecord`
* `InvoiceDocument` / `FacturaDocument` как подтип документа, если хотите потом делать объяснения по НДС аккуратнее.
Без этого вы можете переписать relation rules хоть десять раз, но часть полей всё равно некуда будет положить.
### B. Уходить от одного relation `reference`
Нужен не один `reference`, а словарь осмысленных relations. Минимальный стартовый набор я бы делал такой:
**Документы / журналы**
* `journal_refers_to_document`
* `document_belongs_to_organization`
* `document_has_counterparty`
* `document_has_contract`
* `document_has_currency`
* `document_has_warehouse`
* `document_has_responsible`
* `document_has_cashflow_article`
* `document_has_bank_account`
**Регистры**
* `register_recorded_by_document`
* `register_relates_to_supplier`
* `register_relates_to_buyer`
* `register_relates_to_invoice`
* `register_relates_to_vat_account`
* `register_relates_to_contract`
* `register_relates_to_organization`
**Финансовые документы**
* `payment_relates_to_counterparty`
* `payment_relates_to_bank_account`
* `payment_relates_to_cashflow_article`
* `payment_relates_to_individual`
* `payment_relates_to_department`
**Строки табличных частей**
* `document_line_has_item`
* `document_line_has_account`
* `document_line_has_vat_account`
* `document_line_has_expense_account`
* `document_line_has_income_account`
### C. Менять сам принцип типизации
Не по имени поля alone, а по комбинации:
1. `source_entity`
2. `source_field`
3. `*_Type` рядом
4. наличие `navigationLinkUrl`
5. контекст набора полей вокруг записи.
Именно это даст переносимость между разными 1С-контурами, а не ручную подгонку под июнь 2020, чего у вас как раз требует ТЗ.
## Как я бы правил правила маппинга
### Правило 1. `*_Type` имеет приоритет над эвристикой имени
Если есть:
* `Recorder_Type`
* `Ref_Type`
* `СчетФактура_Type`
* `ДокументОплаты_Type`
* и т.п.,
то target_entity нужно определять не по имени поля, а по значению type.
Например:
* `StandardODATA.Document_*` → `Document`
* `StandardODATA.Catalog_Контрагенты` → `Counterparty`
* `StandardODATA.Catalog_Склады` → `Warehouse`
* `StandardODATA.Catalog_Валюты` → `Currency`
* `StandardODATA.Catalog_ФизическиеЛица` → `Individual`
* `StandardODATA.Catalog_СтатьиДвиженияДенежныхСредств` → `CashflowArticle`.
Это автоматически лечит большую часть `Recorder`, `Ref` и typed-полей.
### Правило 2. Для конкретных полей — словарь приоритетных semantic mappings
Нужен явный field dictionary, например:
* `Recorder` → relation `register_recorded_by_document`, target `Document`
* `Ref` в `DocumentJournal_*` → relation `journal_refers_to_document`, target `Document`
* `Поставщик_Key` → relation `*_relates_to_supplier`, target `Counterparty`
* `Покупатель_Key` → relation `*_relates_to_buyer`, target `Counterparty`
* `Ответственный_Key` → relation `*_has_responsible`, target `ResponsiblePerson`
* `Валюта_Key` / `ВалютаДокумента_Key` → relation `*_has_currency`, target `Currency`
* `Склад_Key` → relation `*_has_warehouse`, target `Warehouse`
* `СтатьяДвиженияДенежныхСредств_Key` → relation `*_has_cashflow_article`, target `CashflowArticle`
* `ФизЛицо_Key` → relation `*_relates_to_individual`, target `Individual`
* `БанковскийСчет_Key` / `СчетОрганизации_Key` → target `BankAccount`.
### Правило 3. `СчетФактура` — специальный case
Поле `СчетФактура` нельзя по общему правилу отправлять в `Account`.
Если рядом есть `СчетФактура_Type = StandardODATA.Document_*`, то это relation к документу:
* `register_relates_to_invoice`
* target `Document`.
### Правило 4. Нулевые GUID не создавать как обычные бизнес-связи
`00000000-0000-0000-0000-000000000000` сейчас плодит мусорные links. Их лучше:
* либо не писать в canonical links вообще,
* либо писать как `null_reference` / `empty_reference` технического типа, отдельно от семанических relations.
Это сразу уменьшит шум в графе и не будет создавать фальшивых “связей с нулевым контрагентом”.
### Правило 5. `source_id` для register records нельзя оставлять `unknown`
Для регистров, где нет `Ref_Key`, нужно собирать составной ключ, например:
* `source_entity + Recorder + Recorder_Type + LineNumber + Period`
или аналог.
Иначе у вас 358 записей с `source_id=unknown`, и это разрушает стабильное переиспользование сущности при повторных загрузках.
## Приоритеты ремонта по очереди
### Приоритет 1 — `Recorder`, `Ref`, `СчетФактура`, `Поставщик_Key`, `Покупатель_Key`
Это даст самый большой выигрыш по semantic coverage, потому что именно эти поля сейчас массово ломают регистры, журналы и ключевые документы.
### Приоритет 2 — `Ответственный_Key`, `Валюта_Key`, `ВалютаДокумента_Key`
Это быстро снижает долю `Unknown` в журналах и платежных документах, плюс улучшает explainability по “кто оформлял / в какой валюте”.
### Приоритет 3 — `СтатьяДвиженияДенежныхСредств_Key`, `ФизЛицо_Key`, `Склад_Key`, `ПодразделениеДт_Key`
Это уже следующий слой качества, особенно для операционных и зарплатных/кассовых сценариев.
## Что это даст benchmark’у
Если вы сделаете только этот ontology repair, без переписывания всего остального, я ожидаю:
* существенное падение `unknown_relations`;
* рост semantic coverage заметно выше текущих 61%;
* лучшую связность сценариев `document ↔ register ↔ journal`;
* меньше ошибочных ответов по cross-entity вопросам;
* меньше ложной уверенности в store-only ответах, потому что router будет видеть более богатую и корректную семантику.
Особенно выиграют вопросы вида:
* связать документ и проводки,
* связать контрагента, договор и документ,
* объяснить происхождение записи НДС,
* пройти от банковской выписки к первичному документу.
## Практический план, что делать прямо сейчас
Я бы формализовал следующий этап так:
**Шаг 1. Переписать mapper architecture**
* ввести `entity_type_resolver`
* ввести `relation_resolver`
* ввести `null_guid_filter`
* ввести `composite_source_id_builder` для register records.
**Шаг 2. Сделать словарь field semantics**
Отдельная таблица:
* `source_entity_pattern`
* `field_name`
* `optional_type_field`
* `target_class`
* `relation_name`
* `allow_zero_guid`
* `priority`.
Это даст переносимость и управляемость.
**Шаг 3. Расширить canonical classes**
Хотя бы теми классами, которые сейчас массово падают в `Unknown`.
**Шаг 4. Переингестить июнь 2020**
После изменения rules — заново посадить slice и пересчитать:
* unknown_relations,
* semantic_coverage_pct,
* relation_types_total,
* top problematic fields.
**Шаг 5. Только потом — дочищать router**
Потому что router сейчас частично страдает вторично: он опирается на недотипизированный граф. Сначала надо дать ему нормальную семаническую подложку.
## Мой жёсткий вывод
Сейчас у вас не “плохая онтология”, а **слишком примитивный canonical mapper для домена уровня 1С-бухгалтерии**. Он годится как стартовый универсальный мост, но не как слой, который должен объяснять бухгалтерскую причинность. Проблема не в snapshot’е июня 2020 — он как раз полезен и достаточно показателен. Проблема в том, что mapping-слой ещё не различает:
* тип ссылки,
* бизнес-роль ссылки,
* typed reference,
* пустую ссылку,
* и специальные 1С-паттерны вроде `Recorder/Ref/СчетФактура`.
Следующим сообщением я могу собрать тебе уже **прямо готовое ТЗ на ремонт ontology/mapping-слоя**: с таблицей новых canonical classes, таблицей relation types и таблицей правил для `Recorder`, `Ref`, `Поставщик`, `Покупатель`, `Ответственный`, `Валюта`, `Склад`, `СтатьяДДС`.