290 lines
18 KiB
Markdown
290 lines
18 KiB
Markdown
Да, теперь уже видно не просто то, что “онтология просажена”, а **почему именно она просажена и как именно её чинить**.
|
||
|
||
Главный вывод после догрузки: у вас ломается не “граф вообще”, а **семаническая нормализация ссылок**. Текущий 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`, `Поставщик`, `Покупатель`, `Ответственный`, `Валюта`, `Склад`, `СтатьяДДС`.
|