18 KiB
Да, теперь уже видно не просто то, что “онтология просажена”, а почему именно она просажена и как именно её чинить.
Главный вывод после догрузки: у вас ломается не “граф вообще”, а семаническая нормализация ссылок. Текущий 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_БанковскиеВыписки есть:
RefRef_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/ResponsiblePersonCurrencyWarehouseCashflowArticleDepartmentIndividualItem/NomenclatureBankAccountTaxRegisterRecordили более общийRegisterRecordInvoiceDocument/FacturaDocumentкак подтип документа, если хотите потом делать объяснения по НДС аккуратнее.
Без этого вы можете переписать relation rules хоть десять раз, но часть полей всё равно некуда будет положить.
B. Уходить от одного relation reference
Нужен не один reference, а словарь осмысленных relations. Минимальный стартовый набор я бы делал такой:
Документы / журналы
journal_refers_to_documentdocument_belongs_to_organizationdocument_has_counterpartydocument_has_contractdocument_has_currencydocument_has_warehousedocument_has_responsibledocument_has_cashflow_articledocument_has_bank_account
Регистры
register_recorded_by_documentregister_relates_to_supplierregister_relates_to_buyerregister_relates_to_invoiceregister_relates_to_vat_accountregister_relates_to_contractregister_relates_to_organization
Финансовые документы
payment_relates_to_counterpartypayment_relates_to_bank_accountpayment_relates_to_cashflow_articlepayment_relates_to_individualpayment_relates_to_department
Строки табличных частей
document_line_has_itemdocument_line_has_accountdocument_line_has_vat_accountdocument_line_has_expense_accountdocument_line_has_income_account
C. Менять сам принцип типизации
Не по имени поля alone, а по комбинации:
source_entitysource_field*_Typeрядом- наличие
navigationLinkUrl - контекст набора полей вокруг записи.
Именно это даст переносимость между разными 1С-контурами, а не ручную подгонку под июнь 2020, чего у вас как раз требует ТЗ.
Как я бы правил правила маппинга
Правило 1. *_Type имеет приоритет над эвристикой имени
Если есть:
Recorder_TypeRef_TypeСчетФактура_TypeДокументОплаты_Type- и т.п., то target_entity нужно определять не по имени поля, а по значению type. Например:
StandardODATA.Document_*→DocumentStandardODATA.Catalog_Контрагенты→CounterpartyStandardODATA.Catalog_Склады→WarehouseStandardODATA.Catalog_Валюты→CurrencyStandardODATA.Catalog_ФизическиеЛица→IndividualStandardODATA.Catalog_СтатьиДвиженияДенежныхСредств→CashflowArticle. Это автоматически лечит большую частьRecorder,Refи typed-полей.
Правило 2. Для конкретных полей — словарь приоритетных semantic mappings
Нужен явный field dictionary, например:
Recorder→ relationregister_recorded_by_document, targetDocumentRefвDocumentJournal_*→ relationjournal_refers_to_document, targetDocumentПоставщик_Key→ relation*_relates_to_supplier, targetCounterpartyПокупатель_Key→ relation*_relates_to_buyer, targetCounterpartyОтветственный_Key→ relation*_has_responsible, targetResponsiblePersonВалюта_Key/ВалютаДокумента_Key→ relation*_has_currency, targetCurrencyСклад_Key→ relation*_has_warehouse, targetWarehouseСтатьяДвиженияДенежныхСредств_Key→ relation*_has_cashflow_article, targetCashflowArticleФизЛицо_Key→ relation*_relates_to_individual, targetIndividualБанковскийСчет_Key/СчетОрганизации_Key→ targetBankAccount.
Правило 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_patternfield_nameoptional_type_fieldtarget_classrelation_nameallow_zero_guidpriority. Это даст переносимость и управляемость.
Шаг 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, Поставщик, Покупатель, Ответственный, Валюта, Склад, СтатьяДДС.