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