Compare commits

...

50 Commits

Author SHA1 Message Date
dctouch 04244ff3d7 Open-World: добавить годовую динамику в бизнес-обзор 2026-05-04 11:47:31 +03:00
dctouch 027e9b373e Open-World: добавить концентрацию поставщиков в бизнес-обзор 2026-05-04 11:36:33 +03:00
dctouch cd22164f33 Open-World: добавить debt staleness risk proxy в бизнес-обзор 2026-05-04 11:08:20 +03:00
dctouch 34e7a135ee Open-World: уточнить незакрытые проверки бизнес-обзора 2026-05-04 10:50:18 +03:00
dctouch 3a28709ce4 Open-World: добавить staleness risk proxy в бизнес-обзор 2026-05-04 10:43:10 +03:00
dctouch bf3ae110ef Open-World: добавить sales-to-stock proxy в бизнес-обзор 2026-05-04 10:22:08 +03:00
dctouch 062655eca0 Open-World: добавить торговый margin proxy в бизнес-обзор 2026-05-04 10:07:26 +03:00
dctouch 822baedcba Open-World: усилить аналитический синтез бизнес-обзора 2026-05-04 08:59:33 +03:00
dctouch 9b26bd05ef Open-World: добавить возрастной сигнал открытых расчетов 2026-05-04 08:52:43 +03:00
dctouch 7294eca381 Open-World: включить маржинальность выбранной номенклатуры 2026-05-04 08:40:27 +03:00
dctouch edab736a6d Open-World: расширить бизнес-обзор факт-семействами 2026-05-04 08:20:46 +03:00
dctouch e65b7fdaed Оформить business_overview как маршрутный контракт Open-World Breadth 2026-05-03 21:30:45 +03:00
dctouch 284b201912 Запустить Open-World Breadth через бизнес-обзор компании 2026-05-02 00:09:28 +03:00
dctouch 6df2018086 Актуализировать статусный канон Post-F и Planner Autonomy 2026-05-01 23:41:55 +03:00
dctouch bf5967df61 Задокументировать закрытие planner autonomy и phase83 evidence 2026-05-01 22:39:23 +03:00
dctouch 491bb430dc Сохранить phase83 semantic specs и autorun canary 2026-05-01 22:39:02 +03:00
dctouch 551f9c5673 Добавить gate готовности live MCP перед прогонами 2026-05-01 22:38:44 +03:00
dctouch da3a148918 Очеловечить MCP ответы и broad-eval recap 2026-05-01 22:38:23 +03:00
dctouch 4c00d8c854 Согласовать MCP planner с value-flow цепочками 2026-05-01 22:38:11 +03:00
dctouch 924f6fb0ea Укрепить exact value-flow аналитику адресного контура 2026-05-01 22:37:57 +03:00
dctouch 472d982486 Planner Autonomy: скрыть внутренние MCP-ошибки в checked-source ответах 2026-05-01 17:46:54 +03:00
dctouch f6846206ad Planner Autonomy: собрать mixed AGENT-прогон для catalog-alignment 2026-05-01 16:46:36 +03:00
dctouch e13068cf4d Planner Autonomy: закрепить catalog-alignment в phase32 и AGENT-каталоге 2026-05-01 16:39:59 +03:00
dctouch c69a91342f Planner Autonomy: закрепить catalog-alignment в phase66 spec 2026-05-01 16:27:39 +03:00
dctouch fe12967e2d Planner Autonomy: проверять catalog-alignment в truth harness 2026-05-01 16:25:22 +03:00
dctouch e58a9664e0 Planner Autonomy: добавить invariant catalog-alignment 2026-05-01 16:21:10 +03:00
dctouch 91529d897d Planner Autonomy: предупреждать о divergence catalog-alignment 2026-05-01 15:42:00 +03:00
dctouch a63742f0d6 Planner Autonomy: вывести catalog-alignment в replay artifacts 2026-05-01 15:38:06 +03:00
dctouch 8b5104a2c6 Planner Autonomy: добавить статус catalog-alignment 2026-05-01 15:33:30 +03:00
dctouch 755e2521e8 Planner Autonomy: добавить reason-коды catalog-alignment 2026-05-01 15:28:29 +03:00
dctouch 67f5e908c9 Planner Autonomy: закрепить alignment guard для catalog chains 2026-05-01 14:47:19 +03:00
dctouch 417b51096e Planner Autonomy: добавить verdict выравнивания catalog chain 2026-05-01 14:43:05 +03:00
dctouch ea96df9ca1 Planner Autonomy: выбрать comparison chain для явного bidirectional graph 2026-05-01 14:20:24 +03:00
dctouch 1c1383b8b5 Planner Autonomy: протащить catalog chain matches в runtime debug 2026-05-01 14:15:55 +03:00
dctouch 52a974d4d7 Planner Autonomy: вынести catalog chain matches в контракт 2026-05-01 14:11:52 +03:00
dctouch 4dcffef7d6 Planner Autonomy: ранжировать catalog chain templates 2026-05-01 14:02:55 +03:00
dctouch ccfa9283e9 Planner Autonomy: выводить lane из metadata surface 2026-05-01 13:57:40 +03:00
dctouch a4db26a76e Planner Autonomy: связать inventory templates с exact runtime 2026-05-01 13:31:41 +03:00
dctouch 3f7caae668 Planner Autonomy: закрепить boundary inventory templates 2026-05-01 13:17:09 +03:00
dctouch 9243642400 Planner Autonomy: поднять inventory routes в MCP catalog 2026-05-01 13:08:21 +03:00
dctouch 6bfbd5cb20 Runtime: сохранить хвост MCP proxy после Planner Autonomy 2026-05-01 12:54:31 +03:00
dctouch c7c85e9b42 Planner Autonomy: закрепить bounded inference и semantic bridges 2026-05-01 12:53:21 +03:00
dctouch 3634404b1c Runtime: обновить хвост MCP proxy 2026-05-01 12:08:02 +03:00
dctouch f76ea72b8d Planner Autonomy: закрепить metadata lane scoring 2026-05-01 12:05:47 +03:00
dctouch b333d67665 Planner Autonomy: разрешить value-flow candidate поверх exact reply 2026-05-01 11:50:34 +03:00
dctouch 0fe9d50066 Runtime: обновить live MCP log tail 2026-05-01 11:38:37 +03:00
dctouch 8c9cef7aac Runtime: сохранить хвосты open-world MCP прогонов 2026-05-01 11:38:11 +03:00
dctouch 499d0882b3 Архитектура: синхронизировать open-world breadth и Planner Autonomy 2026-05-01 11:38:01 +03:00
dctouch 5cd4d459fe Planner Autonomy: вынести MCP chain templates в route fabric 2026-05-01 11:37:50 +03:00
dctouch b12f370784 Inventory breadth: закрепить stock provenance и sale-trace контуры 2026-05-01 11:37:40 +03:00
151 changed files with 131430 additions and 955 deletions

1
.gitignore vendored
View File

@ -29,3 +29,4 @@ graphify-out/
# domain-case loop artifacts
artifacts/domain_runs/*
!artifacts/domain_runs/.gitkeep
artifacts/runtime_logs/*.log

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
> llm-normalizer-backend@0.1.0 start
> node dist/server.js
{"timestamp":"2026-04-30T21:03:28.150Z","level":"info","service":"llm_normalizer_backend","message":"Backend started on http://localhost:8787"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
> llm-normalizer-backend@0.1.0 start
> node dist/server.js
{"timestamp":"2026-05-01T08:59:27.164Z","level":"info","service":"llm_normalizer_backend","message":"Backend started on http://localhost:8787"}
{"timestamp":"2026-05-01T08:59:58.391Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-ZXoLBdUbUs","eventType":"assistant_message_address","details":{"session_id":"asst-ZXoLBdUbUs","message_id":"msg-a_1Kv1yXGd","user_message":"какие объекты 1С есть по НДС?","effective_address_user_message":"какие объекты 1С есть по НДС?","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"BQeRoV4QTv7ztU","address_llm_predecompose_reason":"normalized_fragment_rejected_semantic_guard","address_fallback_rule_hit":null,"address_sanitized_user_message":"какие объекты 1с есть по ндс?","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"new_topic","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":[],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"unknown","resolver_confidence":"low","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: По метаданным 1С найдены конкурирующие schema-поверхности; перед следующим шагом нужно удержать неоднозначность явно.\n\nЧто подтверждено:\n- Confirmed 1C metadata surface for НДС: 22 rows and 22 matching objects\n- Доступные типы metadata-объектов: Документ, РегистрНакопления, Справочник.\n- Metadata surface family scores: document=10, movement=11, catalog=1\n- Подтвержденная metadata-поверхность 1С по области \"НДС\": 22 строк metadata-ответа. Типы объектов: Документ, РегистрНакопления, Справочник. Найденные объекты: Документ.ВосстановлениеНДС, Документ.ВосстановлениеНДСПоОбъектамНедвижимости, Документ.НачислениеНДСпоСМРхозспособом, Документ.ОтражениеНачисленияНДС, Документ.ОтражениеНДСКВычету, Документ.ПодтверждениеНулевойСтавкиНДС, Документ.ПоясненияКДекларацииПоНДС, Документ.РаспределениеНДСКосвенныхРасходов.\n\nЧто можно сказать только как вывод:\n- По подтвержденной metadata-поверхности видно несколько конкурирующих family: Документ, РегистрНакопления, Справочник. Следующий data-lane пока нельзя выбрать без явного сужения.\n\nЧто не подтверждено:\n- Точная downstream metadata-поверхность пока неоднозначна между family: Документ, РегистрНакопления, Справочник.\n\nСледующий шаг: Следующим шагом лучше сузить surface до одного семейства: Документ, РегистрНакопления, Справочник.","reply_type":"partial_coverage","trace_id":"address-F1RMGB6aTU"}}
{"timestamp":"2026-05-01T09:00:12.204Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-ZXoLBdUbUs","eventType":"assistant_message_address","details":{"session_id":"asst-ZXoLBdUbUs","message_id":"msg-Y5DO8_QznP","user_message":"давай дальше","effective_address_user_message":"давай дальше","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"rjOB-Lm1LUjYk5","address_llm_predecompose_reason":"normalized_fragment_rejected_semantic_guard","address_fallback_rule_hit":null,"address_sanitized_user_message":"давай дальше","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"followup_context_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":false,"address_semantic_contract_quality":"low","address_semantic_apply_canonical_recommended":false,"address_semantic_reason_codes":["unsupported_low_confidence_contract"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"unknown","resolver_confidence":"low","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя.\n\nСледующий шаг: Уточните, в какой контур идти дальше: по документам или по движениям/регистрам.","reply_type":"partial_coverage","trace_id":"address-vcsHzk9GH9"}}
{"timestamp":"2026-05-01T09:00:27.573Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-ZXoLBdUbUs","eventType":"assistant_message_address","details":{"session_id":"asst-ZXoLBdUbUs","message_id":"msg-tgzKwSHEIn","user_message":"по движениям по ООО Альтернатива Плюс","effective_address_user_message":"по движениям по ООО Альтернатива Плюс","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"F5aaIEjuOxdzhJ","address_llm_predecompose_reason":"followup_raw_message_preferred_over_llm_rewrite","address_fallback_rule_hit":null,"address_sanitized_user_message":"по движениям по ооо альтернатива плюс","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"followup_context_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":["unsupported_low_confidence_contract","rewrite_without_structured_gain"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20,"organization":"ООО Альтернатива Плюс"},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"organization","resolver_confidence":"medium","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: Могу идти дальше по движениям/регистрам, но для запуска поиска в 1С нужен проверяемый период.\n\nСледующий шаг: Уточните период, и я продолжу поиск по движениям/регистрам в 1С.","reply_type":"partial_coverage","trace_id":"address-jsE3nokVwx"}}
{"timestamp":"2026-05-01T09:00:54.667Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-ZXoLBdUbUs","eventType":"assistant_message_address","details":{"session_id":"asst-ZXoLBdUbUs","message_id":"msg-PANGADfyJi","user_message":"за 2020 год","effective_address_user_message":"указать период с 01.01.2020 по 31.12.2020","address_followup_context_applied":true,"address_followup_context_previous_intent":"bank_operations_by_counterparty","address_followup_context_previous_anchor":"ООО Альтернатива Плюс","address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":true,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"8kZf7zI67CQQ9T","address_llm_predecompose_reason":"normalized_fragment_applied","address_fallback_rule_hit":null,"address_sanitized_user_message":"за 2020 год","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":"bank_operations_by_counterparty","address_retry_attempted":true,"address_retry_reason":"limited_result_retry_with_raw_message","address_retry_initial_limited_category":"missing_anchor","address_retry_result_category":"unsupported","address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"year","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":["rewrite_without_structured_gain"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"bank_operations_by_counterparty","extracted_filters":{"sort":"period_desc","limit":20,"period_from":"2020-01-01","period_to":"2020-12-31","organization":"ООО Альтернатива Плюс"},"selected_recipe":"address_bank_operations_by_counterparty_v1","mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"organization","resolver_confidence":"medium","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"LIVE_QUERYABLE_WITH_LIMITS","limited_reason_category":"missing_anchor","response_type":"LIMITED_WITH_REASON","limitations":["missing_required_filters"],"assistant_reply":"Коротко: По движениям за 2020 полный срез не подтвержден; пока есть только ограниченная граница проверенного окна 1С.\n\nЧто можно сказать только как вывод:\n- По движениям за 2020 удалось проверить только ограниченный срез 1С; подтвержденных строк движений этим поиском не найдено.\n\nЧто не подтверждено:\n- Полный исторический срез движений вне периода 2020 этим поиском не подтвержден.","reply_type":"partial_coverage","trace_id":"address-Ug7LcguncB"}}
{"timestamp":"2026-05-01T09:01:17.441Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-nfvMpZKurt","eventType":"assistant_message_address","details":{"session_id":"asst-nfvMpZKurt","message_id":"msg-ErIPAIflOR","user_message":"какие объекты 1С есть по НДС?","effective_address_user_message":"какие объекты 1С есть по НДС?","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"U9CKnK3LXBq0tC","address_llm_predecompose_reason":"normalized_fragment_rejected_semantic_guard","address_fallback_rule_hit":null,"address_sanitized_user_message":"какие объекты 1с есть по ндс?","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"new_topic","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":[],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"unknown","resolver_confidence":"low","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: По метаданным 1С найдены конкурирующие schema-поверхности; перед следующим шагом нужно удержать неоднозначность явно.\n\nЧто подтверждено:\n- Confirmed 1C metadata surface for НДС: 22 rows and 22 matching objects\n- Доступные типы metadata-объектов: Документ, РегистрНакопления, Справочник.\n- Metadata surface family scores: document=10, movement=11, catalog=1\n- Подтвержденная metadata-поверхность 1С по области \"НДС\": 22 строк metadata-ответа. Типы объектов: Документ, РегистрНакопления, Справочник. Найденные объекты: Документ.ВосстановлениеНДС, Документ.ВосстановлениеНДСПоОбъектамНедвижимости, Документ.НачислениеНДСпоСМРхозспособом, Документ.ОтражениеНачисленияНДС, Документ.ОтражениеНДСКВычету, Документ.ПодтверждениеНулевойСтавкиНДС, Документ.ПоясненияКДекларацииПоНДС, Документ.РаспределениеНДСКосвенныхРасходов.\n\nЧто можно сказать только как вывод:\n- По подтвержденной metadata-поверхности видно несколько конкурирующих family: Документ, РегистрНакопления, Справочник. Следующий data-lane пока нельзя выбрать без явного сужения.\n\nЧто не подтверждено:\n- Точная downstream metadata-поверхность пока неоднозначна между family: Документ, РегистрНакопления, Справочник.\n\nСледующий шаг: Следующим шагом лучше сузить surface до одного семейства: Документ, РегистрНакопления, Справочник.","reply_type":"partial_coverage","trace_id":"address-zVbKlkm0HW"}}
{"timestamp":"2026-05-01T09:01:31.283Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-nfvMpZKurt","eventType":"assistant_message_address","details":{"session_id":"asst-nfvMpZKurt","message_id":"msg-cMYPGdV_Qh","user_message":"давай дальше","effective_address_user_message":"давай дальше","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"jX1CLpN4FvRmw-","address_llm_predecompose_reason":"normalized_fragment_rejected_semantic_guard","address_fallback_rule_hit":null,"address_sanitized_user_message":"давай дальше","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"followup_context_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":false,"address_semantic_contract_quality":"low","address_semantic_apply_canonical_recommended":false,"address_semantic_reason_codes":["unsupported_low_confidence_contract"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"unknown","resolver_confidence":"low","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя.\n\nСледующий шаг: Уточните, в какой контур идти дальше: по документам или по движениям/регистрам.","reply_type":"partial_coverage","trace_id":"address-3ZS8fFM7Pa"}}
{"timestamp":"2026-05-01T09:02:04.338Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-nfvMpZKurt","eventType":"assistant_message_address","details":{"session_id":"asst-nfvMpZKurt","message_id":"msg-qn8q3ZtHpH","user_message":"по документам по ООО Альтернатива Плюс","effective_address_user_message":"анализ документов для организации ООО Альтернатива Плюс","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":true,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"0q3O8selScj8d8","address_llm_predecompose_reason":"normalized_fragment_applied","address_fallback_rule_hit":null,"address_sanitized_user_message":"по документам по ооо альтернатива плюс","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":"list_documents_by_counterparty","address_retry_attempted":true,"address_retry_reason":"limited_result_retry_with_raw_message","address_retry_initial_limited_category":"missing_anchor","address_retry_result_category":"missing_anchor","address_llm_predecompose_contract_intent":"list_documents_by_counterparty","address_llm_predecompose_contract_aggregation_profile":"list_lookup","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":true,"address_semantic_contract_quality":"high","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":[],"detected_mode":"address_query","query_shape":"DOCUMENT_LIST","detected_intent":"list_documents_by_counterparty","extracted_filters":{"sort":"period_desc","limit":20,"counterparty":"ООО Альтернатива Плюс","organization":"ООО Альтернатива Плюс"},"selected_recipe":"address_documents_by_counterparty_v1","mcp_call_status_legacy":"materialized_but_not_matched","account_scope_mode":"preferred","account_scope_fallback_applied":false,"anchor_type":"counterparty","resolver_confidence":"high","match_failure_stage":"materialized_but_not_anchor_matched","match_failure_reason":"counterparty_anchor_not_matched_in_materialized_rows","mcp_call_status":"materialized_but_not_anchor_matched","rows_fetched":1000,"raw_rows_received":1000,"rows_after_account_scope":124,"rows_after_recipe_filter":0,"rows_materialized":124,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"LIVE_QUERYABLE_WITH_LIMITS","limited_reason_category":"missing_anchor","response_type":"LIMITED_WITH_REASON","limitations":["counterparty_anchor_not_matched_after_materialization"],"assistant_reply":"Коротко: Могу идти дальше по документам, но для запуска поиска в 1С нужен проверяемый период.\n\nСледующий шаг: Уточните период, и я продолжу поиск по документам в 1С.","reply_type":"partial_coverage","trace_id":"address-OnRN2zI5gQ"}}
{"timestamp":"2026-05-01T09:02:32.673Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-nfvMpZKurt","eventType":"assistant_message_address","details":{"session_id":"asst-nfvMpZKurt","message_id":"msg-li3zjTPAg5","user_message":"за 2020 год","effective_address_user_message":"указать период с 01.01.2020 по 31.12.2020","address_followup_context_applied":true,"address_followup_context_previous_intent":"list_documents_by_counterparty","address_followup_context_previous_anchor":"ООО \"Альтернатива Плюс\"","address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":true,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"EeHgraM9uHGTi4","address_llm_predecompose_reason":"normalized_fragment_applied","address_fallback_rule_hit":null,"address_sanitized_user_message":"за 2020 год","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":"list_documents_by_counterparty","address_retry_attempted":true,"address_retry_reason":"limited_result_retry_with_raw_message","address_retry_initial_limited_category":"missing_anchor","address_retry_result_category":"unsupported","address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"year","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":["rewrite_without_structured_gain"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"list_documents_by_counterparty","extracted_filters":{"sort":"period_desc","limit":20,"period_from":"2020-01-01","period_to":"2020-12-31","organization":"ООО Альтернатива Плюс"},"selected_recipe":"address_documents_by_counterparty_v1","mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"organization","resolver_confidence":"medium","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"LIVE_QUERYABLE_WITH_LIMITS","limited_reason_category":"missing_anchor","response_type":"LIMITED_WITH_REASON","limitations":["missing_required_filters"],"assistant_reply":"Коротко: По документам за 2020 в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.\n\nЧто подтверждено:\n- В 1С найдены строки документов за 2020.\n\nЧто можно сказать только как вывод:\n- Срез документов за 2020 ограничен только подтвержденными строками документов, найденными этим поиском.\n\nЧто не подтверждено:\n- Полный исторический срез документов вне периода 2020 этим поиском не подтвержден.","reply_type":"partial_coverage","trace_id":"address-7sOoNyArlx"}}

View File

@ -0,0 +1,13 @@
> llm-normalizer-backend@0.1.0 start
> node dist/server.js
{"timestamp":"2026-05-01T08:53:57.188Z","level":"info","service":"llm_normalizer_backend","message":"Backend started on http://localhost:8787"}
{"timestamp":"2026-05-01T08:54:30.030Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-4Kr5lLnucH","eventType":"assistant_message_address","details":{"session_id":"asst-4Kr5lLnucH","message_id":"msg-opH1xZkPkA","user_message":"какие объекты 1С есть по НДС?","effective_address_user_message":"какие объекты 1С есть по НДС?","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"eYdALyA0V2zLMQ","address_llm_predecompose_reason":"normalized_fragment_rejected_semantic_guard","address_fallback_rule_hit":null,"address_sanitized_user_message":"какие объекты 1с есть по ндс?","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"new_topic","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":[],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"unknown","resolver_confidence":"low","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: По метаданным 1С найдены конкурирующие schema-поверхности; перед следующим шагом нужно удержать неоднозначность явно.\n\nЧто подтверждено:\n- Confirmed 1C metadata surface for НДС: 22 rows and 22 matching objects\n- Доступные типы metadata-объектов: Документ, РегистрНакопления, Справочник.\n- Metadata surface family scores: document=10, movement=11, catalog=1\n- Подтвержденная metadata-поверхность 1С по области \"НДС\": 22 строк metadata-ответа. Типы объектов: Документ, РегистрНакопления, Справочник. Найденные объекты: Документ.ВосстановлениеНДС, Документ.ВосстановлениеНДСПоОбъектамНедвижимости, Документ.НачислениеНДСпоСМРхозспособом, Документ.ОтражениеНачисленияНДС, Документ.ОтражениеНДСКВычету, Документ.ПодтверждениеНулевойСтавкиНДС, Документ.ПоясненияКДекларацииПоНДС, Документ.РаспределениеНДСКосвенныхРасходов.\n\nЧто можно сказать только как вывод:\n- По подтвержденной metadata-поверхности видно несколько конкурирующих family: Документ, РегистрНакопления, Справочник. Следующий data-lane пока нельзя выбрать без явного сужения.\n\nЧто не подтверждено:\n- Точная downstream metadata-поверхность пока неоднозначна между family: Документ, РегистрНакопления, Справочник.\n\nСледующий шаг: Следующим шагом лучше сузить surface до одного семейства: Документ, РегистрНакопления, Справочник.","reply_type":"partial_coverage","trace_id":"address-RZriX0g_PL"}}
{"timestamp":"2026-05-01T08:54:44.002Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-4Kr5lLnucH","eventType":"assistant_message_address","details":{"session_id":"asst-4Kr5lLnucH","message_id":"msg-fbXlw23ObD","user_message":"давай дальше","effective_address_user_message":"давай дальше","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"2M3EeviVjiIPZL","address_llm_predecompose_reason":"normalized_fragment_rejected_semantic_guard","address_fallback_rule_hit":null,"address_sanitized_user_message":"давай дальше","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"followup_context_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":false,"address_semantic_contract_quality":"low","address_semantic_apply_canonical_recommended":false,"address_semantic_reason_codes":["unsupported_low_confidence_contract"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"unknown","resolver_confidence":"low","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя.\n\nСледующий шаг: Уточните, в какой контур идти дальше: по документам или по движениям/регистрам.","reply_type":"partial_coverage","trace_id":"address-BKiuVCQ2c6"}}
{"timestamp":"2026-05-01T08:54:59.595Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-4Kr5lLnucH","eventType":"assistant_message_address","details":{"session_id":"asst-4Kr5lLnucH","message_id":"msg-cjIVzCHu9R","user_message":"по движениям по ООО Альтернатива Плюс","effective_address_user_message":"по движениям по ООО Альтернатива Плюс","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"xY9lxZGu2u9MeL","address_llm_predecompose_reason":"followup_raw_message_preferred_over_llm_rewrite","address_fallback_rule_hit":null,"address_sanitized_user_message":"по движениям по ооо альтернатива плюс","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"followup_context_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":["unsupported_low_confidence_contract","rewrite_without_structured_gain"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20,"organization":"ООО Альтернатива Плюс"},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"organization","resolver_confidence":"medium","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: Могу идти дальше по движениям/регистрам, но для запуска поиска в 1С нужен проверяемый период.\n\nСледующий шаг: Уточните период, и я продолжу поиск по движениям/регистрам в 1С.","reply_type":"partial_coverage","trace_id":"address-owL-_qb_Ru"}}
{"timestamp":"2026-05-01T08:55:25.395Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-4Kr5lLnucH","eventType":"assistant_message_address","details":{"session_id":"asst-4Kr5lLnucH","message_id":"msg-gaNXsS3AC7","user_message":"за 2020 год","effective_address_user_message":"указать период с 01.01.2020 по 31.12.2020","address_followup_context_applied":true,"address_followup_context_previous_intent":"bank_operations_by_counterparty","address_followup_context_previous_anchor":"ООО Альтернатива Плюс","address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":true,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"jG-hen7Z0byPJz","address_llm_predecompose_reason":"normalized_fragment_applied","address_fallback_rule_hit":null,"address_sanitized_user_message":"за 2020 год","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":"bank_operations_by_counterparty","address_retry_attempted":true,"address_retry_reason":"limited_result_retry_with_raw_message","address_retry_initial_limited_category":"missing_anchor","address_retry_result_category":"unsupported","address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"year","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":["rewrite_without_structured_gain"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"bank_operations_by_counterparty","extracted_filters":{"sort":"period_desc","limit":20,"period_from":"2020-01-01","period_to":"2020-12-31","organization":"ООО Альтернатива Плюс"},"selected_recipe":"address_bank_operations_by_counterparty_v1","mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"organization","resolver_confidence":"medium","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"LIVE_QUERYABLE_WITH_LIMITS","limited_reason_category":"missing_anchor","response_type":"LIMITED_WITH_REASON","limitations":["missing_required_filters"],"assistant_reply":"Коротко: По движениям за 2020 полный срез не подтвержден; пока есть только ограниченная граница проверенного окна 1С.\n\nЧто можно сказать только как вывод:\n- По движениям за 2020 удалось проверить только ограниченный срез 1С; подтвержденных строк движений этим поиском не найдено.\n\nЧто не подтверждено:\n- Полный исторический срез движений вне периода 2020 этим поиском не подтвержден.","reply_type":"partial_coverage","trace_id":"address-B5080baQKt"}}
{"timestamp":"2026-05-01T08:55:47.305Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-Q8tEK6w3mU","eventType":"assistant_message_address","details":{"session_id":"asst-Q8tEK6w3mU","message_id":"msg-jopQJaLF1p","user_message":"какие объекты 1С есть по НДС?","effective_address_user_message":"какие объекты 1С есть по НДС?","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"haGLcg1xFqy1d3","address_llm_predecompose_reason":"normalized_fragment_rejected_semantic_guard","address_fallback_rule_hit":null,"address_sanitized_user_message":"какие объекты 1с есть по ндс?","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"new_topic","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":[],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"unknown","resolver_confidence":"low","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: По метаданным 1С найдены конкурирующие schema-поверхности; перед следующим шагом нужно удержать неоднозначность явно.\n\nЧто подтверждено:\n- Confirmed 1C metadata surface for НДС: 22 rows and 22 matching objects\n- Доступные типы metadata-объектов: Документ, РегистрНакопления, Справочник.\n- Metadata surface family scores: document=10, movement=11, catalog=1\n- Подтвержденная metadata-поверхность 1С по области \"НДС\": 22 строк metadata-ответа. Типы объектов: Документ, РегистрНакопления, Справочник. Найденные объекты: Документ.ВосстановлениеНДС, Документ.ВосстановлениеНДСПоОбъектамНедвижимости, Документ.НачислениеНДСпоСМРхозспособом, Документ.ОтражениеНачисленияНДС, Документ.ОтражениеНДСКВычету, Документ.ПодтверждениеНулевойСтавкиНДС, Документ.ПоясненияКДекларацииПоНДС, Документ.РаспределениеНДСКосвенныхРасходов.\n\nЧто можно сказать только как вывод:\n- По подтвержденной metadata-поверхности видно несколько конкурирующих family: Документ, РегистрНакопления, Справочник. Следующий data-lane пока нельзя выбрать без явного сужения.\n\nЧто не подтверждено:\n- Точная downstream metadata-поверхность пока неоднозначна между family: Документ, РегистрНакопления, Справочник.\n\nСледующий шаг: Следующим шагом лучше сузить surface до одного семейства: Документ, РегистрНакопления, Справочник.","reply_type":"partial_coverage","trace_id":"address-EXXkM_wE7Z"}}
{"timestamp":"2026-05-01T08:56:00.958Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-Q8tEK6w3mU","eventType":"assistant_message_address","details":{"session_id":"asst-Q8tEK6w3mU","message_id":"msg-gd4-l7WSlX","user_message":"давай дальше","effective_address_user_message":"давай дальше","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":false,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"qvanhbl3YjYe8T","address_llm_predecompose_reason":"normalized_fragment_rejected_semantic_guard","address_fallback_rule_hit":null,"address_sanitized_user_message":"давай дальше","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"followup_context_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":null,"address_retry_attempted":false,"address_retry_reason":null,"address_retry_initial_limited_category":null,"address_retry_result_category":null,"address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":false,"address_semantic_contract_quality":"low","address_semantic_apply_canonical_recommended":false,"address_semantic_reason_codes":["unsupported_low_confidence_contract"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"unknown","extracted_filters":{"sort":"period_desc","limit":20},"selected_recipe":null,"mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"unknown","resolver_confidence":"low","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"DEEP_ONLY","limited_reason_category":"unsupported","response_type":"LIMITED_WITH_REASON","limitations":["intent_not_supported_in_v1"],"assistant_reply":"Коротко: По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя.\n\nСледующий шаг: Уточните, в какой контур идти дальше: по документам или по движениям/регистрам.","reply_type":"partial_coverage","trace_id":"address-e29Ul3F6xD"}}
{"timestamp":"2026-05-01T08:56:34.748Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-Q8tEK6w3mU","eventType":"assistant_message_address","details":{"session_id":"asst-Q8tEK6w3mU","message_id":"msg-dAeB4fBRML","user_message":"по документам по ООО Альтернатива Плюс","effective_address_user_message":"анализ документов для организации ООО Альтернатива Плюс","address_followup_context_applied":false,"address_followup_context_previous_intent":null,"address_followup_context_previous_anchor":null,"address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":true,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"Br9B5POwR1k2oj","address_llm_predecompose_reason":"normalized_fragment_applied","address_fallback_rule_hit":null,"address_sanitized_user_message":"по документам по ооо альтернатива плюс","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":"list_documents_by_counterparty","address_retry_attempted":true,"address_retry_reason":"limited_result_retry_with_raw_message","address_retry_initial_limited_category":"missing_anchor","address_retry_result_category":"missing_anchor","address_llm_predecompose_contract_intent":"list_documents_by_counterparty","address_llm_predecompose_contract_aggregation_profile":"list_lookup","address_llm_predecompose_contract_period_scope":"unspecified","address_semantic_contract_valid":true,"address_semantic_contract_quality":"high","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":[],"detected_mode":"address_query","query_shape":"DOCUMENT_LIST","detected_intent":"list_documents_by_counterparty","extracted_filters":{"sort":"period_desc","limit":20,"counterparty":"ООО Альтернатива Плюс","organization":"ООО Альтернатива Плюс"},"selected_recipe":"address_documents_by_counterparty_v1","mcp_call_status_legacy":"materialized_but_not_matched","account_scope_mode":"preferred","account_scope_fallback_applied":false,"anchor_type":"counterparty","resolver_confidence":"high","match_failure_stage":"materialized_but_not_anchor_matched","match_failure_reason":"counterparty_anchor_not_matched_in_materialized_rows","mcp_call_status":"materialized_but_not_anchor_matched","rows_fetched":1000,"raw_rows_received":1000,"rows_after_account_scope":124,"rows_after_recipe_filter":0,"rows_materialized":124,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"LIVE_QUERYABLE_WITH_LIMITS","limited_reason_category":"missing_anchor","response_type":"LIMITED_WITH_REASON","limitations":["counterparty_anchor_not_matched_after_materialization"],"assistant_reply":"Коротко: Могу идти дальше по документам, но для запуска поиска в 1С нужен проверяемый период.\n\nСледующий шаг: Уточните период, и я продолжу поиск по документам в 1С.","reply_type":"partial_coverage","trace_id":"address-muzW_iRg3h"}}
{"timestamp":"2026-05-01T08:57:02.373Z","level":"info","service":"assistant_loop","message":"assistant_message_processed","sessionId":"asst-Q8tEK6w3mU","eventType":"assistant_message_address","details":{"session_id":"asst-Q8tEK6w3mU","message_id":"msg-MdLvVQEkiC","user_message":"за 2020 год","effective_address_user_message":"указать период с 01.01.2020 по 31.12.2020","address_followup_context_applied":true,"address_followup_context_previous_intent":"list_documents_by_counterparty","address_followup_context_previous_anchor":"ООО \"Альтернатива Плюс\"","address_llm_predecompose_attempted":true,"address_llm_predecompose_applied":true,"address_llm_predecompose_provider":"local","address_llm_predecompose_trace_id":"p1qDgTf8P62yZC","address_llm_predecompose_reason":"normalized_fragment_applied","address_fallback_rule_hit":null,"address_sanitized_user_message":"за 2020 год","address_tool_gate_decision":"run_address_lane","address_tool_gate_reason":"address_mode_classifier_detected","address_dialog_continuation_decision":"continue_previous","address_dialog_continuation_target_intent":"list_documents_by_counterparty","address_retry_attempted":true,"address_retry_reason":"limited_result_retry_with_raw_message","address_retry_initial_limited_category":"missing_anchor","address_retry_result_category":"unsupported","address_llm_predecompose_contract_intent":"unknown","address_llm_predecompose_contract_aggregation_profile":"unknown","address_llm_predecompose_contract_period_scope":"year","address_semantic_contract_valid":true,"address_semantic_contract_quality":"medium","address_semantic_apply_canonical_recommended":true,"address_semantic_reason_codes":["rewrite_without_structured_gain"],"detected_mode":"address_query","query_shape":"UNKNOWN","detected_intent":"list_documents_by_counterparty","extracted_filters":{"sort":"period_desc","limit":20,"period_from":"2020-01-01","period_to":"2020-12-31","organization":"ООО Альтернатива Плюс"},"selected_recipe":"address_documents_by_counterparty_v1","mcp_call_status_legacy":"skipped","account_scope_mode":"strict","account_scope_fallback_applied":false,"anchor_type":"organization","resolver_confidence":"medium","match_failure_stage":"none","match_failure_reason":null,"mcp_call_status":"skipped","rows_fetched":0,"raw_rows_received":0,"rows_after_account_scope":0,"rows_after_recipe_filter":0,"rows_materialized":0,"rows_matched":0,"materialization_drop_reason":"none","account_token_raw":null,"account_token_normalized":null,"account_scope_fields_checked":["account_dt","account_kt","registrator","analytics"],"account_scope_match_strategy":"account_code_regex_plus_alias_map_v1","account_scope_drop_reason":"not_applicable","runtime_readiness":"LIVE_QUERYABLE_WITH_LIMITS","limited_reason_category":"missing_anchor","response_type":"LIMITED_WITH_REASON","limitations":["missing_required_filters"],"assistant_reply":"Коротко: По документам за 2020 в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.\n\nЧто подтверждено:\n- В 1С найдены строки документов за 2020.\n\nЧто можно сказать только как вывод:\n- Срез документов за 2020 ограничен только подтвержденными строками документов, найденными этим поиском.\n\nЧто не подтверждено:\n- Полный исторический срез документов вне периода 2020 этим поиском не подтвержден.","reply_type":"partial_coverage","trace_id":"address-NHKN9O_m91"}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,21 @@
=1 : ˆ¬ï "=1" ­¥ à á¯®§­ ­® ª ª ¨¬ï ª®¬ ­¤«¥â , ä㭪樨, ä ©«  áæ¥­ à¨ï ¨«¨ ¢ë¯®«­ï¥¬®© ¯à®£à ¬¬ë. <20>஢¥àì⥠¯à ¢¨«ì­®áâì ­ ¯¨á
 ­¨ï ¨¬¥­¨,   â ª¦¥ ­ «¨ç¨¥ ¨ ¯à ¢¨«ì­®áâì ¯ãâ¨, ¯®á«¥ 祣® ¯®¢â®à¨â¥ ¯®¯ëâªã.
áâப :1 §­ ª:1
+ ='1'; ='1'; npm.cmd start
+ ~~~~
+ CategoryInfo : ObjectNotFound: (=1:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
=1 : ˆ¬ï "=1" ­¥ à á¯®§­ ­® ª ª ¨¬ï ª®¬ ­¤«¥â , ä㭪樨, ä ©«  áæ¥­ à¨ï ¨«¨ ¢ë¯®«­ï¥¬®© ¯à®£à ¬¬ë. <20>஢¥àì⥠¯à ¢¨«ì­®áâì ­ ¯¨á
 ­¨ï ¨¬¥­¨,   â ª¦¥ ­ «¨ç¨¥ ¨ ¯à ¢¨«ì­®áâì ¯ãâ¨, ¯®á«¥ 祣® ¯®¢â®à¨â¥ ¯®¯ëâªã.
áâப :1 §­ ª:7
+ ='1'; ='1'; npm.cmd start
+ ~~~~
+ CategoryInfo : ObjectNotFound: (=1:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
npm notice
npm notice New major version of npm available! 10.9.3 -> 11.13.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.13.0
npm notice To update run: npm install -g npm@11.13.0
npm notice

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,83 @@
{
"schema_version": "mcp_live_readiness_check_v1",
"backend_url": "http://127.0.0.1:8787",
"proxy_url": "http://127.0.0.1:6003",
"channel": "default",
"backend_health_ok": true,
"proxy_health_ok": true,
"backend_health": {
"ok": true,
"service": "llm-normalizer-backend",
"status": "RUNNING",
"timezone": "Europe/Moscow",
"now": "2026-05-01T17:36:00.981Z"
},
"backend_health_error": null,
"backend_health_elapsed_seconds": 0.031,
"proxy_health": {
"status": "healthy",
"pending_commands": 0,
"pending_channels_count": 0,
"active_channels_count": 1,
"active_sessions_count": 0,
"polling_channels_count": 1,
"last_poll_at": "2026-05-01T17:36:00.174216+00:00",
"last_delivered_command_at": "2026-05-01T17:35:24.603913+00:00",
"mcp_endpoint": "/mcp",
"pending_commands_by_channel": {},
"active_sessions_by_channel": {},
"poll_activity_by_channel": {
"default": {
"poll_count": 1144,
"empty_poll_count": 1097,
"delivered_command_count": 47,
"last_poll_at": "2026-05-01T17:36:00.174216+00:00",
"last_empty_poll_at": "2026-05-01T17:36:00.174216+00:00",
"last_delivered_command_at": "2026-05-01T17:35:24.603913+00:00",
"last_poll_timeout": 0.0,
"last_delivered_tool": "get_metadata",
"last_delivered_command_id": "b048d102-5a31-4237-99dd-bf5912a76c12"
}
}
},
"proxy_health_error": null,
"proxy_health_elapsed_seconds": 0.016,
"live_probe": {
"kind": "get_metadata",
"ok": true,
"elapsed_seconds": 0.312,
"response": {
"success": true,
"data": "[45]{\"Тип\",\"Количество\"}:\n Подсистема,30\n ОбщийМодуль,242\n ПараметрСеанса,25\n Роль,20\n ОбщийРеквизит,0\n ПланОбмена,8\n КритерийОтбора,5\n ПодпискаНаСобытие,62\n РегламентноеЗадание,16\n Бот,0\n ФункциональнаяОпция,13\n ПараметрФункциональныхОпций,0\n ОпределяемыйТип,0\n ХранилищеНастроек,0\n ОбщаяКоманда,21\n ГруппаКоманд,4\n ОбщаяФорма,113\n ОбщийМакет,107\n ОбщаяКартинка,599\n WebСервис,0\n HTTPСервис,0\n WSСсылка,0\n WebSocketКлиент,0\n СервисИнтеграции,0\n ЭлементСтиля,32\n Стиль,1\n Язык,1\n Константа,95\n Справочник,116\n Документ,164\n ЖурналДокументов,21\n Перечисление,312\n Отчет,481\n Обработка,107\n ПланВидовХарактеристик,4\n ПланСчетов,1\n ПланВидовРасчета,1\n РегистрСведений,228\n РегистрНакопления,51\n РегистрБухгалтерии,1\n РегистрРасчета,0\n БизнесПроцесс,0\n Задача,0\n ВнешнийИсточникДанных,0\n ПакетыXDTO,51",
"error": null,
"configuration": {
"platform_version": "8.3.27.1936",
"infobase_name": "data_mcp",
"metadata": {
"Имя": "БухгалтерияПредприятия",
"Синоним": "Бухгалтерия предприятия, редакция 2.0",
"Комментарий": "",
"Поставщик": "Фирма \"1С\"",
"Версия": "2.0.67.20",
"ОсновнойЯзык": "Русский",
"ОсновнойРежимЗапуска": "Обычное приложение",
"РежимСовместимости": "Версия8_2_16",
"РежимУправленияБлокировкойДанных": "Управляемый",
"РежимСовместимостиИнтерфейса": "Версия8_2",
"РежимИспользованияМодальности": "Использовать",
"РежимАвтонумерацииОбъектов": "НеОсвобождатьАвтоматически",
"РежимИспользованияБлочногоХраненияДвоичныхДанных": "Не использовать",
"РежимИспользованияТабличныхПространствБазыДанных": "НеИспользовать",
"ИспользоватьОбычныеФормыВУправляемомПриложении": false,
"ИспользоватьУправляемыеФормыВОбычномПриложении": true
}
}
},
"error": null
},
"verdict": {
"status": "ready",
"reason": "backend, proxy, and direct read-only 1C probe returned successfully",
"ready_for_live_replay": true
}
}

View File

@ -0,0 +1,83 @@
{
"schema_version": "mcp_live_readiness_check_v1",
"backend_url": "http://127.0.0.1:8787",
"proxy_url": "http://127.0.0.1:6003",
"channel": "default",
"backend_health_ok": true,
"proxy_health_ok": true,
"backend_health": {
"ok": true,
"service": "llm-normalizer-backend",
"status": "RUNNING",
"timezone": "Europe/Moscow",
"now": "2026-05-01T17:35:24.362Z"
},
"backend_health_error": null,
"backend_health_elapsed_seconds": 0.016,
"proxy_health": {
"status": "healthy",
"pending_commands": 0,
"pending_channels_count": 0,
"active_channels_count": 1,
"active_sessions_count": 0,
"polling_channels_count": 1,
"last_poll_at": "2026-05-01T17:35:23.609654+00:00",
"last_delivered_command_at": "2026-05-01T17:24:17.324186+00:00",
"mcp_endpoint": "/mcp",
"pending_commands_by_channel": {},
"active_sessions_by_channel": {},
"poll_activity_by_channel": {
"default": {
"poll_count": 1108,
"empty_poll_count": 1062,
"delivered_command_count": 46,
"last_poll_at": "2026-05-01T17:35:23.609654+00:00",
"last_empty_poll_at": "2026-05-01T17:35:23.609654+00:00",
"last_delivered_command_at": "2026-05-01T17:24:17.324186+00:00",
"last_poll_timeout": 0.0,
"last_delivered_tool": "get_metadata",
"last_delivered_command_id": "00044119-97fc-4463-94dc-770ddee230fb"
}
}
},
"proxy_health_error": null,
"proxy_health_elapsed_seconds": 0.016,
"live_probe": {
"kind": "get_metadata",
"ok": true,
"elapsed_seconds": 0.312,
"response": {
"success": true,
"data": "[45]{\"Тип\",\"Количество\"}:\n Подсистема,30\n ОбщийМодуль,242\n ПараметрСеанса,25\n Роль,20\n ОбщийРеквизит,0\n ПланОбмена,8\n КритерийОтбора,5\n ПодпискаНаСобытие,62\n РегламентноеЗадание,16\n Бот,0\n ФункциональнаяОпция,13\n ПараметрФункциональныхОпций,0\n ОпределяемыйТип,0\n ХранилищеНастроек,0\n ОбщаяКоманда,21\n ГруппаКоманд,4\n ОбщаяФорма,113\n ОбщийМакет,107\n ОбщаяКартинка,599\n WebСервис,0\n HTTPСервис,0\n WSСсылка,0\n WebSocketКлиент,0\n СервисИнтеграции,0\n ЭлементСтиля,32\n Стиль,1\n Язык,1\n Константа,95\n Справочник,116\n Документ,164\n ЖурналДокументов,21\n Перечисление,312\n Отчет,481\n Обработка,107\n ПланВидовХарактеристик,4\n ПланСчетов,1\n ПланВидовРасчета,1\n РегистрСведений,228\n РегистрНакопления,51\n РегистрБухгалтерии,1\n РегистрРасчета,0\n БизнесПроцесс,0\n Задача,0\n ВнешнийИсточникДанных,0\n ПакетыXDTO,51",
"error": null,
"configuration": {
"platform_version": "8.3.27.1936",
"infobase_name": "data_mcp",
"metadata": {
"Имя": "БухгалтерияПредприятия",
"Синоним": "Бухгалтерия предприятия, редакция 2.0",
"Комментарий": "",
"Поставщик": "Фирма \"1С\"",
"Версия": "2.0.67.20",
"ОсновнойЯзык": "Русский",
"ОсновнойРежимЗапуска": "Обычное приложение",
"РежимСовместимости": "Версия8_2_16",
"РежимУправленияБлокировкойДанных": "Управляемый",
"РежимСовместимостиИнтерфейса": "Версия8_2",
"РежимИспользованияМодальности": "Использовать",
"РежимАвтонумерацииОбъектов": "НеОсвобождатьАвтоматически",
"РежимИспользованияБлочногоХраненияДвоичныхДанных": "Не использовать",
"РежимИспользованияТабличныхПространствБазыДанных": "НеИспользовать",
"ИспользоватьОбычныеФормыВУправляемомПриложении": false,
"ИспользоватьУправляемыеФормыВОбычномПриложении": true
}
}
},
"error": null
},
"verdict": {
"status": "ready",
"reason": "backend, proxy, and direct read-only 1C probe returned successfully",
"ready_for_live_replay": true
}
}

View File

@ -0,0 +1,83 @@
{
"schema_version": "mcp_live_readiness_check_v1",
"backend_url": "http://127.0.0.1:8787",
"proxy_url": "http://127.0.0.1:6003",
"channel": "default",
"backend_health_ok": true,
"proxy_health_ok": true,
"backend_health": {
"ok": true,
"service": "llm-normalizer-backend",
"status": "RUNNING",
"timezone": "Europe/Moscow",
"now": "2026-05-01T17:48:43.773Z"
},
"backend_health_error": null,
"backend_health_elapsed_seconds": 0.031,
"proxy_health": {
"status": "healthy",
"pending_commands": 0,
"pending_channels_count": 0,
"active_channels_count": 1,
"active_sessions_count": 0,
"polling_channels_count": 1,
"last_poll_at": "2026-05-01T17:48:42.979609+00:00",
"last_delivered_command_at": "2026-05-01T17:41:56.654632+00:00",
"mcp_endpoint": "/mcp",
"pending_commands_by_channel": {},
"active_sessions_by_channel": {},
"poll_activity_by_channel": {
"default": {
"poll_count": 1895,
"empty_poll_count": 1800,
"delivered_command_count": 95,
"last_poll_at": "2026-05-01T17:48:42.979609+00:00",
"last_empty_poll_at": "2026-05-01T17:48:42.979609+00:00",
"last_delivered_command_at": "2026-05-01T17:41:56.654632+00:00",
"last_poll_timeout": 0.0,
"last_delivered_tool": "get_metadata",
"last_delivered_command_id": "abd92e6f-9fd5-4b7c-80d6-3a8a954086d1"
}
}
},
"proxy_health_error": null,
"proxy_health_elapsed_seconds": 0.031,
"live_probe": {
"kind": "get_metadata",
"ok": true,
"elapsed_seconds": 0.344,
"response": {
"success": true,
"data": "[45]{\"Тип\",\"Количество\"}:\n Подсистема,30\n ОбщийМодуль,242\n ПараметрСеанса,25\n Роль,20\n ОбщийРеквизит,0\n ПланОбмена,8\n КритерийОтбора,5\n ПодпискаНаСобытие,62\n РегламентноеЗадание,16\n Бот,0\n ФункциональнаяОпция,13\n ПараметрФункциональныхОпций,0\n ОпределяемыйТип,0\n ХранилищеНастроек,0\n ОбщаяКоманда,21\n ГруппаКоманд,4\n ОбщаяФорма,113\n ОбщийМакет,107\n ОбщаяКартинка,599\n WebСервис,0\n HTTPСервис,0\n WSСсылка,0\n WebSocketКлиент,0\n СервисИнтеграции,0\n ЭлементСтиля,32\n Стиль,1\n Язык,1\n Константа,95\n Справочник,116\n Документ,164\n ЖурналДокументов,21\n Перечисление,312\n Отчет,481\n Обработка,107\n ПланВидовХарактеристик,4\n ПланСчетов,1\n ПланВидовРасчета,1\n РегистрСведений,228\n РегистрНакопления,51\n РегистрБухгалтерии,1\n РегистрРасчета,0\n БизнесПроцесс,0\n Задача,0\n ВнешнийИсточникДанных,0\n ПакетыXDTO,51",
"error": null,
"configuration": {
"platform_version": "8.3.27.1936",
"infobase_name": "data_mcp",
"metadata": {
"Имя": "БухгалтерияПредприятия",
"Синоним": "Бухгалтерия предприятия, редакция 2.0",
"Комментарий": "",
"Поставщик": "Фирма \"1С\"",
"Версия": "2.0.67.20",
"ОсновнойЯзык": "Русский",
"ОсновнойРежимЗапуска": "Обычное приложение",
"РежимСовместимости": "Версия8_2_16",
"РежимУправленияБлокировкойДанных": "Управляемый",
"РежимСовместимостиИнтерфейса": "Версия8_2",
"РежимИспользованияМодальности": "Использовать",
"РежимАвтонумерацииОбъектов": "НеОсвобождатьАвтоматически",
"РежимИспользованияБлочногоХраненияДвоичныхДанных": "Не использовать",
"РежимИспользованияТабличныхПространствБазыДанных": "НеИспользовать",
"ИспользоватьОбычныеФормыВУправляемомПриложении": false,
"ИспользоватьУправляемыеФормыВОбычномПриложении": true
}
}
},
"error": null
},
"verdict": {
"status": "ready",
"reason": "backend, proxy, and direct read-only 1C probe returned successfully",
"ready_for_live_replay": true
}
}

View File

@ -0,0 +1,83 @@
{
"schema_version": "mcp_live_readiness_check_v1",
"backend_url": "http://127.0.0.1:8787",
"proxy_url": "http://127.0.0.1:6003",
"channel": "default",
"backend_health_ok": true,
"proxy_health_ok": true,
"backend_health": {
"ok": true,
"service": "llm-normalizer-backend",
"status": "RUNNING",
"timezone": "Europe/Moscow",
"now": "2026-05-01T17:17:07.596Z"
},
"backend_health_error": null,
"backend_health_elapsed_seconds": 0.078,
"proxy_health": {
"status": "healthy",
"pending_commands": 0,
"pending_channels_count": 0,
"active_channels_count": 1,
"active_sessions_count": 0,
"polling_channels_count": 1,
"last_poll_at": "2026-05-01T17:17:06.670944+00:00",
"last_delivered_command_at": null,
"mcp_endpoint": "/mcp",
"pending_commands_by_channel": {},
"active_sessions_by_channel": {},
"poll_activity_by_channel": {
"default": {
"poll_count": 28,
"empty_poll_count": 28,
"delivered_command_count": 0,
"last_poll_at": "2026-05-01T17:17:06.670944+00:00",
"last_empty_poll_at": "2026-05-01T17:17:06.670944+00:00",
"last_delivered_command_at": null,
"last_poll_timeout": 0.0,
"last_delivered_tool": null,
"last_delivered_command_id": null
}
}
},
"proxy_health_error": null,
"proxy_health_elapsed_seconds": 0.016,
"live_probe": {
"kind": "get_metadata",
"ok": true,
"elapsed_seconds": 0.187,
"response": {
"success": true,
"data": "[45]{\"Тип\",\"Количество\"}:\n Подсистема,30\n ОбщийМодуль,242\n ПараметрСеанса,25\n Роль,20\n ОбщийРеквизит,0\n ПланОбмена,8\n КритерийОтбора,5\n ПодпискаНаСобытие,62\n РегламентноеЗадание,16\n Бот,0\n ФункциональнаяОпция,13\n ПараметрФункциональныхОпций,0\n ОпределяемыйТип,0\n ХранилищеНастроек,0\n ОбщаяКоманда,21\n ГруппаКоманд,4\n ОбщаяФорма,113\n ОбщийМакет,107\n ОбщаяКартинка,599\n WebСервис,0\n HTTPСервис,0\n WSСсылка,0\n WebSocketКлиент,0\n СервисИнтеграции,0\n ЭлементСтиля,32\n Стиль,1\n Язык,1\n Константа,95\n Справочник,116\n Документ,164\n ЖурналДокументов,21\n Перечисление,312\n Отчет,481\n Обработка,107\n ПланВидовХарактеристик,4\n ПланСчетов,1\n ПланВидовРасчета,1\n РегистрСведений,228\n РегистрНакопления,51\n РегистрБухгалтерии,1\n РегистрРасчета,0\n БизнесПроцесс,0\n Задача,0\n ВнешнийИсточникДанных,0\n ПакетыXDTO,51",
"error": null,
"configuration": {
"platform_version": "8.3.27.1936",
"infobase_name": "data_mcp",
"metadata": {
"Имя": "БухгалтерияПредприятия",
"Синоним": "Бухгалтерия предприятия, редакция 2.0",
"Комментарий": "",
"Поставщик": "Фирма \"1С\"",
"Версия": "2.0.67.20",
"ОсновнойЯзык": "Русский",
"ОсновнойРежимЗапуска": "Обычное приложение",
"РежимСовместимости": "Версия8_2_16",
"РежимУправленияБлокировкойДанных": "Управляемый",
"РежимСовместимостиИнтерфейса": "Версия8_2",
"РежимИспользованияМодальности": "Использовать",
"РежимАвтонумерацииОбъектов": "НеОсвобождатьАвтоматически",
"РежимИспользованияБлочногоХраненияДвоичныхДанных": "Не использовать",
"РежимИспользованияТабличныхПространствБазыДанных": "НеИспользовать",
"ИспользоватьОбычныеФормыВУправляемомПриложении": false,
"ИспользоватьУправляемыеФормыВОбычномПриложении": true
}
}
},
"error": null
},
"verdict": {
"status": "ready",
"reason": "backend, proxy, and direct read-only 1C probe returned successfully",
"ready_for_live_replay": true
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -12,11 +12,26 @@ Each phase must specify:
- `non-goals`
- `acceptance signals`
## Current Execution Snapshot (2026-04-17)
## Current Status Pointer (2026-05-01)
Current honest estimate of overall turnaround completion: `~85%`
This file is now a historical phase matrix, not the current progress dashboard.
Current phase status:
Current status is tracked in:
- [README.md](./README.md)
- [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)
Current reporting baseline:
- Post-F Semantic Integrity Hardening: `99%`, operationally closed/regression gate.
- Planner Autonomy Consolidation: `100%` for the declared phase83 slice.
- Active next pressure: broader Open-World Bounded Autonomy Breadth over unfamiliar 1C asks.
## Archived Execution Snapshot (2026-04-17)
Archived honest estimate of overall turnaround completion for this snapshot: `~85%`
Archived phase status:
- `Phase 0` - `100%`
- `Phase 1` - `100%`

View File

@ -1,4 +1,15 @@
# 08 - Current Status Audit (2026-04-17)
# 08 - Archived Current Status Audit (2026-04-17)
## Status Canon Update - 2026-05-01
This is an archived execution snapshot, not the current progress dashboard.
The percentages and graph counts below were correct for the `2026-04-17` audit window.
Current module wording lives in:
- [README.md](./README.md)
- [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)
## Purpose

View File

@ -1,5 +1,16 @@
# 11 - Continuity Stabilization Plan (2026-04-17)
## Status Canon Update - 2026-05-01
This plan is historical architecture trail for the continuity breakpoint.
Any `78%` estimate inside this file is the original `2026-04-17` planning estimate, not the current module status.
Current module wording lives in:
- [README.md](./README.md)
- [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)
## Purpose
This note defines the recovery plan for the current pre-expansion breakpoint.
@ -603,7 +614,7 @@ Current remaining heavy fronts before low-risk domain expansion:
Execution framing for the next level:
- the working target is now `90%+ pre-multidomain readiness` before controlled domain expansion begins;
- the current honest level is `~78%`, so the remaining gap should be treated as four large iterations, not as a few cosmetic follow-ups.
- the `2026-04-17` honest level was `~78%`, so the remaining gap at that time had to be treated as four large iterations, not as a few cosmetic follow-ups.
Current four-iteration plan:

View File

@ -1,5 +1,16 @@
# 13 - Pre-Multidomain Readiness Audit (2026-04-18)
## Status Canon Update - 2026-05-01
This is an archived readiness audit, not the current module-progress dashboard.
The `78%` pre-multidomain readiness estimate below belongs to the `2026-04-18` audit window.
Current module wording lives in:
- [README.md](./README.md)
- [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)
## Purpose
This note answers one question directly:
@ -16,12 +27,12 @@ Short version:
- the turnaround is real and already operational;
- but the system is still not ready for low-risk broad multi-domain expansion.
Current verdict:
Archived verdict at the `2026-04-18` audit point:
- safe for continued hardening and controlled domain-by-domain expansion under replay gates;
- not yet safe for wide parallel multi-agent domain expansion.
Current confidence snapshot:
Archived confidence snapshot:
- turnaround implementation progress: `~96%`
- exit-from-danger-zone readiness: `~91%`
@ -165,7 +176,7 @@ The system should not be considered ready for the next level until all of the fo
The current planning assumption is:
- `90%+ pre-multidomain readiness` is the practical threshold for starting controlled domain expansion;
- the project is currently around `78%`;
- the project was around `78%` at this audit point;
- therefore the remaining gap should be treated as roughly four large iterations rather than as a handful of cosmetic fixes.
The following priority rule now applies across all four iterations:

View File

@ -284,3 +284,52 @@ The current hand-off is therefore:
- keep D/E/F as the bounded autonomy baseline;
- keep Post-F semantic integrity invariants as regression gates;
- continue the next slice by expanding open-world breadth only behind the same evidence and replay discipline.
## Breadth Proof Update - 2026-05-01
The next bounded breadth slice is now replay-backed for the active inventory stock domain contract:
- active contract: `docs/orchestration/active_domain_contract.json`
- accepted pack: `inventory_stock_open_world_breadth_rerun_semantic_integrity_20260501_fix5`
- domain: `inventory_stock_supplier_provenance`
- execution_status: `exact`
- final_status: `accepted`
- repair targets: `0`
This proof matters for D/E/F because it validates the intended operating model after the foundation closure:
- one mutable domain contract describes a scenario tree rather than scattered ad hoc prompts;
- live replay validates root nodes, critical drilldowns, selected-object wording, colloquial wording, and carryover edges;
- runtime fixes are minimal seams around evidence retrieval, semantic filters, and stale-scope protection;
- acceptance is based on user-facing business answers first, then debug contracts.
This should not be read as arbitrary open-world 1C completion.
It is a successful bounded breadth slice:
- inventory root snapshots;
- supplier provenance;
- purchase documents and dates;
- supplier overlap;
- unresolved supplier-link analysis;
- sale trace;
- purchase-to-sale chain verification.
The detailed map is:
- [19 - inventory_stock_open_world_breadth_proof_2026-05-01.md](./19%20-%20inventory_stock_open_world_breadth_proof_2026-05-01.md)
## Status Canon Update - 2026-05-01
D/E/F remain the bounded-autonomy baseline.
They should not be reopened just because later documents discuss Post-F or Planner Autonomy progress.
Current reading:
- D/E/F are implemented substrate.
- Post-F Semantic Integrity Hardening is operationally closed at `99%` and now acts as a regression gate for this substrate.
- Planner Autonomy Consolidation is closed at `100%` for the declared phase83 planner-brain slice.
- The active next module is broader Open-World Bounded Autonomy Breadth over unfamiliar 1C asks.
Use [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md) as the short status source of truth.

View File

@ -250,3 +250,48 @@ In practice this means:
- breadth work without semantic replay is not enough;
- semantic polish without protecting the actual business object is not enough;
- user trust now depends on both.
## Post-Closure Gate Update - 2026-05-01
The first inventory-stock breadth slice after Post-F closure has now passed under these invariants:
- `inventory_stock_open_world_breadth_rerun_semantic_integrity_20260501_fix5`
- execution_status: `exact`
- final_status: `accepted`
- repair targets: `0`
This does not reopen Post-F.
It confirms the intended hand-off: new bounded breadth work can proceed when every added domain path is forced through the semantic-integrity gates.
The accepted inventory pack specifically exercised Post-F-sensitive seams:
- selected-object carryover from root stock snapshots into supplier provenance;
- pronoun and colloquial follow-ups over the active stock item;
- supplier/buyer verification without stale organization contamination;
- sale-trace pivots after account-41 stock snapshots;
- unresolved supplier-link analysis where semantic period truth must remain visible even when execution broadens to recover purchase history.
The last seam produced the final `fix5` guard:
- semantic filters keep `period_from=2021-09-01`, `period_to=2021-09-30`, `as_of_date=2021-09-30`;
- lifecycle execution detaches the month window but keeps the as-of date for evidence recovery;
- the user-facing answer reports confirmed available evidence and limitations instead of leaking debug mechanics.
Detailed proof:
- [19 - inventory_stock_open_world_breadth_proof_2026-05-01.md](./19%20-%20inventory_stock_open_world_breadth_proof_2026-05-01.md)
## Status Canon Update - 2026-05-01
Post-F should no longer be reported as an active unfinished module at `78%`, `87%`, or `92%`.
Current reporting rule:
- `Прогресс модуля: 99% (Post-F Semantic Integrity Hardening, operationally closed/regression gate)` when this slice itself is being discussed.
- New breadth work should use its own module name and percentage instead of lowering the Post-F percentage.
Planner Autonomy closure and the next active pressure are tracked in:
- [20 - planner_autonomy_consolidation_2026-05-01.md](./20%20-%20planner_autonomy_consolidation_2026-05-01.md)
- [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)

View File

@ -19,11 +19,14 @@ So this document becomes the current map layer over the older maps.
Use the documentation in this order:
1. `README.md` for the current package snapshot and readiness percentages.
2. `16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md` for the D/E/F bounded-autonomy baseline.
3. `17 - post_f_semantic_integrity_hardening_2026-04-23.md` for the semantic-integrity invariants and closure evidence.
4. This document for the code-level responsibility map after Post-F.
5. Documents `01` through `15` as historical architecture and stabilization trail, not as the current final status.
1. `README.md` for the current package snapshot.
2. `21 - current_status_canon_2026-05-01.md` for current module-progress wording.
3. `20 - planner_autonomy_consolidation_2026-05-01.md` for the closed phase83 planner-brain slice.
4. `16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md` for the D/E/F bounded-autonomy baseline.
5. `17 - post_f_semantic_integrity_hardening_2026-04-23.md` for the semantic-integrity invariants and closure evidence.
6. This document for the code-level responsibility map after Post-F.
7. `19 - inventory_stock_open_world_breadth_proof_2026-05-01.md` for the first post-closure inventory-stock breadth proof under Post-F gates.
8. Documents `01` through `15` as historical architecture and stabilization trail, not as the current final status.
## Current Code Map
@ -178,3 +181,37 @@ Every new breadth step must keep the Post-F invariants as regression gates.
- historical docs before `15` are not current status documents and can be misread without this sync layer;
- full broad test-suite green was not the primary proof for Post-F closure; semantic replay remains the stronger evidence;
- future runtime-job artifacts can still accumulate outside the curated autorun flow and should be committed only when intentionally useful.
## Addendum - 2026-05-01
The first post-closure bounded breadth slice is now accepted:
- `inventory_stock_open_world_breadth_rerun_semantic_integrity_20260501_fix5`
- domain: `inventory_stock_supplier_provenance`
- execution_status: `exact`
- final_status: `accepted`
- repair targets: `0`
This addendum does not change the Post-F closure reading.
It adds evidence that the code map can now support a broader inventory-stock scenario tree while preserving the Post-F rules around stale scope, selected-object memory, direct-answer-first behavior, and temporal honesty.
The detailed inventory-stock map is:
- [19 - inventory_stock_open_world_breadth_proof_2026-05-01.md](./19%20-%20inventory_stock_open_world_breadth_proof_2026-05-01.md)
## Status Canon Addendum - 2026-05-01
This document is still the Post-F code map, but it is no longer the top-level status dashboard.
Current module wording lives in:
- [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)
Read this file as:
- Post-F code responsibility map;
- evidence trail for the operational Post-F closure;
- regression-gate checklist for future breadth work.
Do not read it as a reason to lower the Post-F module percentage when the active work has moved to the next breadth slice.

View File

@ -0,0 +1,119 @@
# 19 - Inventory Stock Open-World Breadth Proof (2026-05-01)
## Purpose
This note records the first inventory-stock breadth slice that was hardened after the Post-F semantic integrity closure.
It is not a new Post-F rescue pass.
It is the next track described in the Post-F hand-off:
- grow bounded open-world 1C coverage;
- keep Post-F semantic integrity invariants as regression gates while that breadth grows.
## What Was Hardened
The active domain contract is:
- `docs/orchestration/active_domain_contract.json`
- domain: `inventory_stock_supplier_provenance`
- runtime domain: `inventory_stock`
The accepted pack covers:
- root stock snapshots on current and historical dates;
- selected-item supplier provenance;
- purchase dates and purchase documents for selected stock items;
- supplier overlap and supplier-scoped stock;
- old-stock aging and unresolved supplier-link analysis;
- sale trace and purchase-to-sale chain checks.
The important architecture point is that this pack validates a scenario tree, not isolated prompts. It covers root nodes, critical drilldown nodes, critical edges, UI selected-object wording, colloquial wording, short follow-ups, and pronoun-style carryover.
## Main Runtime Fixes Represented In The Proof
The current accepted runtime slice depends on these code-level seams:
- inventory sale trace now uses document-level sale evidence instead of accounting movement rows when the user asks who the item was sold to;
- purchase-to-sale chain checks use a purchase plus sale document union and keep requested supplier/buyer as verification parties rather than hard row filters;
- explicit `counterparty` and `contract` row fields are materialized before falling back to analytics strings;
- supplier anchors in chain wording are trimmed away from the `-> item -> buyer` tail;
- current-turn supplier/buyer anchors are protected from stale organization-scope poisoning;
- unresolved supplier-link stock questions keep a semantic stock month from `as_of_date`, while execution still queries purchase history broadly up to the snapshot date.
The last bullet is the `fix5` closure seam. Before it, the user-facing answer was already useful, but the pack rejected the step because the semantic period window was not visible when dialog continuation treated the turn as a new topic.
## Replay Evidence
Accepted live replay:
- `inventory_stock_open_world_breadth_rerun_semantic_integrity_20260501_fix5`
- execution_status: `exact`
- final_status: `accepted`
- repair targets: `0`
Scenario results:
- `inventory_snapshot_roots`: accepted
- `inventory_selected_item_provenance`: accepted
- `inventory_supplier_overlap`: accepted
- `inventory_aging_and_unresolved`: accepted
- `inventory_sale_trace`: accepted
The final unresolved supplier-link step now answers directly:
- confirms `192` purchase operations without an explicitly extracted supplier in the available stock-snapshot evidence;
- states the snapshot date `30.09.2021`;
- states the limitation that this is available purchase-trace evidence, not legal batch ownership proof;
- lists concrete item/document evidence.
The debug contract now keeps:
- `detected_intent = inventory_supplier_stock_overlap_as_of_date`
- `selected_recipe = address_inventory_supplier_stock_overlap_as_of_date_v1`
- `capability_id = inventory_inventory_supplier_stock_overlap_as_of_date`
- `mcp_call_status = matched_non_empty`
- `rows_fetched/materialized/matched = 500/500/500`
- `period_from = 2021-09-01`
- `period_to = 2021-09-30`
- `as_of_date = 2021-09-30`
- `missing_required_filters = []`
- `period_window_semantic_from_inventory_as_of_month`
- `period_window_detached_for_lifecycle_execution`
## Validation
Local validation for the accepted slice:
- `npm.cmd test -- addressInventorySelectedObjectFollowup.test.ts`: passed, `20 passed`, `1 skipped`
- targeted regression pack: passed, `537 passed`, `1 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5905 nodes`, `12809 edges`, `137 communities`
- live domain pack `fix5`: accepted
## Architecture Reading
This pass should be read as a breadth step guarded by Post-F invariants.
It does not reopen the Post-F closure. Instead, it proves that new domain breadth can be added while preserving the hard Post-F rules:
- explicit current-turn subject beats stale scope;
- selected-object carryover remains object-centric;
- semantic period/debug truth can differ from broad recovery execution when the user needs historical evidence;
- exact route success must materialize into a business-useful answer;
- replay acceptance is judged first through the human answer, then through debug internals.
## Remaining Risk
This does not mean arbitrary inventory questions are solved.
The accepted slice is strong for the declared stock/provenance/sale-trace tree, but the broader open-world surface still needs more domain packs and unfamiliar-question probes.
The next safe move is to continue one bounded domain slice at a time:
1. choose the next active domain contract or expand this inventory contract deliberately;
2. run a live pack;
3. inspect user-facing answers first;
4. patch minimal runtime seams;
5. rerun until the scenario tree is accepted;
6. only then save or commit runtime artifacts intentionally.

View File

@ -0,0 +1,383 @@
# 20 - Planner Autonomy Consolidation (2026-05-01)
## Purpose
This note starts the consolidation layer after the first accepted inventory-stock breadth proof.
The goal is to move from:
- domain pack proves one more slice;
- planner still carries too many local recipe branches;
to:
- reusable MCP primitive and chain descriptors;
- planner-selected route fabric;
- domain packs as semantic gates, not as the main design mechanism.
This is the continuation of the original "MCP as bounded brain" goal.
## Architectural Reading
The target is not an unrestricted model agent.
The target remains:
`user question -> data_need_graph -> catalog chain template -> reviewed primitives -> bounded evidence loop -> truth gate -> answer`
The LLM may help choose the path, but only inside reviewed MCP boundaries.
## Code Steps
The first consolidation step adds reusable chain templates to `assistantMcpCatalogIndex`.
The catalog now describes not only primitive contracts, but also planner route-fabric templates:
- `metadata_inspection`
- `catalog_drilldown`
- `entity_resolution`
- `document_evidence`
- `movement_evidence`
- `value_flow`
- `value_flow_comparison`
- `value_flow_ranking`
- `lifecycle`
Each template declares:
- semantic data need;
- human-readable chain summary;
- fallback primitive sequence;
- base required axes;
- supported fact/action families;
- planning tags;
- evidence-gate requirement.
The planner now instantiates selected evidence chains from this catalog for the first base lanes instead of keeping all route meaning only in local planner branches.
The follow-up consolidation step moved the value-flow planner seams onto the same catalog fabric:
- bidirectional incoming-vs-outgoing comparison now instantiates `value_flow_comparison`, including explicit-counterparty comparison graphs rather than only subjectless organization-scope graphs;
- ranked revenue/payment questions now instantiate `value_flow_ranking`;
- organization-scoped open totals now instantiate `value_flow` with subjectless primitives but catalog-owned axes and evidence-gate semantics;
- heuristic fallback routes for value-flow, lifecycle, metadata, movement, document, entity, and unclassified metadata inspection now also use catalog chain templates.
This keeps behavior stable while making the planner's route meaning inspectable through catalog descriptors instead of only through local `recipeFor()` branches.
The next consolidation step strengthened lifecycle as a bounded inference chain instead of a loose age-like shortcut:
- the lifecycle template now declares `activity_window` and `legal_fact_boundary` axes;
- the template summary explicitly frames the result as a first/latest confirmed 1C activity window, not legal registration age;
- planner graph and fallback recipes now emit lifecycle bounded-inference reason codes;
- lifecycle evidence facts include the matched row count, first/latest confirmed activity dates, and an explicit legal-fact boundary.
Two arbitration seams were also hardened because they are part of the same planner-autonomy surface:
- current-turn value-flow aggregate questions can override supported exact legacy routes when the user asks for amount/net/payment totals and the exact route would only produce a narrower lookup/list answer;
- broad business evaluation (`broad_business_evaluation`) is intentionally kept in the deterministic living-chat bridge instead of being displaced by generic metadata discovery.
These changes keep the route fabric broader without letting the planner pretend that inferred evidence is a formally proven legal fact.
The following consolidation step promoted the accepted inventory-stock breadth behavior into reviewed catalog route fabric:
- `inventory_stock_snapshot`
- `inventory_supplier_overlap`
- `inventory_purchase_provenance`
- `inventory_sale_trace`
These templates are now first-class catalog chain descriptors and can be selected by the data-need graph/planner. They reuse reviewed generic primitives (`query_movements`, `query_documents`, `aggregate_by_axis`, `drilldown_related_objects`, `probe_coverage`, `explain_evidence_basis`) and add inventory-specific axes such as `as_of_date`, `warehouse`, `supplier`, `buyer`, `quantity`, and `evidence_basis`.
The first runtime bridge for these inventory templates now delegates through existing exact inventory recipes instead of inventing a new generic inventory executor:
- `inventory_stock_snapshot` -> `inventory_on_hand_as_of_date`
- `inventory_supplier_overlap` -> `inventory_supplier_stock_overlap_as_of_date`
- `inventory_purchase_provenance` -> `inventory_purchase_provenance_for_item`
- `inventory_sale_trace` -> `inventory_sale_trace_for_item`
The bridge keeps the reviewed MCP route fabric as the planner surface, but uses `addressRecipeCatalog` exact queries and account scope `41.01` as the evidence source. Root inventory templates execute through `query_movements`; selected-item provenance/sale templates execute through `query_documents`. Missing selected-item anchors remain clarification, not a guessed item.
The runtime answer boundary still makes unsupported or unconfirmed inventory states explicit:
- unsupported inventory route templates get a user-facing "template selected, live execution not yet bridged" answer instead of a generic checked-sources fallback;
- `must_not_claim` forbids presenting inventory planning as executed stock, supplier, purchase, or sale evidence;
- technical unsupported-pilot limitation text is filtered out of user-facing lines, while existing bounded unknowns for lifecycle/value-flow remain intact.
The next local scoring step broadened metadata-surface autonomy without adding a new hard domain route:
- if a confirmed metadata surface is unambiguous and only exposes `Document.*`, `Register.*`, or `Catalog.*` objects, the planner can infer the next reviewed lane even when upstream has not yet filled `downstream_route_family`;
- inferred document surfaces instantiate `document_evidence`;
- inferred register/movement surfaces instantiate `movement_evidence`;
- inferred catalog surfaces instantiate `catalog_drilldown`;
- mixed or ambiguous surfaces still do not guess and continue through clarification / explicit data-need scoring.
The following consolidation step added catalog-level chain-template scoring:
- `assistantMcpCatalogIndex` can now score reviewed `chain_templates` directly from fact family, action family, required axes, comparison, ranking, and aggregation needs;
- comparison-shaped value-flow ranks `value_flow_comparison` above the generic value-flow template;
- ranking-shaped value-flow ranks `value_flow_ranking` above the generic value-flow template;
- document/movement/inventory/lifecycle templates can now be inspected as catalog search results, not only as local planner branch constants;
- `assistantMcpDiscoveryPlanner` records the top catalog chain-template match in reason codes and exposes the ranked matches as `catalog_chain_template_matches` in the planner contract while preserving existing guarded execution behavior.
- the ranked chain-template matches are now propagated into runtime loop state and debug attachment fields, so replay analysis can inspect catalog-fabric intent without parsing reason-code strings.
- `catalog_chain_template_alignment` now records whether the selected chain is the top catalog match, its rank, and whether it appeared in the catalog search results; runtime loop state and debug summary expose the same verdict.
- planner reason codes now emit stable catalog-alignment telemetry for evaluated top-match, selected-equals-top, selected-lower-rank, selected-outside-match-set, and unscored selected-chain states.
- `catalog_chain_template_alignment.alignment_status` now carries the same verdict as one enum-like field, and debug summary exposes it as `mcp_discovery_catalog_chain_alignment_status`.
- `domain_truth_harness` and `scenario_acceptance_policy` now carry the alignment status, top catalog match, and selected-matches-top flag into replay artifacts instead of leaving them buried in raw debug JSON.
- truth-harness now raises a warning finding for `selected_lower_rank` and `selected_outside_match_set` alignment states unless the replay spec explicitly marks `allow_catalog_alignment_divergence`.
- scenario acceptance now groups that warning under `catalog_alignment_ok`, and `final_status.md` prints the invariant alongside direct-answer, temporal, truth-gate, human-answer, meta-context, and selected-object gates.
- truth-harness specs can now assert `expected_catalog_alignment_status`, `expected_catalog_chain_top_match`, and `expected_catalog_selected_matches_top` on each step.
- `address_truth_harness_phase66_human_org_open_scope_dialog.json` now uses those fields to assert `value_flow`, `value_flow_comparison`, and `value_flow_ranking` top matches across the open-organization money dialog.
- `address_truth_harness_phase32_planner_selected_chain_end_to_end.json` now uses the same assertions across selected-counterparty entity grounding, incoming/outgoing/net value-flow, document evidence, and movement evidence follow-ups.
- `agent_semantic_pack_builder` now preserves these expected catalog-alignment fields in the reusable source catalog and adds the `planner_catalog_alignment` tag, so future mixed AGENT packs can deliberately select planner-brain regression probes instead of relying on hand-picked replay filenames.
- The new `turnaround_11_planner_brain_alignment_mix` builder recipe generates `address_truth_harness_phase83_planner_brain_alignment_mix.json`, a 20-step mixed canary that crosses selected-counterparty value-flow, open-organization totals/comparison/ranking, broad-evaluation continuity, metadata drilldown, and off-domain living-chat safety.
- The phase83 live replay now confirms that selected chains match the reviewed catalog top match across the mixed planner-brain pack and that the business-answer path remains usable after cross-stage pivots.
- Checked-source failure replies now sanitize raw MCP transport/internal continuation strings from the user-facing answer while keeping the raw diagnostics in technical debug payloads.
- Confirmed metadata-surface follow-ups now promote the surface-grounded chain template (`document_evidence`, `movement_evidence`, or `catalog_drilldown`) to the top catalog match when the selected chain came from the same checked surface. This keeps the planner's executed route and catalog-alignment diagnostics consistent without allowing ambiguous or stale surfaces to override explicit current-turn data needs.
## Why This Matters
This reduces the pressure to add one hard route per user wording.
Future domain enablement should prefer:
- add or strengthen primitive descriptors;
- add or strengthen chain templates;
- let data-need graph and catalog search assemble the path;
- use domain packs to verify the scenario tree and catch semantic drift.
Domain-specific exact recipes can still exist as fast paths, but they should not be the only way the assistant understands a new business question.
## Validation
Local validation after the catalog-template, value-flow, metadata-lane scoring, lifecycle bounded-inference, current-turn value-flow arbitration, and broad-evaluation bridge steps:
- `npm.cmd test -- assistantMcpCatalogIndex.test.ts assistantMcpDiscoveryPlanner.test.ts`: passed, `47 passed`
- MCP-discovery suite: passed, `227 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5911 nodes`, `12830 edges`, `138 communities`
- live value-flow canary: `address_truth_harness_phase66_human_org_open_scope_dialog_planner_template_rerun2`, accepted `7/7`
- live metadata movement canary: `address_truth_harness_phase52_metadata_movement_full_recovery_planner_metadata_scoring_rerun2`, accepted `4/4`
- live metadata document canary: `address_truth_harness_phase54_metadata_document_full_recovery_planner_metadata_scoring_rerun2`, accepted `4/4`
Additional code-level consolidation:
- ambiguous metadata surfaces no longer carry both document and movement primitives when the current data-need graph explicitly selects `document_evidence` or `movement_evidence`;
- thin neutral metadata follow-ups still do not force a lane and keep the clarification boundary intact;
- planner reason codes now expose when an explicit lane family is scored against carried metadata ambiguity:
`planner_metadata_surface_scored_with_explicit_lane_family`.
Latest validation after the lifecycle and arbitration hardening:
- targeted lifecycle/catalog/planner/answer tests: passed, `75 passed`, `1 skipped`
- full MCP-discovery suite: passed, `268 passed`, `9 skipped`
- broad MCP/living-chat/route/meaning slice: passed, `305 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5912 nodes`, `12833 edges`, `138 communities`
- live lifecycle/value-flow response gate: `address_truth_harness_phase19_mcp_discovery_response_gate_planner_lifecycle_rerun4`, accepted `8/8`
- live broad-eval to net-flow follow-up: `address_truth_harness_phase21_net_followup_after_broad_eval_planner_lifecycle_rerun2`, accepted `3/3`
- live broad-evaluation bridge: `address_truth_harness_phase22_broad_business_evaluation_bridge_planner_lifecycle_rerun2`, accepted `3/3`
Latest validation after the inventory catalog-template lift:
- targeted catalog/data-need/planner/turn-input tests: passed, `139 passed`, `6 skipped`
- full MCP-discovery suite: passed, `276 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5912 nodes`, `12833 edges`, `138 communities`
Latest validation after the inventory runtime-boundary hardening:
- targeted runtime-bridge/answer-adapter/pilot-executor tests: passed, `68 passed`, `1 skipped`
- full MCP-discovery suite: passed, `277 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5913 nodes`, `12837 edges`, `138 communities`
Latest validation after the inventory exact-runtime bridge:
- targeted runtime-bridge/answer-adapter/pilot-executor tests: passed, `70 passed`, `1 skipped`
- full MCP-discovery suite: passed, `279 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5930 nodes`, `12884 edges`, `135 communities`
Latest validation after unambiguous metadata-surface lane inference:
- targeted planner tests: passed, `36 passed`
- full MCP-discovery suite: passed, `281 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5937 nodes`, `12899 edges`, `138 communities`
- live inventory full-pack attempt: `inventory_stock_exact_bridge_live_20260501_after_runtime_bridge`, status `partial`
- live attempt interpretation: route/intent/recipe/capability selection matched, but MCP execution failed with `MCP fetch failed: This operation was aborted`; direct proxy `get_metadata` also timed out while `/health` reported `active_sessions_count=0` and pending commands, so this is an infrastructure/polling-session blocker rather than accepted semantic evidence.
Latest validation after catalog chain-template scoring:
- targeted catalog/planner tests: passed, `54 passed`
- full MCP-discovery suite: passed, `282 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5938 nodes`, `12903 edges`, `139 communities`
Latest validation after structured catalog chain-template contract exposure:
- targeted planner tests: passed, `36 passed`
- full MCP-discovery suite: passed, `282 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5939 nodes`, `12906 edges`, `138 communities`
Latest validation after runtime/debug propagation of structured chain matches:
- targeted runtime/debug tests: passed, `18 passed`
- full MCP-discovery suite: passed, `282 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5940 nodes`, `12909 edges`, `137 communities`
Latest validation after subject-aware bidirectional comparison arbitration:
- targeted planner tests: passed, `36 passed`
- full MCP-discovery suite: passed, `282 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5940 nodes`, `12909 edges`, `137 communities`
Latest validation after structured catalog chain-template alignment verdict:
- targeted planner/runtime/debug tests: passed, `54 passed`
- full MCP-discovery suite: passed, `282 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5941 nodes`, `12911 edges`, `136 communities`
Latest validation after representative catalog-alignment regression guard:
- targeted planner tests: passed, `37 passed`
- full MCP-discovery suite: passed, `283 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5942 nodes`, `12912 edges`, `140 communities`
Latest validation after catalog-alignment reason-code telemetry:
- targeted planner/runtime tests: passed, `53 passed`
- full MCP-discovery suite: passed, `283 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5943 nodes`, `12915 edges`, `136 communities`
Latest validation after explicit catalog-alignment status propagation:
- targeted planner/runtime/debug tests: passed, `55 passed`
- full MCP-discovery suite: passed, `283 passed`, `9 skipped`
- `npm.cmd run build`: passed
- graphify rebuild: `5943 nodes`, `12915 edges`, `136 communities`
Latest validation after truth-harness catalog-alignment artifact surfacing:
- Python replay-tooling tests: passed, `4 passed`
- graphify rebuild: `5946 nodes`, `12918 edges`, `136 communities`
Latest validation after catalog-alignment divergence warning gate:
- Python replay-tooling tests: passed, `5 passed`
- graphify rebuild: `5947 nodes`, `12920 edges`, `138 communities`
Latest validation after catalog-alignment acceptance invariant:
- Python replay-tooling tests: passed, `6 passed`
- graphify rebuild: `5949 nodes`, `12923 edges`, `136 communities`
Latest validation after catalog-alignment spec assertions:
- Python replay-tooling tests: passed, `7 passed`
- graphify rebuild: `5951 nodes`, `12926 edges`, `139 communities`
Latest validation after phase66 catalog-alignment spec hardening:
- Python replay-tooling tests: passed, `7 passed`
- `load_truth_harness_spec` confirmed the phase66 expected top-match chain sequence: `value_flow`, `value_flow`, `value_flow`, `value_flow_comparison`, `value_flow_comparison`, `value_flow_ranking`, `value_flow_ranking`
Latest validation after phase32 catalog-alignment spec hardening and AGENT source-catalog surfacing:
- Python replay-tooling tests: passed, `9 passed`
- `load_truth_harness_spec` confirmed the phase32 expected top-match chain sequence: `entity_resolution`, `value_flow`, `value_flow`, `value_flow_comparison`, `document_evidence`, `movement_evidence`
- `agent_semantic_pack_builder.py inventory` regenerated `agent_semantic_source_catalog.*` with reusable `planner_catalog_alignment` coverage
Latest validation after phase83 mixed planner-brain spec generation:
- `scripts.test_agent_semantic_pack_builder`: passed, `3 passed`
- generated `address_truth_harness_phase83_planner_brain_alignment_mix.json`: `20` steps, `15` expected catalog top-match checks after the phase19/21/22 alignment hardening
- regenerated `agent_semantic_source_catalog.*`: `planner_catalog_alignment` is visible with `26` reusable entries, including phase32, phase66, and phase83 probes
- graphify rebuild: `5952 nodes`, `12927 edges`, `138 communities`
Prior live-readiness diagnosis after phase83 live replay and checked-source error sanitation:
- backend health is green on `http://127.0.0.1:8787/api/health`;
- proxy health is green on `http://127.0.0.1:6003/health`, with `pending_commands=0`, `active_channels_count=1`, and `active_sessions_count=0`;
- targeted checked-source sanitation tests still pass `61/61` with `1` skipped;
- `npm.cmd run build` still passes;
- full phase83 rerun `phase83_planner_brain_alignment_live_20260501_rerun4` again ended `partial`, with `8/20` pass, `2` warning, `10` fail, and `catalog_alignment_ok=true`;
- direct proxy `get_metadata` with a 180-second client timeout also timed out, so the remaining live blocker is below the assistant planner/backend layer: the proxy accepts requests, but the 1C side does not return read-only evidence in time;
- `scripts/check_mcp_live_readiness.py` now provides a repo-native preflight that separates backend/proxy health from confirmed live 1C evidence readiness before spending time on a full semantic replay.
- graphify rebuild after the readiness preflight/docs sync: `5970 nodes`, `12958 edges`, `140 communities`.
Prior follow-up diagnosis of the proxy/1C seam:
- `1cv8c` is running locally with the `MCP Toolkit - Бухгалтерия предприятия, редакция 2.0` window title, so the failure is not simply "1C process absent";
- observing a read-only `get_metadata` command on the `default` channel showed `pending_commands=1` for 15 seconds and no pickup by the 1C client;
- the diagnostic command was explicitly drained from `/1c/poll` and completed through `/1c/result` with a synthetic cancel result so the proxy queue stayed clean;
- the proxy health endpoint now exposes polling telemetry: `polling_channels_count`, `last_poll_at`, `last_delivered_command_at`, and optional `poll_activity_by_channel` when `HEALTH_INCLUDE_CHANNEL_DETAILS=true`;
- after proxy restart with this telemetry enabled, `polling_channels_count=0` stayed stable for 20 seconds, proving no `/1c/poll` activity reached the proxy;
- `scripts/check_mcp_live_readiness.py --confirm-live` now refuses to create a direct live probe when proxy health already proves no 1C polling activity, preventing abandoned pending commands during readiness checks.
- `domain_truth_harness.py run-live --require-mcp-live-readiness` now applies the same readiness gate before the first assistant step, writes `mcp_live_readiness.json`, and exits early when live 1C evidence is unavailable;
- smoke of that harness gate against phase83 stopped before step execution with `ready_for_live_replay=false`, so future blocked runs should no longer waste a full semantic replay just to rediscover the missing `/1c/poll`.
- readiness can now wait for polling before probing: `--wait-for-polling-seconds` in `check_mcp_live_readiness.py` and `--mcp-wait-for-polling-seconds` in `domain_truth_harness.py run-live`; a 2-second smoke waited twice, observed no polling, and skipped the live probe without leaving proxy queue garbage.
Latest validation after guarded phase83 acceptance and surface-grounded catalog promotion:
- targeted planner/response-policy/pilot/continuity slice: `npm.cmd test -- assistantMcpDiscoveryPlanner.test.ts assistantMcpDiscoveryResponsePolicy.test.ts assistantMcpDiscoveryPilotExecutor.test.ts assistantContinuityPolicy.test.ts` passed `109/109`;
- `npm.cmd run build`: passed;
- graphify rebuild: `5973 nodes`, `12971 edges`, `138 communities`;
- live-readiness preflight after backend restart: `mcp_live_readiness_phase83_rerun3_after_backend_restart.json` reported `ready`;
- full guarded phase83 replay: `phase83_planner_brain_alignment_live_20260501_readygate_rerun3` accepted `20/20`, `0` warnings, `0` failures;
- final invariant result: `catalog_alignment_ok=true`, `direct_answer_ok=true`, `temporal_honesty_ok=true`, `selected_object_continuity_ok=true`, `truth_gate_ok=true`, `human_answer_quality_ok=true`, and `meta_context_integrity_ok=true`;
- the previously warning step `step_02_neutral_followup_catalog_drilldown` now reports `catalog_alignment_status=selected_matches_top`, `catalog_top_match=catalog_drilldown`, and `catalog_selected_matches_top=True`.
- saved autorun canary: `AGENT | Planner Autonomy phase83: мозг маршрутов, pivots и legacy continuity` (`gen-ag05011759-6f85fc`), sourced from the accepted phase83 spec after the live replay was reviewed.
## Next Step
The declared Planner Autonomy Consolidation slice is now closed for the phase83 acceptance target.
Keep using the live preflight before future full replays:
`python scripts/check_mcp_live_readiness.py --confirm-live --wait-for-polling-seconds 60 --poll-interval-seconds 2 --output-json artifacts/runtime/mcp_live_readiness_phase83.json`
Run future full candidates with the built-in gate:
`python scripts/domain_truth_harness.py run-live --spec docs/orchestration/address_truth_harness_phase83_planner_brain_alignment_mix.json --output-dir artifacts/domain_runs/phase83_planner_brain_alignment_live_<stamp> --require-mcp-live-readiness --mcp-wait-for-polling-seconds 60 --mcp-poll-interval-seconds 2`
Only when readiness reports `ready_for_live_replay=true` should a full replay be treated as meaningful business-evidence proof. If it reports no `/1c/poll` activity, fix the 1C toolkit client/session/channel first; another full replay will only reproduce checked-source partial answers.
Recommended order:
1. save the accepted phase83 pack into autoruns only if the product flow needs it as a legacy AGENT canary;
2. continue broader open-world bounded autonomy with phase83 as a regression gate, not as an open blocker;
3. broaden catalog scoring into unfamiliar 1C asks where metadata surface and data-need graph can pick reviewed lanes;
4. grow primitive descriptors only where live replay shows a real evidence gap;
5. keep phase19, phase21, phase22, value-flow, metadata ambiguity, inventory-stock, and phase83 as regression gates.
The key rule remains:
- do not hide a domain workaround inside the planner;
- promote repeated successful domain behavior into a reviewed primitive or chain template.
## Status Canon Addendum - 2026-05-01
The declared Planner Autonomy Consolidation slice is closed at `100%` for the phase83 acceptance target.
Future work should not lower this module's percentage.
If new defects appear while broadening unfamiliar 1C asks, track them under the next active module:
- `Open-World Bounded Autonomy Breadth`
Keep phase83 as a regression gate for that next module:
- catalog alignment must remain visible in replay artifacts;
- direct-answer and human-answer quality remain acceptance invariants;
- live-readiness preflight must run before expensive live semantic replays;
- checked-source failures must not leak raw MCP/internal continuation errors into the user-facing answer.
The short status source of truth is:
- [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)

View File

@ -0,0 +1,99 @@
# 21 - Current Status Canon (2026-05-01)
## Purpose
This note is the short source of truth for current module wording after the Post-F and Planner Autonomy closure work.
It exists to prevent stale percentage drift in planning discussions.
If another document says `78%`, `87%`, `92%`, or `85%` for a module that is now closed, read that value as a historical snapshot unless this note explicitly repeats it as current.
## Current Module Map
- `Post-F Semantic Integrity Hardening`: `99%`, operationally closed as a hardening slice and now used as a regression gate.
- `Inventory Stock Open-World Breadth Proof`: `100%` for the declared inventory-stock scenario pack, not for arbitrary inventory questions.
- `Planner Autonomy Consolidation`: `100%` for the declared phase83 planner-brain slice, including catalog alignment, live-readiness gating, checked-source sanitation, and accepted mixed replay.
- Active next module: broader `Open-World Bounded Autonomy Breadth` over unfamiliar 1C asks, while keeping Post-F and phase83 as regression gates.
- Completed active slice: `Business Overview Evidence Fusion`, tracked in `22 - open_world_bounded_autonomy_breadth_2026-05-01.md`.
- Completed active slice: `Business Overview Catalog Route Fabric`: the route is reviewed in catalog/data-need/planner contracts and exposes the stable `business_overview` route scope.
- Completed active slice: `Business Overview Fresh Multi-Probe Runtime Bridge`: the reviewed route now executes incoming money flow, outgoing supplier payout, activity-window, net-spread, top-customer, and analyst-safe answer drafting, and has passed live semantic replay against the real assistant runtime.
- Completed active slice: `Business Overview VAT/Tax Fact-Family Bridge`: explicit-period business overview can include confirmed VAT/tax position, while all-time follow-ups and negated VAT periods do not reuse stale tax scope.
- Completed active slice: `Business Overview Debt-Position Fact-Family Bridge`: explicit-period business overview can include confirmed receivables/payables as-of-date debt position, while all-time follow-ups do not reuse stale debt snapshots and debt quality/aging remains unclaimed.
- Completed active slice: `Business Overview Inventory-Position Fact-Family Bridge`: explicit-date business overview can include confirmed stock-on-hand inventory position, while all-time follow-ups do not reuse stale inventory snapshots and inventory liquidity/turnover remains unclaimed.
- Completed active slice: `Business Overview Open-Settlement Quality Bridge`: explicit-period business overview can check open-contract settlement concentration on 60/62/76, while due-date aging/overdue debt remains unclaimed until a reviewed due-date route exists.
- Completed active slice: `Selected-Item Profitability Route Bridge`: selected-object inventory profitability now has a bounded exact recipe over purchase/sale document rows, with explicit boundaries that this is a gross spread/margin proxy rather than company net profit.
- Completed active slice: `Business Overview Contract-Date Debt Age Signal Bridge`: explicit-period open-settlement quality can now include contract-date age as a bounded signal, while due-date aging/overdue debt remains unclaimed until a reviewed payment-term route exists.
- Completed active slice: `Business Overview Analyst Synthesis Layer`: business-overview answers now turn checked fact families into a bounded analyst note with operating scale, customer concentration, risk contours, and explicit profit/margin boundaries.
- Completed active slice: `Business Overview Trading Margin Proxy Bridge`: explicit-period business overview can include a bounded товарный sales-vs-purchase document proxy for revenue, purchase-cost trace, gross spread, and margin proxy, while clean profit/accounting финрезультат remains unclaimed.
- Completed active slice: `Business Overview Inventory Sales Velocity Proxy Bridge`: when explicit-period stock and товарные sales evidence are both present, business overview can include a bounded sales-to-stock proxy while full FIFO turnover/liquidity remains unclaimed.
- Completed active slice: `Business Overview Inventory Staleness Risk Proxy Bridge`: when current-turn stock aging and sales-to-stock evidence are both present, business overview can include a bounded warehouse staleness-risk proxy while confirmed obsolete stock, reserves, write-offs, and liquidation value remain unclaimed.
- Completed active slice: `Business Overview Gap-Specific Headline And Next-Step Precision`: broad company-analysis answers now name the remaining unchecked families from `missing_signal_families` instead of using stale generic profit/debt/VAT/warehouse wording after partial proxies are proven.
- Completed active slice: `Business Overview Debt Staleness Risk Proxy Bridge`: when current-turn open-settlement concentration and contract-date age are both present, business overview can include a bounded debt staleness-risk proxy while contractual delinquency, credit risk, and due-date aging remain unclaimed.
- Completed active slice: `Business Overview Supplier Concentration Proxy Bridge`: business overview now derives top suppliers/recipients from confirmed outgoing payment rows and surfaces procurement concentration without claiming vendor risk, procurement quality, or full expense structure.
- Completed active slice: `Business Overview Yearly Operating-Flow Proxy Bridge`: business overview now derives annual incoming/outgoing/net buckets from confirmed money-flow rows and can name the strongest incoming year and best operating-net year without claiming profit or P&L.
- Next active slice: continue breadth into exact company-wide accounting profit/margin, real due-date debt aging, confirmed inventory reserve/write-off/liquidation evidence, and broader unfamiliar 1C route families only where reviewed evidence routes exist.
- Active module progress: `~90% (Open-World Bounded Autonomy Breadth)`.
## Reporting Rule
Use these labels when reporting progress:
- `Прогресс модуля: 99% (Post-F Semantic Integrity Hardening, operationally closed/regression gate)` when discussing the Post-F slice itself.
- `Прогресс модуля: 100% (Planner Autonomy Consolidation, declared phase83 slice closed)` when discussing the planner-autonomy slice that was just completed.
- `Прогресс модуля: X% (Open-World Bounded Autonomy Breadth, active slice: <name>)` for new breadth work after this point.
Do not report Post-F as `78%`, `87%`, or `92%`.
Do not report Planner Autonomy as still open unless the discussion is about the next broader module, not the declared phase83 closure target.
## What Is Actually Closed
Post-F closed the acute semantic-integrity rescue layer:
- stale organization scope must not beat explicit current-turn counterparty meaning;
- stale focus objects must not hijack newly grounded entities;
- VAT and exact materialization must not self-filter confirmed rows away;
- repeated pivots must keep subject, lane, and time continuity honest;
- business-answer review remains the primary acceptance surface.
Planner Autonomy closed the declared planner-brain slice:
- reusable catalog chain templates are first-class planner objects;
- selected chains expose catalog-template matches and alignment status;
- replay artifacts surface catalog alignment as an acceptance invariant;
- phase83 proved selected-counterparty, open-organization money flow, broad-evaluation continuity, metadata drilldown, and off-domain safety together under live-readiness gating.
## What Is Not Closed
The project is not yet a universal arbitrary-1C agent.
Remaining work belongs to the next breadth module:
- extend `business_overview` beyond money-flow/activity, customer and supplier concentration, yearly operating-flow dynamics, explicit-period VAT/tax, as-of-date debt position, open-settlement concentration, contract-date debt age, debt staleness-risk proxy, as-of-date inventory position, trading-margin proxy, sales-to-stock inventory proxy, and warehouse staleness-risk proxy into separately proven exact accounting profit/margin, due-date debt aging/overdue, and confirmed reserve/write-off/liquidation inventory evidence families;
- broader dynamic schema traversal for unfamiliar 1C asks;
- more primitive descriptors where live evidence proves a real gap;
- more replay-backed domain packs that start from user business meaning, not from route convenience;
- continued pressure reduction around central intent seams such as `resolveAddressIntent()`;
- preservation of Post-F and phase83 canaries as regression gates during breadth expansion.
## Graphify Rule
`graphify-out/GRAPH_REPORT.md` is the current graph snapshot.
Older graph counts inside historical validation bullets are evidence-at-the-time, not the current graph size.
After any code or documentation sync that changes the map, rebuild graphify and treat the new report as the navigational source.
## Canonical Reading Order
For current planning, read:
1. `README.md`
2. this document
3. `22 - open_world_bounded_autonomy_breadth_2026-05-01.md`
4. `20 - planner_autonomy_consolidation_2026-05-01.md`
5. `19 - inventory_stock_open_world_breadth_proof_2026-05-01.md`
6. `17 - post_f_semantic_integrity_hardening_2026-04-23.md`
7. `16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md`
Documents `01` through `15` remain valuable, but mostly as the historical architecture trail.

View File

@ -0,0 +1,530 @@
# 22 - Open-World Bounded Autonomy Breadth (2026-05-01)
## Purpose
This note opens the next active module after:
- Post-F Semantic Integrity Hardening operational closure;
- inventory-stock bounded breadth proof;
- Planner Autonomy Consolidation phase83 closure.
The goal is not to reopen those slices.
The goal is to grow the assistant from reviewed route families toward broader bounded 1C autonomy:
`user business ask -> data-need graph -> catalog route fabric -> reviewed MCP evidence -> truth gate -> useful analyst answer`
## Entry Baseline
Already closed and kept as regression gates:
- Post-F semantic integrity: explicit current-turn meaning beats stale scope, stale focus object, and wrong post-pivot arbitration.
- Planner phase83: selected chain must align with reviewed catalog templates and replay artifacts must expose that alignment.
- Inventory breadth proof: a domain scenario tree can be accepted only through user-facing business correctness, not route labels alone.
The current active pressure is:
- unfamiliar or broad human 1C asks still need more bounded breadth;
- broad business questions must become evidence-guided analyst answers, not generic chat summaries;
- new capabilities must grow through reusable data-need and catalog surfaces, not one-off prompt patches.
## Slice 1 - Business Overview Evidence Fusion
User-facing trigger examples:
- `Как ты оценишь деятельность компании?`
- `Дай полный анализ компании`
- `Сделай LLM-аудит бизнеса`
- `Что думаешь о компании в целом?`
### Problem
Before this slice, broad business evaluation was protected from stale lifecycle replay, but it could still be too thin:
- it summarized context, but did not consistently surface confirmed metrics;
- it did not clearly separate cash-flow signals from profit/margin claims;
- it could under-answer when the user expected a mature analyst-style company overview.
### Current Implementation Boundary
The first implementation step is intentionally bounded:
- it does not invent fresh facts;
- it fuses already-confirmed MCP/session evidence from recent grounded answers;
- it recognizes broader company-analysis wording as `broad_business_evaluation`;
- it extracts useful analyst signals from confirmed value-flow, bidirectional net-flow, ranking, lifecycle, and inventory evidence;
- it states that profit, margin, debt quality, and full company health remain unproved unless corresponding evidence exists.
This is not yet the final automatic multi-probe company-analysis chain.
It is the safe first slice: better user-facing business overview without bypassing evidence gates.
### Runtime Contract
When this bridge answers, it must:
- start with a direct business summary;
- list confirmed metrics separately from interpretation;
- call out cash-flow direction and net spread only as cash evidence, not as profit;
- mention top counterparty/customer only when a ranked value-flow evidence slice exists;
- keep profit/margin/debt/VAT as explicit missing evidence when not checked;
- avoid raw MCP, planner, catalog, route, primitive, or debug wording.
## Slice 2 - Business Overview Catalog Route Fabric
This slice promotes the broad-evaluation contour from a living-chat bridge into the reviewed planner/catalog vocabulary.
### Current Implementation Boundary
Implemented now:
- `business_overview` exists as a reviewed catalog chain template;
- the data-need graph recognizes broad company analysis as a bounded business-overview evidence need;
- fresh business-overview probes require an organization scope instead of silently reusing stale context;
- planner output can select `business_overview` as the catalog top match with structured alignment telemetry;
- the pilot executor exposes `business_overview_route_template_v1` as the stable runtime scope for the next bridge.
This slice was deliberately only route fabric: it made the reviewed route visible without pretending fresh runtime evidence existed yet.
The assistant now has the route-fabric contract used by Slice 3.
## Slice 3 - Business Overview Fresh Multi-Probe Runtime Bridge
This slice makes the reviewed `business_overview` route execute as a bounded multi-probe MCP discovery bridge.
Implemented now:
- broad company-analysis turn meaning routes into `business overview evidence with bounded analyst interpretation` instead of being kept only in deterministic living chat;
- the bridge runs fresh scoped probes for incoming customer money flow, outgoing supplier payouts, and activity-window evidence;
- the pilot derives checked incoming total, checked outgoing total, net cash-flow spread, net direction, top confirmed customer, and activity window;
- the answer adapter turns those derived facts into a layered analyst-safe draft: confirmed facts, bounded interpretation, unknowns, and next probes;
- response policy can replace the old deterministic broad summary only when the discovery candidate has grounded text, while clarification candidates still preserve the safe deterministic answer;
- user-facing answers must not expose `business_overview_route_template_v1`, MCP primitive names, raw planner/debug labels, or profit/margin claims.
Live semantic replay is now accepted for this slice:
- `address_truth_harness_phase84_business_overview_multi_probe_bridge_live_20260503_runtime_bridge2` passed `3/3`;
- step 1 proves explicit company business overview for `ООО Альтернатива Плюс`;
- step 2 proves an exact `Группа СВК` 2020 net-flow follow-up after the company overview without stale organization-scope contamination;
- step 3 proves returning to `ООО Альтернатива Плюс` business overview after a counterparty pivot without treating net cash-flow as profit.
## Slice 4 - Business Overview VAT/Tax Fact-Family Bridge
This slice adds the first separately checked fact family beyond money-flow/activity.
Implemented now:
- explicit-period business overview can select the reviewed VAT/tax recipe and execute the tax probe beside the money-flow/activity probes;
- the pilot derives sales VAT, purchase/deduction VAT, net VAT direction, and the checked tax period only from confirmed VAT rows;
- the answer adapter can surface VAT/tax position as a confirmed line without treating net cash-flow as profit or margin;
- broad all-time business overview does not silently reuse a prior or negated VAT period;
- wording such as `do not carry VAT for 2020` / `не тащи НДС за 2020` is treated as a temporal exclusion, not as the active period for the current turn;
- organization extraction strips trailing all-time/business-overview clauses so the company scope remains the company name, not the whole user sentence.
Live semantic replay is accepted for this slice:
- `address_truth_harness_phase85_business_overview_tax_family_live_20260504_taxfamily2` passed `2/2`;
- step 1 proves explicit 2020 business overview may include a confirmed VAT/tax position;
- step 2 proves all-time follow-up over the same company does not reuse the 2020 VAT/tax position from the prior turn or from the negated wording;
- the accepted debug path shows `explicit_date_scope=null`, `tax_position=null`, and `pilot_business_overview_tax_probe_skipped_without_explicit_period` for the all-time follow-up.
## Slice 5 - Business Overview Debt-Position Fact-Family Bridge
This slice adds a second separately checked fact family, but deliberately stops short of debt quality.
Implemented now:
- explicit-period business overview can derive an as-of-date from the user-visible period and execute reviewed receivables/payables snapshot recipes beside money-flow/activity and optional VAT/tax probes;
- the pilot derives receivables, payables, net debt-position amount, net direction, and top debt-side counterparties only from confirmed 1C balance rows;
- the answer adapter can surface the debt-position snapshot as a confirmed line without treating it as overdue debt, debt aging, credit quality, profit, or margin;
- all-time business overview does not reuse the prior as-of-date debt snapshot and instead keeps debt position as an unknown fact family until the user gives a new explicit date;
- raw organization extraction now strips trailing explicit period clauses such as `за 2020 год` / `на 2020-12-31`, so the company scope remains the company name and the period remains a separate temporal axis.
Live semantic replay is accepted for this slice:
- `address_truth_harness_phase86_business_overview_debt_position_live_20260504_debt2` passed `2/2`;
- step 1 proves explicit 2020 business overview may include a confirmed receivables/payables debt-position snapshot on `2020-12-31`;
- step 2 proves an all-time follow-up over the same company does not reuse the `2020-12-31` debt snapshot as current or all-time debt position;
- the accepted debug path shows `organization_scope=ООО Альтернатива Плюс`, `explicit_date_scope=2020`, `debt_position.as_of_date=2020-12-31` for the explicit-period step, and `explicit_date_scope=null`, `debt_position=null`, `pilot_business_overview_debt_probe_skipped_without_explicit_as_of_date` for the all-time follow-up.
## Slice 6 - Business Overview Inventory-Position Fact-Family Bridge
This slice adds a third separately checked fact family, but deliberately stops short of warehouse liquidity or turnover.
Implemented now:
- explicit-date business overview can derive an as-of date and execute reviewed inventory on-hand and optional purchase-date aging probes beside the existing money-flow/activity, VAT/tax, and debt-position probes;
- the pilot derives inventory on-hand rows, rows with amount, rows with quantity, total stock amount, total quantity, top stock items, and optional purchase-date aging signal only from confirmed 1C inventory rows;
- the answer adapter can surface the inventory-position snapshot as a confirmed line without treating it as turnover, obsolescence, liquidation value, full inventory health, profit, or margin;
- if the current business-overview slice has no incoming/outgoing money rows, the answer no longer emits a fake `net 0` cash-flow interpretation;
- business-overview headlines now list only the fact families that were actually confirmed in the current turn, so a stock/debt/tax snapshot does not claim money-flow or activity when those rows were absent;
- all-time business overview does not reuse a prior as-of-date inventory snapshot and instead keeps stock/inventory position as an unknown fact family until the user gives a new explicit date.
Live semantic replay is accepted for this slice:
- `address_truth_harness_phase87_business_overview_inventory_position_live_20260504_inventory2` passed `2/2`;
- step 1 proves explicit-date business overview may include a confirmed warehouse stock snapshot on `2026-04-16`;
- step 2 proves an all-time follow-up over the same company does not reuse the `2026-04-16` stock snapshot as current or all-time warehouse position;
- the accepted user-facing answer confirms stock amount `716 418,33` rub. over `11` rows and keeps warehouse liquidity/turnover as unconfirmed.
## Slice 7 - Business Overview Open-Settlement Quality Bridge
This slice widens the debt family from a simple receivables/payables position into a bounded quality signal, but deliberately stops short of due-date aging or confirmed overdue debt.
Implemented now:
- explicit-period business overview can execute the reviewed `open_contracts_confirmed_as_of_date` balance recipe beside money-flow/activity, VAT/tax, debt-position, and inventory probes;
- the pilot derives gross open settlement amount, unique open-contract count, unique counterparty count, top open contracts, top counterparties, and concentration percentages only from confirmed 1C balance rows on 60/62/76;
- the answer adapter can surface this as `качество открытых расчетов` without treating concentration as contractual due-date aging, confirmed overdue debt, credit risk, profit, or margin;
- all-time business overview keeps open-settlement quality unknown unless the current turn has a fresh explicit as-of date;
- the remaining hard boundary is still due-date/overdue aging: open contracts prove concentration of open balances, not payment-term delinquency.
Live semantic replay is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts`: passed `31/31`;
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `34/34` with `1` skipped;
- `npm.cmd test -- assistantMcp`: passed `305/305` with `9` skipped;
- `npm.cmd run build`: passed;
- `address_truth_harness_phase88_business_overview_open_settlement_quality_live_20260504_openquality4` passed `2/2`;
- step 1 proves explicit 2020 business overview may include confirmed open-settlement concentration: `35 472 380,36` rub. gross open contract balances, `8` contracts, `11` counterparties, and top contract share `54.2%`;
- step 2 proves an all-time follow-up does not reuse the `2020-12-31` open-contract/debt snapshot as current overdue debt or all-time debt quality;
- the accepted user-facing answer keeps due-date aging and overdue debt as unconfirmed because open contracts prove concentration of balances, not payment-term delinquency.
## Slice 8 - Selected-Item Profitability Route Bridge
This slice opens a bounded profitability lane where the evidence is concrete enough: not profit for the whole company, but revenue, purchase-cost proxy, spread, and margin proxy for one selected inventory item.
Implemented now:
- `inventory_profitability_for_item` has a dedicated exact recipe, route expectation, capability policy entry, and runtime contract;
- the recipe reuses the reviewed purchase/sale document union over `41.01`, so the answer is grounded in purchase and sale document rows rather than generic LLM inference;
- selected-object wording such as `по выбранному объекту ... сколько заработали` now keeps the selected item and carries the prior inventory period when the user did not give a new date;
- the reply builder computes confirmed sales revenue, purchase-document cost proxy, gross spread, margin-to-revenue, and markup-to-purchase-cost;
- the user-facing answer explicitly says that this is not company net profit or a бухгалтерский финрезультат, and that exact cost of sale still requires stronger lot/management-accounting evidence.
This does not close broad company-wide profit/margin analysis. It only converts one previously visible recipe gap into a bounded, truthful exact route for selected-item unit economics.
Local validation is accepted for this slice:
- `npm.cmd test -- addressInventoryProfitabilitySelectedObjectRegression.test.ts`: passed `2/2`;
- `npm.cmd test -- addressCapabilityPolicy.test.ts assistantRuntimeContractRegistry.test.ts`: passed `31/31`;
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412/412`;
- `npm.cmd run build`: passed.
## Slice 9 - Business Overview Contract-Date Debt Age Signal Bridge
This slice deepens the open-settlement quality family without crossing the evidence boundary into contractual overdue debt.
Implemented now:
- `debt_open_settlement_quality` now carries an optional `age_signal` derived only from dates found inside confirmed open-contract rows;
- the pilot extracts contract-like dates from contract/document/analytics text, normalizes them to ISO dates, and computes a max age in days against the explicit as-of date;
- the derived signal reports oldest/latest contract date, contracts with known start dates, and top aged contracts by age and amount;
- the answer adapter surfaces this as `возрастной сигнал открытых расчетов`, not as payment-term delinquency, credit risk, or confirmed overdue debt;
- `debt_due_date_aging_quality` remains a missing family, because dates embedded in contract names prove contract age, not contractual payment due dates.
Local validation is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `65/65` with `1` skipped.
## Slice 10 - Business Overview Analyst Synthesis Layer
This slice improves answer usefulness after the fact families have been checked.
It does not add a new 1C fact route. It upgrades the response draft so a broad company-analysis answer reads like a bounded analyst note instead of a metric list.
Implemented now:
- the answer adapter emits multiple business-overview inference lines: checked operating scale, incoming/outgoing net direction, customer concentration, debt/tax/stock risk contours, and a concise `Сводный LLM-аудит`;
- customer concentration is computed only from confirmed top-customer and incoming-flow rows;
- debt, open-settlement, contract-age, tax, and inventory signals are summarized only when those families are present in the current derived overview;
- the synthesis keeps the hard boundary that this is not profit, margin, accounting net result, company health, due-date aging, or full inventory liquidity;
- the draft carries a stable `answer_contains_business_overview_analyst_synthesis` reason code for replay review.
Local validation is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `34/34` with `1` skipped.
- `npm.cmd test -- assistantMcp`: passed `305/305` with `9` skipped.
- `npm.cmd run build`: passed.
## Slice 11 - Business Overview Trading Margin Proxy Bridge
This slice opens a company-period trading-margin proxy lane where the evidence is concrete enough, while keeping the accounting boundary explicit.
It does not claim clean profit, бухгалтерский финрезультат, or exact cost-of-sales margin.
Implemented now:
- `inventory_trading_margin_proxy_for_organization` exists as a bounded recipe over the reviewed purchase/sale document union on `41.01`;
- explicit-period business overview can execute this document probe beside money-flow/activity, VAT/tax, debt-position, open-settlement quality, and inventory probes;
- the pilot derives sales revenue, purchase-document cost proxy, gross spread proxy, margin-to-revenue, markup-to-purchase-cost, and top items only from confirmed товарные document rows;
- the answer adapter surfaces this as `торговый margin proxy`, not as чистая прибыль or accounting margin;
- the unknown family narrows from generic `profit_margin` to `accounting_profit_margin` when the proxy exists, so the user sees both what is now supported and what remains unproved;
- all-time business overview does not silently reuse the prior period's trading proxy, because the probe requires a fresh explicit period window.
This is a material breadth step for broad company analysis, but it is still not full company-wide profit analysis. Exact profit/margin still needs reviewed evidence for себестоимость продаж, expenses, period closing, and accounting result.
Local validation is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts addressQueryRuntimeM23.test.ts`: passed `478` with `1` skipped.
- `npm.cmd run build`: passed.
### Still Pending Breadth Slices
Grow this bridge beyond the first confirmed signal bundle:
- add separate evidence families for exact company-wide accounting profit/margin and due-date debt aging/overdue quality where reviewed closing-cost and due-date/payment-term routes exist;
- extend inventory evidence beyond stock position, sales-to-stock proxy, and purchase-date staleness risk proxy into full FIFO turnover, confirmed obsolete-stock/liquidity, reserves, write-offs, or liquidation value only when reviewed evidence exists;
- upgrade debt evidence beyond as-of-date position, open-settlement concentration, contract-date age, and debt staleness risk proxy into overdue aging only when reviewed due-date or payment-term aging evidence exists;
- extend VAT/tax beyond explicit-period tax position only when the requested tax fact is provable and the period is explicit;
- keep Post-F stale-scope and phase83 catalog-alignment canaries green while widening the route.
## Acceptance Signals
The slice is healthy when:
- broad analysis wording lands on the business-overview contour;
- the answer is materially more informative than a generic recap;
- confirmed metrics are visibly separated from LLM-style interpretation;
- profit/margin are not claimed without supporting evidence, and trading-margin proxy is visibly bounded away from clean profit/accounting финрезультат;
- Post-F stale-scope and phase83 catalog-alignment canaries remain green.
## Validation
Initial local validation:
- `npm.cmd test -- assistantTurnMeaningPolicy.test.ts assistantLivingChatRuntimeAdapter.test.ts`: passed `20/20`.
- `npm.cmd test -- assistantTurnMeaningPolicy.test.ts assistantLivingChatRuntimeAdapter.test.ts assistantRoutePolicy.test.ts assistantMcpDiscoveryResponsePolicy.test.ts`: passed `56/56`.
- `npm.cmd run build`: passed.
- graphify rebuild at Slice 2 boundary: `5977 nodes`, `12983 edges`, `137 communities`.
Business-overview route-fabric validation:
- `npm.cmd test -- assistantMcpCatalogIndex.test.ts assistantMcpDiscoveryDataNeedGraph.test.ts assistantMcpDiscoveryPlanner.test.ts assistantMcpDiscoveryPilotExecutor.test.ts`: passed `102/102`.
Business-overview fresh runtime bridge validation:
- `npm.cmd test -- assistantMcpDiscoveryRuntimeEntryPoint.test.ts assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts assistantMcpDiscoveryTurnInputAdapter.test.ts assistantMcpDiscoveryResponsePolicy.test.ts assistantMcpDiscoveryPlanner.test.ts`: passed `211/211` with `9` skipped.
- `npm.cmd test -- assistantMcp`: passed `296/296` with `9` skipped.
- `npm.cmd run build`: passed.
- live replay `address_truth_harness_phase84_business_overview_multi_probe_bridge_live_20260503_runtime_bridge2`: accepted `3/3` with `catalog_alignment_ok=true`, `human_answer_quality_ok=true`, and no internal route/debug terms in the user-facing answer.
Business-overview VAT/tax fact-family validation:
- `npm.cmd test -- assistantMcpDiscoveryTurnInputAdapter.test.ts`: passed `76/76` with `6` skipped.
- `npm.cmd test -- assistantMcp`: passed `300/300` with `9` skipped.
- `npm.cmd run build`: passed.
- live replay `address_truth_harness_phase85_business_overview_tax_family_live_20260504_taxfamily2`: accepted `2/2` and proved that a negated 2020 VAT period is not reused as an all-time tax position.
Business-overview debt-position fact-family validation:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts`: passed `30/30`.
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `33/33` with `1` skipped.
- `npm.cmd test -- assistantMcpDiscoveryTurnInputAdapter.test.ts`: passed `77/77` with `6` skipped.
- `npm.cmd test -- assistantMcp`: passed `303/303` with `9` skipped.
- `npm.cmd run build`: passed.
- live replay `address_truth_harness_phase86_business_overview_debt_position_live_20260504_debt2`: accepted `2/2` and proved that explicit debt-position snapshots do not leak into all-time follow-ups.
Business-overview inventory-position fact-family validation:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts`: passed `31/31`.
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `34/34` with `1` skipped.
- `npm.cmd test -- assistantMcp`: passed `305/305` with `9` skipped.
- `npm.cmd run build`: passed.
- live replay `address_truth_harness_phase87_business_overview_inventory_position_live_20260504_inventory2`: accepted `2/2` and proved that explicit inventory-position snapshots do not leak into all-time follow-ups.
Graphify rebuild after Slice 6 code/doc sync: `6001 nodes`, `13058 edges`, `140 communities`.
Graphify rebuild after Slice 7 code/doc sync: `6008 nodes`, `13078 edges`, `138 communities`.
Selected-item profitability route validation:
- `npm.cmd test -- addressInventoryProfitabilitySelectedObjectRegression.test.ts`: passed `2/2`.
- `npm.cmd test -- addressCapabilityPolicy.test.ts assistantRuntimeContractRegistry.test.ts`: passed `31/31`.
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412/412`.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 8 code/doc sync: `6012 nodes`, `13086 edges`, `138 communities`.
Contract-date debt age signal validation:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `65/65` with `1` skipped.
- `npm.cmd test -- assistantMcp`: passed `305/305` with `9` skipped.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 9 code/doc sync: `6016 nodes`, `13098 edges`, `139 communities`.
Business-overview analyst synthesis validation:
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `34/34` with `1` skipped.
- `npm.cmd test -- assistantMcp`: passed `305/305` with `9` skipped.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 10 code/doc sync: `6023 nodes`, `13112 edges`, `136 communities`.
Business-overview trading-margin proxy validation:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts addressQueryRuntimeM23.test.ts`: passed `478` with `1` skipped.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 11 code/doc sync: `6028 nodes`, `13131 edges`, `137 communities`.
Business-overview inventory sales velocity proxy validation:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `66` with `1` skipped.
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412`.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 12 code/doc sync: `6030 nodes`, `13136 edges`, `137 communities`.
## Slice 12 - Business Overview Inventory Sales Velocity Proxy Bridge
This slice converts an existing missing family into a bounded cross-signal proxy.
It uses only already checked fact families:
- inventory on-hand amount on an explicit as-of date;
- товарные sales document revenue for the same explicit period.
Implemented now:
- the pilot derives `inventory_turnover_proxy` only when both `inventory_position` and `trading_margin_proxy` are present in the current business-overview turn;
- the proxy reports sales revenue, stock amount, sales-to-stock ratio, and stock-to-sales percentage;
- the answer adapter surfaces this as `оборотный proxy склада`, not as full inventory liquidity, FIFO turnover, obsolescence analysis, or liquidation value;
- when this proxy exists, the missing family narrows from `inventory_turnover_quality` to `inventory_liquidity_quality`;
- all-time business overview still does not reuse stale stock or sales-window evidence.
This is a useful management signal for broad company analysis, but it is not a complete warehouse-liquidity conclusion. Full inventory quality still needs reviewed evidence for FIFO/lot turnover, aging/obsolescence, reserves, and liquidation value.
Local validation is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `66` with `1` skipped.
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412`.
- `npm.cmd run build`: passed.
## Slice 13 - Business Overview Inventory Staleness Risk Proxy Bridge
This slice adds one more bounded warehouse-quality signal without pretending that the assistant now knows true obsolete stock, reserves, write-offs, or liquidation value.
It uses only evidence already materialized in the same current business-overview turn:
- `inventory_position.aging_signal.oldest_purchase_date` and `max_age_days`;
- `inventory_turnover_proxy.sales_to_stock_amount_ratio`.
Implemented now:
- the pilot derives `inventory_staleness_risk_proxy` only when current-turn inventory position, purchase-date age, and sales-to-stock proxy are all present;
- the proxy reports as-of date, period scope, oldest purchase signal date, max purchase age in days, sales-to-stock ratio, and one of four bounded bands: `lower_visible_risk`, `watch`, `elevated`, `high`;
- the answer adapter surfaces this as `staleness risk proxy склада`, explicitly not as confirmed неликвидность, reserve, write-off, or liquidation value;
- when this proxy exists, the missing inventory family narrows again from generic liquidity quality toward `inventory_reserve_liquidation_quality`;
- all-time business overview still does not reuse stale stock, sales-window, or purchase-age evidence.
This is a management risk indicator, not an accounting conclusion. It helps the broad company overview say "there is visible warehouse staleness pressure" when the evidence supports that, while keeping formal inventory quality facts out of the answer until a reviewed route proves them.
Local validation is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `66` with `1` skipped.
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412`.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 13 code/doc sync: `6034 nodes`, `13145 edges`, `136 communities`.
## Slice 14 - Business Overview Gap-Specific Headline And Next-Step Precision
This slice does not add a new 1C evidence route. It tightens the user-facing semantics after Slice 13 so the assistant does not keep speaking in older broad gap labels after the runtime has already narrowed the evidence boundary.
Implemented now:
- the business-overview headline now describes the remaining inventory gap by the deepest confirmed state:
- no stock signal -> `склад`;
- stock only -> `полноценная складская ликвидность`;
- sales-to-stock proxy -> `FIFO-оборачиваемость/подтвержденная складская ликвидность`;
- staleness-risk proxy -> `резервы/списания/ликвидационная стоимость склада`;
- the business-overview next-step line is generated from `missing_signal_families` instead of a generic "profit/debt/VAT/warehouse" template;
- remaining checks are listed as concrete future work and explicitly separated from already confirmed overview facts.
This is a semantic-integrity hardening slice inside the breadth module. It makes broad company-analysis answers safer for human review because the answer now explains exactly what is still missing instead of implying that the whole warehouse/liquidity domain remains equally unknown after partial proxies were proven.
Local validation is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `34` with `1` skipped.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 14 code/doc sync: `6036 nodes`, `13149 edges`, `134 communities`.
## Slice 15 - Business Overview Debt Staleness Risk Proxy Bridge
This slice adds a bounded management risk signal for open settlements without crossing into contractual delinquency.
It uses only already reviewed current-turn evidence:
- open-contract balance rows on 60/62/76;
- contract-date age extracted from those open-settlement rows;
- concentration of the oldest large open contract inside gross open balances.
Implemented now:
- the pilot derives `debt_staleness_risk_proxy` only when open-settlement quality and contract-date age are both present in the current business-overview turn;
- the proxy reports as-of date, gross open amount, oldest contract start date, max contract age, oldest large contract, its counterparty, amount, share of gross open balances, and a bounded risk band: `lower_visible_risk`, `watch`, `elevated`, or `high`;
- the answer adapter surfaces this as `staleness risk proxy открытых расчетов`, explicitly not as confirmed overdue debt, contractual delinquency, credit risk, or due-date aging;
- the headline narrows the remaining debt gap to `договорные сроки оплаты/due-date просрочка` when this proxy exists;
- M23 stayed green, including open-contract, stale-contract, VAT-after-contract, and follow-up carryover tests.
This is the debt-side counterpart to the inventory staleness proxy: it gives a useful analyst warning when old contract-date signals and concentration are visible, but it still leaves true due-date/payment-term aging as pending reviewed route work.
Local validation is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `66` with `1` skipped.
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412`.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 15 code/doc sync: `6040 nodes`, `13158 edges`, `135 communities`.
## Slice 16 - Business Overview Supplier Concentration Proxy Bridge
This slice fills a low-risk but important analyst gap in the broad company overview.
Before this slice, `business_overview` could name the largest confirmed customer from incoming payment rows, but the outgoing side only showed total supplier payout. That made company analysis asymmetrical: the assistant could discuss customer concentration but could not say whether outgoing payments were concentrated on one supplier/recipient.
Implemented now:
- the pilot reuses the reviewed `deriveRankedValueFlow` aggregation on the outgoing supplier-payout result;
- `derived_business_overview.top_suppliers` now carries the top confirmed outgoing counterparties from the current-turn rows;
- evidence and answer drafting surface the largest confirmed supplier/recipient and the share of checked outgoing payment flow;
- the wording is explicitly bounded as `procurement concentration proxy`, not vendor risk, procurement quality, or full expense structure;
- reason codes now expose both pilot-side and answer-side supplier concentration signals for replay review.
This is intentionally not a new due-diligence domain. It is a management concentration signal derived from rows already fetched by the existing business-overview runtime bridge.
Local validation is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `66` with `1` skipped.
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412`.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 16 code/doc sync: `6041 nodes`, `13162 edges`, `136 communities`.
## Slice 17 - Business Overview Yearly Operating-Flow Proxy Bridge
This slice answers a common broad-analysis expectation without crossing into unsupported accounting profit.
User wording such as "какой самый доходный год" can mean profit, revenue, cash inflow, or operating scale. Until a reviewed P&L/profit route exists, the safe answer is to compute only what the current business-overview runtime actually proves: annual incoming payment flow, annual outgoing payment flow, and annual net over confirmed rows.
Implemented now:
- the pilot derives `yearly_breakdown` from the same confirmed incoming/outgoing money-flow rows already fetched by `business_overview`;
- each bucket contains year, incoming total, outgoing total, row counts, calculated net, human-readable amounts, and net direction;
- evidence and answer drafting can name the strongest year by confirmed incoming receipts and the best year by calculated operating net;
- headline/reason-code surfaces expose this as yearly operating-flow dynamics for semantic replay;
- answer boundaries explicitly say this is `operating-flow proxy`, not profit, финрезультат, or a complete annual P&L.
This improves management usefulness for broad company analysis while preserving the hard boundary that exact company-wide accounting profit still needs separately reviewed closing, cost, expense, and P&L evidence.
Local validation is accepted for this slice:
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `66` with `1` skipped.
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412`.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 17 code/doc sync: `6047 nodes`, `13177 edges`, `139 communities`.

View File

@ -36,12 +36,40 @@ This package answers the next question:
16. [16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md](./16%20-%20data_need_graph_and_open_world_mcp_plan_2026-04-22.md)
17. [17 - post_f_semantic_integrity_hardening_2026-04-23.md](./17%20-%20post_f_semantic_integrity_hardening_2026-04-23.md)
18. [18 - post_f_code_documentation_sync_2026-04-24.md](./18%20-%20post_f_code_documentation_sync_2026-04-24.md)
19. [19 - inventory_stock_open_world_breadth_proof_2026-05-01.md](./19%20-%20inventory_stock_open_world_breadth_proof_2026-05-01.md)
20. [20 - planner_autonomy_consolidation_2026-05-01.md](./20%20-%20planner_autonomy_consolidation_2026-05-01.md)
21. [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)
22. [22 - open_world_bounded_autonomy_breadth_2026-05-01.md](./22%20-%20open_world_bounded_autonomy_breadth_2026-05-01.md)
## Current Status Snapshot (2026-04-24)
## Current Status Snapshot (2026-05-04)
This package is no longer planning-only.
It now documents a turnaround that is already operational in code, already materially past the acute regression breakpoint, and already moved through the bounded MCP autonomy build-out into the next semantic hardening layer:
Status canon for planning:
- Post-F Semantic Integrity Hardening is operationally closed at `99%` and should now be used as a regression gate, not as the active module denominator.
- Planner Autonomy Consolidation is closed at `100%` for the declared phase83 planner-brain slice.
- The active next module is now `Open-World Bounded Autonomy Breadth` over unfamiliar 1C asks, with Post-F and phase83 retained as semantic canaries.
- The first active slice is `Business Overview Evidence Fusion`: broad company-analysis wording now produces a richer evidence-grounded business overview from confirmed MCP/session facts instead of a thin generic summary.
- The current completed slice is `Business Overview Fresh Multi-Probe Runtime Bridge`: `business_overview` is now a reviewed catalog/data-need/planner chain and a live-replay accepted runtime bridge over incoming money flow, outgoing supplier payouts, activity-window evidence, net-spread, top customer, and analyst-safe answer drafting.
- The current completed breadth slice is `Business Overview VAT/Tax Fact-Family Bridge`: explicit-period business overview can include confirmed VAT/tax position, while all-time follow-ups and negated VAT period wording do not reuse stale tax scope.
- The current completed breadth slice is `Business Overview Debt-Position Fact-Family Bridge`: explicit-period business overview can include confirmed receivables/payables as-of-date debt position, while all-time follow-ups do not reuse stale debt snapshots and debt quality/aging remains unclaimed.
- The current completed breadth slice is `Business Overview Inventory-Position Fact-Family Bridge`: explicit-date business overview can include confirmed stock-on-hand inventory position, while all-time follow-ups do not reuse stale inventory snapshots and inventory liquidity/turnover remains unclaimed.
- The current completed breadth slice is `Business Overview Open-Settlement Quality Bridge`: explicit-period business overview can check open-contract settlement concentration, while due-date aging and confirmed overdue debt remain outside the answer until a reviewed due-date route exists.
- The current completed breadth slice is `Selected-Item Profitability Route Bridge`: selected-object inventory profitability now has a bounded exact route over purchase/sale document rows and reports gross spread/margin proxy without claiming company net profit.
- The current completed breadth slice is `Business Overview Contract-Date Debt Age Signal Bridge`: explicit-period open-settlement quality can include contract-date age as a bounded signal, while due-date aging/overdue debt still waits for reviewed payment-term evidence.
- The current completed breadth slice is `Business Overview Analyst Synthesis Layer`: broad company-analysis answers now synthesize checked fact families into operating scale, customer concentration, risk contours, and a concise bounded LLM-audit.
- The current completed breadth slice is `Business Overview Trading Margin Proxy Bridge`: explicit-period company analysis can now include товарный sales-vs-purchase document proxy for revenue, purchase-cost trace, gross spread, and margin proxy, while clean profit/accounting финрезультат remains unclaimed.
- The current completed breadth slice is `Business Overview Inventory Sales Velocity Proxy Bridge`: when explicit-period stock and sales evidence are both present, company analysis can include a bounded sales-to-stock proxy while full FIFO/liquidity/obsolescence remains unclaimed.
- The current completed breadth slice is `Business Overview Inventory Staleness Risk Proxy Bridge`: when current-turn stock aging and sales-to-stock evidence are both present, company analysis can include a bounded staleness-risk proxy while confirmed obsolete stock, reserves, write-offs, and liquidation value remain unclaimed.
- The current completed breadth slice is `Business Overview Gap-Specific Headline And Next-Step Precision`: business-overview answers now name remaining unchecked families from `missing_signal_families` instead of falling back to stale generic gap wording.
- The current completed breadth slice is `Business Overview Debt Staleness Risk Proxy Bridge`: when current-turn open-settlement concentration and contract-date age are both present, company analysis can include a bounded debt staleness-risk proxy while confirmed overdue debt, contractual delinquency, credit risk, and due-date aging remain unclaimed.
- The current completed breadth slice is `Business Overview Supplier Concentration Proxy Bridge`: company analysis now ranks confirmed outgoing payment counterparties and surfaces supplier/procurement concentration as a bounded proxy, not as vendor risk or full expense structure.
- The current completed breadth slice is `Business Overview Yearly Operating-Flow Proxy Bridge`: company analysis now builds annual incoming/outgoing/net buckets from confirmed money-flow rows and names strongest years as operating-flow proxy, not profit or full P&L.
- The next active breadth slice continues breadth into exact company-wide accounting profit/margin, real due-date debt aging, confirmed reserve/write-off/liquidation inventory evidence, and broader unfamiliar 1C route families without relaxing truth boundaries.
- The short source of truth for status wording is [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md).
It now documents a turnaround that is already operational in code, already materially past the acute regression breakpoint, and already moved through bounded MCP autonomy, Post-F hardening, inventory breadth proof, and the declared Planner Autonomy slice:
- route, transition, boundary, meta, memory, and provider policy owners exist as separate modules;
- exact-lane truth and coverage/evidence contracts exist as explicit runtime artifacts;
@ -53,7 +81,7 @@ It now documents a turnaround that is already operational in code, already mater
- `Question -> Data Need Graph`
- dynamic schema traversal and primitive search
- multi-hop evidence loop with bounded clarification recovery
- the current architecture mainline is now `Post-F Semantic Integrity Hardening`:
- the Post-F semantic integrity layer is now a closed regression-gate layer:
- protect grounded subject integrity against stale scope contamination
- protect exact and planner-selected pivots from metadata/discovery drift
- keep temporal continuity and repeated lane switches semantically stable
@ -63,19 +91,55 @@ It now documents a turnaround that is already operational in code, already mater
- runtime artifact tail commit: `837e1fe Post-F: сохранить хвосты ручных runtime-прогонов`
- live map sync: [18 - post_f_code_documentation_sync_2026-04-24.md](./18%20-%20post_f_code_documentation_sync_2026-04-24.md)
- the next bounded breadth slice is now replay-backed under Post-F gates:
- inventory stock/provenance/sale-trace pack: `inventory_stock_open_world_breadth_rerun_semantic_integrity_20260501_fix5`, accepted
- live map sync: [19 - inventory_stock_open_world_breadth_proof_2026-05-01.md](./19%20-%20inventory_stock_open_world_breadth_proof_2026-05-01.md)
- Planner Autonomy Consolidation is now closed for the declared phase83 slice beyond the initial catalog-template cut:
- MCP catalog now carries reusable chain templates in addition to primitive contracts;
- planner route-fabric selection has started moving from local recipe branches toward catalog-instantiated chains;
- value-flow, value-flow comparison, value-flow ranking, lifecycle, metadata, movement, document, and entity fallback branches now expose catalog-template instantiation reason codes in planner output;
- explicit document/movement data-need now scores over ambiguous carried metadata surfaces without forcing neutral follow-ups into a lane;
- lifecycle now behaves as a bounded activity-window inference chain with an explicit legal-fact boundary instead of an unqualified age answer;
- current-turn value-flow aggregate questions can override narrower supported exact routes when the user asks for totals/net/payment amounts;
- broad business evaluation remained guarded during phase83 and is now carried forward in the next breadth module as a reviewed `business_overview` discovery route instead of being displaced by generic metadata discovery;
- inventory stock snapshot, supplier overlap, purchase provenance, and sale trace are now reviewed catalog chain templates; generic free-form inventory execution remains forbidden, and evidence must pass through reviewed exact recipe bridges;
- runtime bridge and answer adapter now keep unsupported inventory route templates behind an explicit user-facing boundary instead of letting template planning look like confirmed stock/supplier/purchase/sale evidence;
- inventory catalog templates now bridge through existing exact inventory recipes (`41.01` scoped stock, supplier overlap, purchase provenance, and sale trace) inside the bounded MCP discovery pilot, while missing selected-item anchors still clarify instead of guessing;
- unambiguous metadata surfaces can now infer the next reviewed lane from `Document.*`, `Register.*`, or `Catalog.*` objects even before upstream labels `downstream_route_family`, while mixed surfaces still do not guess;
- catalog index now scores reviewed chain templates directly from fact/action/axis/comparison/ranking needs, and planner/runtime/debug surfaces expose ranked catalog chain matches through the structured `catalog_chain_template_matches` contract path instead of relying only on reason-code strings;
- planner/runtime/debug surfaces now expose `catalog_chain_template_alignment`, so semantic replay can see whether selected chains match the catalog top match, fall back to a lower-ranked template, or bypass catalog search;
- planner reason codes now also emit stable catalog-alignment telemetry, so automated replay review can filter top-match, lower-rank, outside-match, and unscored selected-chain states without hand-parsing debug JSON;
- catalog-alignment now carries a single `alignment_status` verdict through planner/runtime/debug, making replay divergence detection explicit instead of reconstructing it from booleans;
- truth-harness and scenario acceptance artifacts now preserve catalog-alignment status/top-match fields, so AGENT replay review can spot planner-vs-catalog divergence directly in `truth_review.md` and `scenario_acceptance_matrix.json`;
- truth-harness now emits a warning finding when selected chains fall below or outside the reviewed catalog top match, unless a spec explicitly allows that divergence;
- scenario acceptance now exposes `catalog_alignment_ok`, so planner-vs-catalog divergence is a first-class acceptance invariant instead of an ungrouped warning;
- truth-harness specs can now assert expected catalog-alignment status/top-match/top-flag per step, so AGENT packs can validate the planner brain's selected chain against the reviewed catalog route fabric;
- the phase66 open-scope money dialog spec now asserts expected catalog-chain top matches across value-flow totals, bidirectional comparison, and ranking follow-ups;
- the phase32 selected-counterparty chain spec now asserts expected catalog-chain top matches across entity grounding, incoming/outgoing/net value-flow, document evidence, and movement evidence follow-ups;
- AGENT semantic source catalog generation now preserves expected catalog-alignment fields and tags reusable steps as `planner_catalog_alignment`, so mixed pack construction can find planner-brain regression probes explicitly;
- phase83 planner-brain mixed replay spec is now generated from the AGENT source catalog and interleaves selected-counterparty catalog alignment, open-organization money flow/ranking, broad-evaluation continuity, metadata drilldown, and off-domain living-chat safety;
- phase83 live replay now accepts `20/20` under guarded MCP live-readiness and proves catalog-alignment, direct-answer, temporal honesty, selected-object continuity, truth-gate, human-answer-quality, and meta-context invariants together;
- checked-source failure answers now keep raw MCP transport/internal continuation errors out of the user-facing layer while preserving those details in technical debug artifacts;
- explicit-counterparty incoming-vs-outgoing data-need graphs now select the reviewed `value_flow_comparison` chain instead of falling back to generic `value_flow`;
- confirmed metadata-surface follow-ups now promote the surface-grounded chain template to the catalog top match, so neutral catalog drilldowns no longer look like lower-rank planner/catalog divergence;
- live map sync: [20 - planner_autonomy_consolidation_2026-05-01.md](./20%20-%20planner_autonomy_consolidation_2026-05-01.md)
Current honest status:
- turnaround implementation progress: `~99%`
- exit-from-danger-zone readiness: `~97%`
- pre-multidomain readiness: `~90%`
- bounded-autonomy foundation readiness: `~89%`
- open-world bounded-autonomy readiness: `~75%`
- open-world bounded-autonomy readiness: `~87%`
- active Open-World Bounded Autonomy Breadth progress: `~90%`, with business-overview evidence fusion, the reviewed `business_overview` catalog/data-need/planner route-fabric slice, the fresh multi-probe runtime bridge, the explicit-period VAT/tax fact-family bridge, the explicit-period debt-position bridge, the explicit-date inventory-position bridge, the open-settlement quality bridge accepted by live semantic replay, selected-item profitability bridged by local semantic/runtime regression tests, contract-date debt age bridged locally, debt staleness-risk proxy bridged locally, supplier concentration proxy bridged locally, yearly operating-flow proxy bridged locally, analyst synthesis added to business-overview answer drafting, company-period trading margin proxy bridged locally, inventory sales-to-stock proxy bridged locally, inventory staleness-risk proxy bridged locally, and gap-specific answer shaping bridged locally; exact accounting profit/margin, true due-date debt aging/overdue, confirmed vendor-risk/procurement-quality analysis, and confirmed reserve/write-off/liquidation inventory evidence are still pending
- Post-F semantic integrity module progress: `~99%` operationally closed, with remaining risk now treated as next-slice discovery rather than an open blocker inside the closed slice
- graph snapshot after latest rebuild: `5892 nodes`, `12772 edges`, `137 communities`
- current breakpoint:
- active inventory-stock breadth slice progress: `100%` for the declared scenario pack, not for arbitrary inventory questions
- Planner Autonomy Consolidation progress: `100%` for the declared module, with catalog-fabric, value-flow arbitration, lifecycle bounded inference, broad-evaluation bridge, inventory catalog templates, inventory runtime-boundary honesty, exact inventory recipe bridging, unambiguous metadata-surface lane inference, catalog chain-template scoring, structured chain-match contract exposure, runtime/debug propagation, subject-aware bidirectional comparison arbitration, structured catalog-alignment verdicts, representative alignment regression guard, catalog-alignment reason-code telemetry, explicit `alignment_status` propagation, truth-harness/acceptance-matrix surfacing, soft divergence warning, `catalog_alignment_ok` acceptance invariant, step-level expected catalog-alignment assertions, phase66 and phase32 spec alignment expectations, AGENT source-catalog surfacing, generated phase83 mixed planner-brain replay spec, checked-source user-facing error sanitation, surface-grounded catalog promotion, and guarded live phase83 acceptance validated. Broader unfamiliar 1C asks are now next-module breadth work rather than an open blocker inside this declared slice
- graph snapshot after latest rebuild: `6047 nodes`, `13177 edges`, `139 communities`
- current regression-gate breakpoint:
- the validated hot paths are no longer structurally broken;
- flagship continuity collapse is no longer the primary risk;
- the main remaining risk is no longer "A/B/C or D/E/F do not exist", but "already-supported semantic chains can still be contaminated by stale scope, legacy focus state, or wrong post-pivot arbitration";
- the main closed-slice regression risk is no longer "A/B/C or D/E/F do not exist", but "already-supported semantic chains can still be contaminated by stale scope, legacy focus state, or wrong post-pivot arbitration";
- pure wording polish remains secondary debt, but semantic integrity and explicit-subject protection are now first-class blockers;
- the practical product risk is no longer only "the route collapsed", but "the user can still occasionally see a semantically wrong answer on a question that the architecture should already support".
- main remaining architectural pressure:
@ -103,6 +167,53 @@ Latest live proof now includes:
- `address_truth_harness_phase11_manual_followup_meta_quality_live_rerun_vatfix` accepted `10/10`
- `address_truth_harness_phase20_continuity_stabilization_live_rerun_vatfix` accepted `6/6`
- `addressQueryRuntimeM23.test.ts` full semantic/runtime slice accepted `403/403` after Post-F VAT/date-basis, scope-recovery, open value-flow organization clarification, document-vs-bank arbitration, and reply-shape hardening
- `inventory_stock_open_world_breadth_rerun_semantic_integrity_20260501_fix5` accepted all declared inventory stock scenarios: root snapshots, selected-item supplier provenance, supplier overlap, stock aging/unresolved supplier-link, sale trace, and purchase-to-sale chain
- `address_truth_harness_phase66_human_org_open_scope_dialog_planner_template_rerun2` accepted `7/7`, proving catalog-instantiated value-flow still preserves open-organization clarification, year switches, and guarded candidate arbitration
- `address_truth_harness_phase52_metadata_movement_full_recovery_planner_metadata_scoring_rerun2` accepted `4/4`, proving metadata-born movement continuation keeps lane choice, organization, and period recovery intact
- `address_truth_harness_phase54_metadata_document_full_recovery_planner_metadata_scoring_rerun2` accepted `4/4`, proving metadata-born document continuation keeps lane choice, organization, and period recovery intact
- MCP planner/catalog consolidation slice accepted locally: `assistantMcpCatalogIndex.test.ts` + `assistantMcpDiscoveryPlanner.test.ts` passed `47/47`, full MCP-discovery slice passed `227/227` with `9` skipped
- lifecycle/value-flow Planner Autonomy response gate accepted: `address_truth_harness_phase19_mcp_discovery_response_gate_planner_lifecycle_rerun4` accepted `8/8`, proving bounded lifecycle inference, current-turn value-flow aggregate arbitration, and sanitized evidence wording
- broad-evaluation bridge continuity accepted: `address_truth_harness_phase21_net_followup_after_broad_eval_planner_lifecycle_rerun2` accepted `3/3` and `address_truth_harness_phase22_broad_business_evaluation_bridge_planner_lifecycle_rerun2` accepted `3/3`
- latest local Planner Autonomy slice accepted: full MCP-discovery suite passed `268/268` with `9` skipped; broad MCP/living-chat/route/meaning slice passed `305/305` with `9` skipped; build passed
- business-overview route-fabric slice accepted locally: catalog/data-need/planner/pilot boundary slice passed `102/102`, proving the reviewed `business_overview` chain and stable route scope
- business-overview fresh multi-probe runtime bridge accepted locally: targeted runtime-entry/pilot/answer/turn-input/response-policy/planner slice passed `211/211` with `9` skipped; full MCP-discovery suite passed `296/296` with `9` skipped; build passed
- business-overview fresh multi-probe runtime bridge accepted live: `address_truth_harness_phase84_business_overview_multi_probe_bridge_live_20260503_runtime_bridge2` accepted `3/3`, proving explicit company overview, exact counterparty net-flow after the company overview, and explicit company overview after a counterparty pivot with `catalog_alignment_ok=true`, `human_answer_quality_ok=true`, and no internal route/debug leakage in the user-facing answer
- business-overview VAT/tax fact-family bridge accepted live: `address_truth_harness_phase85_business_overview_tax_family_live_20260504_taxfamily2` accepted `2/2`, proving explicit-period VAT/tax position in the company overview and all-time follow-up protection against stale or negated VAT-period reuse
- business-overview debt-position fact-family bridge accepted live: `address_truth_harness_phase86_business_overview_debt_position_live_20260504_debt2` accepted `2/2`, proving explicit-period receivables/payables as-of-date debt position and all-time follow-up protection against stale debt snapshot reuse
- business-overview inventory-position fact-family bridge accepted live: `address_truth_harness_phase87_business_overview_inventory_position_live_20260504_inventory2` accepted `2/2`, proving explicit-date stock-on-hand position and all-time follow-up protection against stale inventory snapshot reuse
- business-overview open-settlement quality bridge accepted live: `address_truth_harness_phase88_business_overview_open_settlement_quality_live_20260504_openquality4` accepted `2/2`, proving explicit-period open-contract concentration and all-time follow-up protection against stale open-contract/debt-quality reuse
- business-overview contract-date debt age signal accepted locally: targeted executor/answer-adapter slice passed `65/65` with `1` skipped; full MCP-discovery slice passed `305/305` with `9` skipped; build passed; graphify rebuilt to `6016 nodes`, `13098 edges`, `139 communities`; contract-date age is surfaced as a bounded signal while due-date aging/overdue debt remains unclaimed
- business-overview analyst synthesis accepted locally: answer-adapter slice passed `34/34` with `1` skipped; full MCP-discovery slice passed `305/305` with `9` skipped; build passed; graphify rebuilt to `6023 nodes`, `13112 edges`, `136 communities`; broad company-analysis drafts now include operating scale, customer concentration, risk contours, and bounded LLM-audit inference lines
- business-overview inventory staleness-risk proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6034 nodes`, `13145 edges`, `136 communities`; the proxy combines stock aging and sales-to-stock ratio while confirmed obsolete stock, reserves, write-offs, and liquidation value remain unclaimed
- business-overview gap-specific answer shaping accepted locally: answer-adapter slice passed `34/34` with `1` skipped; build passed; graphify rebuilt to `6036 nodes`, `13149 edges`, `134 communities`; headline and next-step wording now follow `missing_signal_families` instead of stale generic gap labels
- business-overview debt staleness-risk proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6040 nodes`, `13158 edges`, `135 communities`; the proxy combines contract-date age and open-balance concentration while confirmed overdue debt, contractual delinquency, credit risk, and due-date aging remain unclaimed
- business-overview supplier concentration proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6041 nodes`, `13162 edges`, `136 communities`; the proxy ranks confirmed outgoing payment counterparties while vendor risk, procurement quality, and full expense structure remain unclaimed
- business-overview yearly operating-flow proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6047 nodes`, `13177 edges`, `139 communities`; the proxy builds annual incoming/outgoing/net buckets from confirmed money-flow rows while profit, финрезультат, and full P&L remain unclaimed
- inventory template lift accepted locally: catalog/data-need/planner/turn-input slice passed `139/139` with `6` skipped; full MCP-discovery slice passed `276/276` with `9` skipped; build passed; graphify stayed at `5912 nodes`, `12833 edges`, `138 communities`
- inventory runtime-boundary hardening accepted locally: runtime-bridge/answer-adapter/pilot-executor slice passed `68/68` with `1` skipped; full MCP-discovery slice passed `277/277` with `9` skipped; build passed; graphify rebuilt to `5913 nodes`, `12837 edges`, `138 communities`
- inventory exact-runtime bridge accepted locally: runtime-bridge/answer-adapter/pilot-executor slice passed `70/70` with `1` skipped; full MCP-discovery slice passed `279/279` with `9` skipped; build passed; graphify rebuilt to `5930 nodes`, `12884 edges`, `135 communities`
- unambiguous metadata-surface lane inference accepted locally: planner slice passed `36/36`; full MCP-discovery slice passed `281/281` with `9` skipped; build passed; graphify rebuilt to `5937 nodes`, `12899 edges`, `138 communities`
- live inventory exact-bridge rerun `inventory_stock_exact_bridge_live_20260501_after_runtime_bridge` is recorded as infrastructure-blocked, not accepted: route/intent/recipe/capability matched, but MCP calls aborted and direct `get_metadata` timed out while proxy health showed `active_sessions_count=0` with pending commands
- catalog chain-template scoring accepted locally: catalog/planner slice passed `54/54`; full MCP-discovery slice passed `282/282` with `9` skipped; build passed; graphify rebuilt to `5938 nodes`, `12903 edges`, `139 communities`
- structured chain-template planner contract accepted locally: planner slice passed `36/36`; full MCP-discovery slice passed `282/282` with `9` skipped; build passed; graphify rebuilt to `5939 nodes`, `12906 edges`, `138 communities`
- structured chain-template runtime/debug propagation accepted locally: runtime/debug slice passed `18/18`; full MCP-discovery slice passed `282/282` with `9` skipped; build passed; graphify rebuilt to `5940 nodes`, `12909 edges`, `137 communities`
- subject-aware bidirectional comparison arbitration accepted locally: planner slice passed `36/36`; full MCP-discovery slice passed `282/282` with `9` skipped; build passed; graphify rebuilt to `5940 nodes`, `12909 edges`, `137 communities`
- structured catalog-alignment verdict accepted locally: planner/runtime/debug slice passed `54/54`; full MCP-discovery slice passed `282/282` with `9` skipped; build passed; graphify rebuilt to `5941 nodes`, `12911 edges`, `136 communities`
- representative catalog-alignment regression guard accepted locally: planner slice passed `37/37`; full MCP-discovery slice passed `283/283` with `9` skipped; build passed; graphify rebuilt to `5942 nodes`, `12912 edges`, `140 communities`
- catalog-alignment reason-code telemetry accepted locally: planner/runtime slice passed `53/53`; full MCP-discovery suite passed `283/283` with `9` skipped; build passed; graphify rebuilt to `5943 nodes`, `12915 edges`, `136 communities`
- catalog-alignment status verdict accepted locally: planner/runtime/debug slice passed `55/55`; full MCP-discovery suite passed `283/283` with `9` skipped; build passed; graphify rebuilt to `5943 nodes`, `12915 edges`, `136 communities`
- catalog-alignment replay artifact surfacing accepted locally: Python truth-harness/acceptance tests passed `4/4`; graphify rebuilt to `5946 nodes`, `12918 edges`, `136 communities`
- catalog-alignment divergence warning accepted locally: Python truth-harness/acceptance tests passed `5/5`; graphify rebuilt to `5947 nodes`, `12920 edges`, `138 communities`
- catalog-alignment acceptance invariant accepted locally: Python truth-harness/acceptance tests passed `6/6`; graphify rebuilt to `5949 nodes`, `12923 edges`, `136 communities`
- catalog-alignment spec assertions accepted locally: Python truth-harness/acceptance tests passed `7/7`; graphify rebuilt to `5951 nodes`, `12926 edges`, `139 communities`
- phase66 planner-alignment spec hardening accepted locally: Python truth-harness/acceptance tests passed `7/7`; `load_truth_harness_spec` confirmed expected top matches `[value_flow, value_flow, value_flow, value_flow_comparison, value_flow_comparison, value_flow_ranking, value_flow_ranking]`
- phase32 selected-counterparty planner-alignment spec hardening and AGENT source-catalog surfacing accepted locally: Python replay-tooling tests passed `9/9`; `load_truth_harness_spec` confirmed expected top matches `[entity_resolution, value_flow, value_flow, value_flow_comparison, document_evidence, movement_evidence]`; regenerated source catalog exposes `planner_catalog_alignment` as a reusable tag
- phase83 mixed planner-brain spec generation accepted locally: Python replay-tooling tests passed `10/10`; generated spec has `20` steps and `15` expected catalog top-match checks after the phase19/21/22 alignment hardening; regenerated source catalog exposes `planner_catalog_alignment` with `26` reusable entries; graphify rebuilt to `5952 nodes`, `12927 edges`, `138 communities`
- checked-source error sanitation accepted: targeted answer/candidate/policy tests passed `61/61` with `1` skipped; build passed; `phase83_first2_sanity_live_20260501_errorfilter` confirms the user-facing assistant section no longer exposes raw `MCP fetch failed` or `Entity-resolution could not continue` strings; graphify rebuilt to `5953 nodes`, `12930 edges`, `137 communities`
- live-readiness guard accepted: `scripts/check_mcp_live_readiness.py --confirm-live --wait-for-polling-seconds 60` now confirms backend/proxy/direct read-only 1C evidence before expensive live replays;
- guarded phase83 acceptance: `phase83_planner_brain_alignment_live_20260501_readygate_rerun3` accepted `20/20`, with `0` warnings, `0` failures, `catalog_alignment_ok=true`, `direct_answer_ok=true`, `temporal_honesty_ok=true`, `selected_object_continuity_ok=true`, `truth_gate_ok=true`, `human_answer_quality_ok=true`, and `meta_context_integrity_ok=true`;
- surface-grounded catalog promotion accepted locally: targeted planner/response-policy/pilot/continuity slice passed `109/109`, build passed, and graphify rebuilt to `5973 nodes`, `12971 edges`, `138 communities`.
- accepted phase83 is saved as an autorun canary: `AGENT | Planner Autonomy phase83: мозг маршрутов, pivots и legacy continuity` (`gen-ag05011759-6f85fc`).
Current architectural reading:
@ -124,6 +235,10 @@ For the detailed audit, current percentages, and remaining debt, read:
- [15 - mcp_bounded_autonomy_reset_plan_2026-04-21.md](./15%20-%20mcp_bounded_autonomy_reset_plan_2026-04-21.md)
- [16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md](./16%20-%20data_need_graph_and_open_world_mcp_plan_2026-04-22.md)
- [17 - post_f_semantic_integrity_hardening_2026-04-23.md](./17%20-%20post_f_semantic_integrity_hardening_2026-04-23.md)
- [19 - inventory_stock_open_world_breadth_proof_2026-05-01.md](./19%20-%20inventory_stock_open_world_breadth_proof_2026-05-01.md)
- [20 - planner_autonomy_consolidation_2026-05-01.md](./20%20-%20planner_autonomy_consolidation_2026-05-01.md)
- [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)
- [22 - open_world_bounded_autonomy_breadth_2026-05-01.md](./22%20-%20open_world_bounded_autonomy_breadth_2026-05-01.md)
## Architectural Objects Of Planning
@ -160,6 +275,10 @@ Read in this order:
17. `16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md`
18. `17 - post_f_semantic_integrity_hardening_2026-04-23.md`
19. `18 - post_f_code_documentation_sync_2026-04-24.md`
20. `19 - inventory_stock_open_world_breadth_proof_2026-05-01.md`
21. `20 - planner_autonomy_consolidation_2026-05-01.md`
22. `21 - current_status_canon_2026-05-01.md`
23. `22 - open_world_bounded_autonomy_breadth_2026-05-01.md`
## Planning Rules
@ -179,7 +298,7 @@ and start being described as:
- "a stateful exact-data assistant with explicit transition contracts and isolated truth gating."
As of `2026-04-24`, the project is already materially closer to the target description and is no longer in the same acute collapse state. The remaining blocker is no longer the original continuity failure itself, no longer the A/B/C or D/E/F build-out, and no longer the first Post-F rescue slice. The active blocker is now the combination of:
As of `2026-05-01`, the project is already materially closer to the target description and is no longer in the same acute collapse state. The remaining blocker is no longer the original continuity failure itself, no longer the A/B/C or D/E/F build-out, no longer the first Post-F rescue slice, and no longer the declared phase83 Planner Autonomy consolidation slice. The active blocker is now the combination of:
- unfinished convergence from reviewed bounded MCP chains toward broader open-world autonomy;
- continued use of Post-F semantic integrity invariants as regression gates while that breadth grows.

View File

@ -1,6 +1,6 @@
{
"schema_version": "address_route_expectations_v1",
"updated_at": "2026-04-14T09:30:00.000Z",
"updated_at": "2026-05-04T00:00:00.000Z",
"entries": [
{
"intent": "payables_confirmed_as_of_date",
@ -44,6 +44,12 @@
"expected_requested_result_modes": ["confirmed_balance"],
"expected_result_modes": ["confirmed_balance"]
},
{
"intent": "inventory_profitability_for_item",
"expected_selected_recipes": ["address_inventory_profitability_for_item_v1"],
"expected_requested_result_modes": ["confirmed_balance"],
"expected_result_modes": ["confirmed_balance"]
},
{
"intent": "inventory_purchase_to_sale_chain",
"expected_selected_recipes": ["address_inventory_purchase_to_sale_chain_v1"],

View File

@ -771,9 +771,7 @@
},
"expected_result_mode": "confirmed_balance",
"required_filters": {
"as_of_date": "2021-09-30",
"period_from": "2021-09-01",
"period_to": "2021-09-30"
"as_of_date": "2021-09-30"
},
"invariant_severity": {
"wrong_as_of_date": "P0",
@ -794,15 +792,11 @@
"source": "binding_target_date_historical"
},
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
"analysis_context": {
"as_of_date": "2021-09-30",
"source": "binding_target_date_current"
},
"expected_result_mode": "confirmed_balance",
"required_filters": {
"as_of_date": "2021-09-30",
"period_from": "2021-09-01",
"period_to": "2021-09-30"
"as_of_date": "2019-03-31",
"period_from": "2019-03-01",
"period_to": "2019-03-31"
},
"invariant_severity": {
"wrong_as_of_date": "P0",
@ -844,8 +838,7 @@
},
"required_filters": {
"as_of_date": "2021-09-30",
"period_from": "2021-09-01",
"period_to": "2021-09-30"
"account": "41"
},
"invariant_severity": {
"wrong_as_of_date": "P0",
@ -1074,9 +1067,7 @@
"organization_scope"
],
"required_filters": {
"as_of_date": "2019-03-31",
"period_from": "2019-03-01",
"period_to": "2019-03-31"
"as_of_date": "2019-03-31"
},
"invariant_severity": {
"wrong_as_of_date": "P0",
@ -1222,9 +1213,24 @@
"step_01_snapshot_historical",
"step_02_selected_item_supplier_ui"
],
"analysis_context": {
"as_of_date": "2019-03-31",
"source": "binding_target_date_historical"
},
"expected_capability": "inventory_purchase_provenance_for_item",
"required_state_objects": [
"focus_object"
],
"required_filters": {
"as_of_date": "2019-03-31"
},
"required_carryover_invariants": [
"selected_object",
"focus_object",
"date_scope",
"reusable_bundle",
"followup_action_resolution"
],
"forbidden_capabilities": [
"confirmed_inventory_on_hand_as_of_date"
],

View File

@ -57,6 +57,9 @@
"step_id": "step_03_counterparty_lifecycle_uses_guarded_discovery",
"title": "Unsupported-but-understood counterparty lifecycle question uses guarded discovery answer",
"question": "сколько лет мы работаем с Группа СВК?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "lifecycle",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)свк",
"(?i)1с|активност|подтвержд",
@ -87,6 +90,9 @@
"step_id": "step_04_counterparty_value_flow_uses_guarded_discovery",
"title": "Unsupported-but-understood counterparty value-flow question uses guarded discovery answer",
"question": "какой денежный поток был у Группа СВК за 2020 год?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)свк",
"(?i)1с|найден|строк|подтвержд",
@ -118,6 +124,9 @@
"step_id": "step_05_counterparty_supplier_payout_uses_guarded_discovery",
"title": "Unsupported-but-understood counterparty payout question uses guarded supplier discovery answer",
"question": "сколько мы заплатили Группа СВК за 2020 год?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)свк",
"(?i)1с|найден|строк|подтвержд",
@ -150,6 +159,9 @@
"step_id": "step_06_counterparty_bidirectional_net_flow_uses_guarded_discovery",
"title": "Unsupported-but-understood counterparty net cash-flow question composes incoming and outgoing discovery",
"question": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)свк",
"(?i)1с|найден|строк|проверен",
@ -182,6 +194,9 @@
"step_id": "step_07_counterparty_bidirectional_monthly_net_flow_uses_guarded_discovery",
"title": "Unsupported-but-understood counterparty monthly net cash-flow question keeps monthly structure from discovery",
"question": "какое помесячное нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили по месяцам?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)свк",
"(?i)1с|найден|строк|проверен",

View File

@ -10,6 +10,9 @@
"step_id": "step_01_company_activity_lifecycle",
"title": "Activity lifecycle answer seeds broad counterparty context",
"question": "а по Альтернативе Плюс сколько лет активности в базе 1С?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "lifecycle",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual",
@ -44,6 +47,9 @@
"step_id": "step_03_net_flow_after_broad_eval",
"title": "Net-flow follow-up overrides stale lifecycle carryover and answers with inflow outflow and net",
"question": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"

View File

@ -10,6 +10,9 @@
"step_id": "step_01_company_activity_lifecycle",
"title": "Lifecycle answer seeds grounded organization context",
"question": "а по Альтернативе Плюс сколько лет активности в базе 1С?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "lifecycle",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual",
@ -51,6 +54,9 @@
"step_id": "step_03_net_flow_after_broad_eval",
"title": "Exact net-flow follow-up still answers after the broad bridge",
"question": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"

View File

@ -11,6 +11,9 @@
"title": "Entity resolution grounds the checked 1C counterparty from a loose alias",
"question": "найди в 1С контрагента СВК",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "entity_resolution",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": ["(?i)свк", "(?i)контрагент"],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
@ -34,6 +37,9 @@
"title": "Incoming value-flow follow-up reuses the resolved counterparty anchor",
"question": "сколько получили по нему за 2020 год",
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": ["(?i)2020", "(?i)получил|входящ|поступ", "(?i)руб"],
"required_answer_patterns_any": ["(?i)группа\\s+свк", "(?i)свк"],
"forbidden_answer_patterns": [
@ -49,6 +55,9 @@
"title": "Outgoing payment follow-up keeps the same grounded counterparty and checked year",
"question": "а теперь сколько заплатили?",
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": ["(?i)2020", "(?i)заплатил|исходящ|списан|платеж", "(?i)руб"],
"required_answer_patterns_any": ["(?i)группа\\s+свк", "(?i)свк"],
"forbidden_answer_patterns": [
@ -65,6 +74,9 @@
"title": "Net-flow follow-up reuses the same grounded counterparty and checked year after payout",
"question": "а какое нетто?",
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": ["(?i)2020", "(?i)нетто|сальдо", "(?i)руб"],
"required_answer_patterns_any": ["(?i)получ", "(?i)заплат", "(?i)группа\\s+свк", "(?i)свк"],
"forbidden_answer_patterns": [
@ -80,6 +92,9 @@
"title": "Document evidence follow-up keeps the grounded counterparty after the net answer",
"question": "а по документам?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "document_evidence",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": ["(?i)документ|счет|накладн|акт"],
"required_answer_patterns_any": ["(?i)группа\\s+свк", "(?i)свк", "(?i)2020"],
"forbidden_answer_patterns": [
@ -98,6 +113,9 @@
"title": "Movement evidence follow-up keeps the grounded counterparty after the document answer",
"question": "а по движениям?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "movement_evidence",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": ["(?i)движени|операц|платеж|списан|поступ"],
"required_answer_patterns_any": ["(?i)группа\\s+свк", "(?i)свк", "(?i)2020"],
"forbidden_answer_patterns": [

View File

@ -11,6 +11,9 @@
"title": "The user asks for incoming money without naming the organization yet",
"question": "Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
"allowed_reply_types": ["clarification_required", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)уточн|нужно",
"(?i)организац"
@ -23,6 +26,9 @@
"title": "The user selects the organization and gets the 2020 incoming total",
"question": "По ООО Альтернатива Плюс.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2020",
"(?i)входящ|поступлен|получ"
@ -40,6 +46,9 @@
"title": "The user broadens the same organization slice to all available time",
"question": "Понял, тогда за все время.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)все доступное время|все время|весь период",
"(?i)входящ|поступлен|получ"
@ -58,6 +67,9 @@
"title": "The user asks which money direction is larger for the organization",
"question": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2020",
"(?i)входящ|исходящ|получ|заплат|больше"
@ -70,6 +82,9 @@
"title": "The user asks the same comparison for another year",
"question": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2021",
"(?i)входящ|исходящ|получ|заплат|больше"
@ -82,6 +97,9 @@
"title": "The user asks who brought the most money for the organization",
"question": "И кто больше всего принес денег этой организации в 2020 году?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_ranking",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2020",
"(?i)кто|контрагент|клиент|принес|доход"
@ -94,6 +112,9 @@
"title": "The user asks the same ranking for another year",
"question": "А в 2021 году?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_ranking",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2021"
],

View File

@ -0,0 +1,595 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase83_planner_brain_alignment_mix",
"domain": "planner_autonomy_consolidation",
"title": "Phase 83 mixed planner-brain replay for catalog alignment, pivots, and legacy continuity",
"description": "Mixed AGENT replay for Planner Autonomy Consolidation. The pack interleaves selected-counterparty catalog-alignment probes, open-organization money flow, ranking, broad-evaluation continuity, metadata drilldown, and off-domain living-chat safety.",
"bindings": {},
"steps": [
{
"step_id": "step_01_human_smalltalk_sanity",
"title": "Human smalltalk remains living chat and does not expose discovery internals",
"question": "привет, ты на связи?",
"required_answer_patterns_any": [
"(?i)привет|на связи|готов|помочь"
],
"forbidden_answer_patterns": [
"(?i)mcp",
"(?i)runtime_",
"(?i)query_documents",
"(?i)primitive"
],
"criticality": "info",
"semantic_tags": [
"human_answer",
"mcp_discovery_gate_sanity",
"meta_smalltalk"
],
"notes": "[mixed_pack_slot=slot_01_smalltalk_sanity source=address_truth_harness_phase19_mcp_discovery_response_gate:step_01_human_smalltalk_sanity]"
},
{
"step_id": "step_01_resolve_counterparty_alias",
"title": "Entity resolution grounds the checked 1C counterparty from a loose alias",
"question": "найди в 1С контрагента СВК",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "entity_resolution",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)свк",
"(?i)контрагент"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)каталог",
"(?i)найден",
"(?i)наиболее вероятн"
],
"forbidden_answer_patterns": [
"(?i)получили",
"(?i)заплатили",
"(?i)нетто",
"(?i)оборот",
"(?i)выручк",
"(?i)сумм(а|ы)"
],
"criticality": "critical",
"semantic_tags": [
"entity_resolution",
"alias_grounding",
"followup_anchor",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_02_counterparty_grounding source=address_truth_harness_phase32_planner_selected_chain_end_to_end:step_01_resolve_counterparty_alias]"
},
{
"step_id": "step_02_incoming_by_resolved_entity",
"title": "Incoming value-flow follow-up reuses the resolved counterparty anchor",
"question": "сколько получили по нему за 2020 год",
"allowed_reply_types": [
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2020",
"(?i)получил|входящ|поступ",
"(?i)руб"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)свк"
],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту"
],
"criticality": "critical",
"semantic_tags": [
"entity_resolution",
"incoming_value_flow",
"followup_reuse",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_03_counterparty_incoming source=address_truth_harness_phase32_planner_selected_chain_end_to_end:step_02_incoming_by_resolved_entity]"
},
{
"step_id": "step_03_payout_switch_by_resolved_entity",
"title": "Outgoing payment follow-up keeps the same grounded counterparty and checked year",
"question": "а теперь сколько заплатили?",
"allowed_reply_types": [
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2020",
"(?i)заплатил|исходящ|списан|платеж",
"(?i)руб"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)свк"
],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту",
"(?i)за какой год"
],
"criticality": "critical",
"semantic_tags": [
"entity_resolution",
"payout_switch",
"followup_reuse",
"date_carryover",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_04_counterparty_payout source=address_truth_harness_phase32_planner_selected_chain_end_to_end:step_03_payout_switch_by_resolved_entity]"
},
{
"step_id": "step_04_net_after_payout",
"title": "Net-flow follow-up reuses the same grounded counterparty and checked year after payout",
"question": "а какое нетто?",
"allowed_reply_types": [
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2020",
"(?i)нетто|сальдо",
"(?i)руб"
],
"required_answer_patterns_any": [
"(?i)получ",
"(?i)заплат",
"(?i)группа\\s+свк",
"(?i)свк"
],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту"
],
"criticality": "critical",
"semantic_tags": [
"entity_resolution",
"net_value_flow",
"followup_reuse",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_05_counterparty_net source=address_truth_harness_phase32_planner_selected_chain_end_to_end:step_04_net_after_payout]"
},
{
"step_id": "step_05_documents_after_net",
"title": "Document evidence follow-up keeps the grounded counterparty after the net answer",
"question": "а по документам?",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "document_evidence",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)документ|счет|накладн|акт"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)свк",
"(?i)2020"
],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту",
"(?i)сколько получили",
"(?i)сколько заплатили",
"(?i)нетто"
],
"criticality": "critical",
"semantic_tags": [
"entity_resolution",
"document_evidence",
"value_flow_pivot",
"followup_reuse",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_06_counterparty_documents source=address_truth_harness_phase32_planner_selected_chain_end_to_end:step_05_documents_after_net]"
},
{
"step_id": "step_06_movements_after_documents",
"title": "Movement evidence follow-up keeps the grounded counterparty after the document answer",
"question": "а по движениям?",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "movement_evidence",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)движени|операц|платеж|списан|поступ"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)свк",
"(?i)2020"
],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту",
"(?i)сколько получили",
"(?i)сколько заплатили",
"(?i)нетто"
],
"criticality": "critical",
"semantic_tags": [
"entity_resolution",
"movement_evidence",
"document_pivot",
"followup_reuse",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_07_counterparty_movements source=address_truth_harness_phase32_planner_selected_chain_end_to_end:step_06_movements_after_documents]"
},
{
"step_id": "step_01_open_scope_incoming_total",
"title": "The user asks for incoming money without naming the organization yet",
"question": "Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
"allowed_reply_types": [
"clarification_required",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)уточн|нужно",
"(?i)организац"
],
"criticality": "critical",
"semantic_tags": [
"open_scope_total",
"organization_scope",
"human_dialog",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_08_open_org_total source=address_truth_harness_phase66_human_org_open_scope_dialog:step_01_open_scope_incoming_total]"
},
{
"step_id": "step_02_all_time_same_open_scope",
"title": "The user selects the organization and gets the 2020 incoming total",
"question": "По ООО Альтернатива Плюс.",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2020",
"(?i)входящ|поступлен|получ"
],
"forbidden_answer_patterns": [
"(?i)уточните .*контрагент",
"(?i)не найден контрагент",
"(?i)уточните .*организац"
],
"criticality": "critical",
"semantic_tags": [
"organization_clarification",
"open_scope_total",
"human_dialog",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_09_open_org_selection source=address_truth_harness_phase66_human_org_open_scope_dialog:step_02_all_time_same_open_scope]"
},
{
"step_id": "step_03_all_time_same_open_scope",
"title": "The user broadens the same organization slice to all available time",
"question": "Понял, тогда за все время.",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)все доступное время|все время|весь период",
"(?i)входящ|поступлен|получ"
],
"forbidden_answer_patterns": [
"(?i)за 2020",
"(?i)уточните .*контрагент",
"(?i)уточните .*период",
"(?i)уточните .*организац"
],
"criticality": "critical",
"semantic_tags": [
"all_time_followup",
"organization_scope",
"human_dialog",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_10_open_org_all_time source=address_truth_harness_phase66_human_org_open_scope_dialog:step_03_all_time_same_open_scope]"
},
{
"step_id": "step_04_bidirectional_comparison",
"title": "The user asks which money direction is larger for the organization",
"question": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2020",
"(?i)входящ|исходящ|получ|заплат|больше"
],
"criticality": "critical",
"semantic_tags": [
"value_flow_comparison",
"organization_scope",
"human_dialog",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_11_open_org_comparison source=address_truth_harness_phase66_human_org_open_scope_dialog:step_04_bidirectional_comparison]"
},
{
"step_id": "step_05_comparison_year_switch",
"title": "The user asks the same comparison for another year",
"question": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2021",
"(?i)входящ|исходящ|получ|заплат|больше"
],
"criticality": "critical",
"semantic_tags": [
"value_flow_comparison",
"year_switch",
"organization_scope",
"human_dialog",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_12_open_org_comparison_year_switch source=address_truth_harness_phase66_human_org_open_scope_dialog:step_05_comparison_year_switch]"
},
{
"step_id": "step_06_ranking_top_counterparty",
"title": "The user asks who brought the most money for the organization",
"question": "И кто больше всего принес денег этой организации в 2020 году?",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_ranking",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2020",
"(?i)кто|контрагент|клиент|принес|доход"
],
"criticality": "critical",
"semantic_tags": [
"value_flow_ranking",
"organization_scope",
"human_dialog",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_13_open_org_ranking source=address_truth_harness_phase66_human_org_open_scope_dialog:step_06_ranking_top_counterparty]"
},
{
"step_id": "step_07_ranking_year_switch",
"title": "The user asks the same ranking for another year",
"question": "А в 2021 году?",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_ranking",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": [
"(?i)2021"
],
"criticality": "critical",
"semantic_tags": [
"value_flow_ranking",
"year_switch",
"organization_scope",
"human_dialog",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_14_open_org_ranking_year_switch source=address_truth_harness_phase66_human_org_open_scope_dialog:step_07_ranking_year_switch]"
},
{
"step_id": "step_01_company_activity_lifecycle",
"title": "Lifecycle answer seeds grounded organization context",
"question": "а по Альтернативе Плюс сколько лет активности в базе 1С?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "lifecycle",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual",
"factual_with_explanation"
],
"required_answer_patterns_any": [
"(?i)лет",
"(?i)активност",
"(?i)1с",
"(?i)не получил|не подтвержден|проверил доступный контур"
],
"criticality": "warning",
"semantic_tags": [
"company_activity_lifecycle",
"grounded_context_seed",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_15_broad_eval_context source=address_truth_harness_phase22_broad_business_evaluation_bridge:step_01_company_activity_lifecycle]"
},
{
"step_id": "step_02_broad_business_evaluation",
"title": "Broad business evaluation becomes grounded summary instead of stale lifecycle dump",
"question": "Как ты оценишь деятельность компании?",
"required_answer_patterns_all": [
"(?i)коротко|оценк|частичн",
"(?i)1с|подтвержд",
"(?i)денежн|долг|ндс|контрагент|операц"
],
"forbidden_answer_patterns": [
"(?i)активных заказчиков",
"(?i)последняя активность",
"(?i)^\\s*1\\."
],
"criticality": "warning",
"semantic_tags": [
"broad_business_evaluation",
"grounded_summary"
],
"notes": "[mixed_pack_slot=slot_16_broad_eval_bridge source=address_truth_harness_phase22_broad_business_evaluation_bridge:step_02_broad_business_evaluation]"
},
{
"step_id": "step_03_net_flow_after_broad_eval",
"title": "Exact net-flow follow-up still answers after the broad bridge",
"question": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)свк",
"(?i)получил|входящ|поступ",
"(?i)заплат|исходящ|списан|плат[её]ж",
"(?i)нетто|сальдо|разниц",
"(?i)2020|период",
"(?i)руб"
],
"forbidden_answer_patterns": [
"(?i)активных заказчиков",
"(?i)лет в базе",
"(?i)последняя активность"
],
"criticality": "critical",
"semantic_tags": [
"counterparty_net_cash_flow",
"broad_eval_bridge_preserved",
"planner_catalog_alignment"
],
"notes": "[mixed_pack_slot=slot_17_broad_eval_return_to_net source=address_truth_harness_phase22_broad_business_evaluation_bridge:step_03_net_flow_after_broad_eval]"
},
{
"step_id": "step_01_catalog_metadata_surface",
"title": "Catalog-oriented metadata surface is surfaced honestly for counterparties",
"question": "какие справочники 1С есть по контрагентам?",
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)metadata|метадан",
"(?i)справоч|catalog|directory",
"(?i)контрагент"
],
"forbidden_answer_patterns": [
"(?i)получили",
"(?i)заплатили",
"(?i)нетто",
"(?i)документные строки найдены",
"(?i)строки денежных движений найдены"
],
"criticality": "warning",
"semantic_tags": [
"catalog_metadata_surface",
"counterparty_catalog_scope"
],
"notes": "[mixed_pack_slot=slot_18_metadata_surface source=address_truth_harness_phase42_catalog_metadata_drilldown:step_01_catalog_metadata_surface]"
},
{
"step_id": "step_02_neutral_followup_catalog_drilldown",
"title": "Neutral follow-up continues into deeper catalog metadata instead of asking for a documents-vs-movements lane choice",
"question": "давай дальше",
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)metadata|метадан|схем",
"(?i)справоч|catalog|directory",
"(?i)контрагент|counterpart"
],
"forbidden_answer_patterns": [
"(?i)документ",
"(?i)движени|регистр",
"(?i)уточн.*контур",
"(?i)получили",
"(?i)заплатили",
"(?i)нетто"
],
"criticality": "warning",
"semantic_tags": [
"catalog_drilldown",
"neutral_followup"
],
"notes": "[mixed_pack_slot=slot_19_metadata_drilldown source=address_truth_harness_phase42_catalog_metadata_drilldown:step_02_neutral_followup_catalog_drilldown]"
},
{
"step_id": "step_08_off_domain_living_chat_not_hijacked",
"title": "Off-domain living chat remains human and is not hijacked by discovery carryover",
"question": "а чем капибара отличается от утки?",
"required_answer_patterns_any": [
"(?i)капибар.*утк|утк.*капибар",
"(?i)млекопита|птиц|грызун"
],
"forbidden_answer_patterns": [
"(?i)свк",
"(?i)контрагент",
"(?i)mcp",
"(?i)query_documents",
"(?i)runtime_",
"(?i)primitive"
],
"criticality": "warning",
"semantic_tags": [
"off_domain_living_chat",
"stale_replay_forbidden"
],
"notes": "[mixed_pack_slot=slot_20_off_domain_guard source=address_truth_harness_phase19_mcp_discovery_response_gate:step_08_off_domain_living_chat_not_hijacked]"
}
]
}

View File

@ -0,0 +1,106 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase84_business_overview_multi_probe_bridge",
"domain": "address_phase84_business_overview_multi_probe_bridge",
"title": "Phase 84 business overview multi-probe bridge replay",
"description": "Targeted AGENT replay for the fresh business_overview MCP discovery bridge: broad company analysis must execute through reviewed multi-probe evidence, keep profit/margin boundaries honest, and not contaminate later exact counterparty pivots or repeated company-overview pivots.",
"bindings": {},
"steps": [
{
"step_id": "step_01_explicit_company_business_overview",
"title": "Explicit company overview uses the reviewed business_overview bridge",
"question": "Дай бизнес-обзор ООО Альтернатива Плюс по данным 1С: обороты, нетто, активность, что подтверждено и что пока неизвестно.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)1с|подтвержд",
"(?i)входящ|поступлен|оборот|денежн",
"(?i)исходящ|платеж|списан|поставщик",
"(?i)нетто|разниц|сальдо",
"(?i)прибыл|марж|не подтвержд|не доказан",
"(?i)ндс|vat|налог|долг|склад|inventory"
],
"forbidden_answer_patterns": [
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
"(?i)это прибыль|маржа составляет|чистая прибыль"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_multi_probe",
"broad_business_evaluation",
"planner_catalog_alignment",
"profit_margin_boundary"
]
},
{
"step_id": "step_02_exact_net_flow_after_company_overview",
"title": "Exact counterparty net-flow still answers after company overview",
"question": "Теперь отдельно: какое нетто по деньгам с Группа СВК за 2020 год, сколько получили и сколько заплатили?",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "value_flow_comparison",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)свк",
"(?i)2020|период",
"(?i)получил|входящ|поступлен",
"(?i)заплат|исходящ|списан|платеж",
"(?i)нетто|разниц|сальдо",
"(?i)руб"
],
"forbidden_answer_patterns": [
"(?i)активность компании",
"(?i)прибыль компании",
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
],
"criticality": "critical",
"semantic_tags": [
"counterparty_net_cash_flow",
"post_broad_eval_exact_pivot",
"planner_catalog_alignment"
]
},
{
"step_id": "step_03_explicit_company_overview_after_counterparty_pivot",
"title": "Repeated company overview does not inherit the counterparty pivot",
"question": "Вернись к ООО Альтернатива Плюс в целом: дай краткий бизнес-аудит по подтвержденным данным 1С, но не выдавай нетто за прибыль.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)альтернатива|компани|организац",
"(?i)1с|подтвержд",
"(?i)нетто|денежн|поток",
"(?i)не прибыль|не является прибыл|не марж|марж.*не подтвержд",
"(?i)следующ|нужно|не подтвержд|отдельн"
],
"forbidden_answer_patterns": [
"(?i)группа свк.*бизнес-аудит",
"(?i)чистая прибыль",
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_after_counterparty_pivot",
"stale_scope_guard",
"planner_catalog_alignment",
"profit_margin_boundary"
]
}
]
}

View File

@ -0,0 +1,78 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase85_business_overview_tax_family",
"domain": "address_phase85_business_overview_tax_family",
"title": "Phase 85 business overview VAT/tax fact-family replay",
"description": "Targeted replay for Business Overview Fact-Family Expansion: explicit-period company overview may include checked VAT/tax evidence, while all-time follow-up must not reuse the previous VAT period as confirmed tax position.",
"bindings": {},
"steps": [
{
"step_id": "step_01_explicit_period_business_overview_with_tax",
"title": "Explicit-period business overview includes checked VAT/tax family",
"question": "Дай бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: деньги, нетто, активность, НДС-позиция, что подтверждено и что пока неизвестно. Не выдавай нетто за прибыль.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)1с|подтвержд",
"(?i)2020",
"(?i)входящ|поступлен|денежн",
"(?i)исходящ|платеж|списан",
"(?i)ндс|vat",
"(?i)книга продаж|продаж",
"(?i)книга покуп|вычет|покуп",
"(?i)нетто",
"(?i)не прибыль|не марж|прибыль.*не подтвержд|марж.*не подтвержд"
],
"forbidden_answer_patterns": [
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
"(?i)чистая прибыль|маржа составляет|это прибыль"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_tax_family",
"explicit_period_tax_position",
"planner_catalog_alignment",
"profit_margin_boundary"
]
},
{
"step_id": "step_02_all_time_business_overview_does_not_reuse_tax_period",
"title": "All-time overview after explicit tax period keeps VAT unknown",
"question": "Теперь по ООО Альтернатива Плюс за все доступное время дай бизнес-обзор в целом, но не тащи НДС за 2020 как подтвержденную общую налоговую позицию.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)альтернатива|организац|компани",
"(?i)все доступн|проверенн.*окн|активност",
"(?i)денежн|нетто",
"(?i)ндс|vat|налог",
"(?i)не подтвержд|нужен отдельн|явн.*период"
],
"forbidden_answer_patterns": [
"(?i)ндс-позиция за 2020",
"(?i)книга продаж.*2020",
"(?i)книга покуп.*2020",
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_tax_family",
"stale_period_guard",
"all_time_tax_boundary",
"planner_catalog_alignment"
]
}
]
}

View File

@ -0,0 +1,79 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase86_business_overview_debt_position",
"domain": "address_phase86_business_overview_debt_position",
"title": "Phase 86 business overview debt-position fact-family replay",
"description": "Targeted replay for Business Overview Fact-Family Expansion: explicit-period company overview may include checked receivables/payables as-of-date debt position, while all-time follow-up must not reuse the previous as-of-date debt snapshot as current or all-time debt quality.",
"bindings": {},
"steps": [
{
"step_id": "step_01_explicit_period_business_overview_with_debt_position",
"title": "Explicit-period business overview includes checked receivables/payables debt position",
"question": "Дай бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: деньги, нетто, активность, дебиторка и кредиторка на дату, что подтверждено и что пока неизвестно. Не выдавай долговой срез за просрочку, качество долга или прибыль.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)1с|подтвержд",
"(?i)2020|2020-12-31",
"(?i)входящ|поступлен|денежн",
"(?i)исходящ|платеж|списан",
"(?i)дебитор|кредитор|долгов",
"(?i)нетто",
"(?i)не прибыль|не марж|прибыль.*не подтвержд|марж.*не подтвержд",
"(?i)качество.*не подтвержд|просроч.*не подтвержд|aging|due-date|не.*просроч"
],
"forbidden_answer_patterns": [
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
"(?i)просроченная дебиторка составляет|качество долга хорошее|кредитный риск низкий",
"(?i)чистая прибыль|маржа составляет|это прибыль"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_debt_position_family",
"explicit_period_as_of_debt_snapshot",
"debt_quality_boundary",
"profit_margin_boundary",
"planner_catalog_alignment"
]
},
{
"step_id": "step_02_all_time_business_overview_does_not_reuse_debt_snapshot",
"title": "All-time overview after explicit debt position keeps debt snapshot unknown",
"question": "Теперь по ООО Альтернатива Плюс за все доступное время дай бизнес-обзор в целом, но не тащи долговой срез на 2020-12-31 как текущую или общую долговую позицию.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)альтернатива|организац|компани",
"(?i)все доступн|проверенн.*окн|активност",
"(?i)денежн|нетто",
"(?i)дебитор|кредитор|долг",
"(?i)не подтвержд|нужен отдельн|явн.*дат|as-of|дату"
],
"forbidden_answer_patterns": [
"(?i)долгов(ой|ая).*2020-12-31",
"(?i)дебиторка.*2020-12-31",
"(?i)кредиторка.*2020-12-31",
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_debt_position_family",
"stale_as_of_debt_snapshot_guard",
"all_time_debt_boundary",
"planner_catalog_alignment"
]
}
]
}

View File

@ -0,0 +1,76 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase87_business_overview_inventory_position",
"domain": "address_phase87_business_overview_inventory_position",
"title": "Phase 87 business overview inventory-position fact-family replay",
"description": "Targeted replay for Business Overview Fact-Family Expansion: explicit-date company overview may include checked inventory on-hand and purchase-date aging signals, while all-time follow-up must not reuse the previous inventory as-of-date snapshot as current or all-time warehouse health.",
"bindings": {},
"steps": [
{
"step_id": "step_01_explicit_date_business_overview_with_inventory_position",
"title": "Explicit-date business overview includes checked inventory position",
"question": "Дай бизнес-обзор ООО Альтернатива Плюс на 2026-04-16 по данным 1С: деньги, активность, складской остаток и товарный срез на дату, что подтверждено и что пока неизвестно. Не выдавай складской остаток за ликвидность, оборачиваемость, прибыль или полноценное здоровье бизнеса.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)1с|подтвержд",
"(?i)2026-04-16|16\\.04\\.2026",
"(?i)склад|остат|товар",
"(?i)руб|сумм|стоимост",
"(?i)не прибыль|не марж|прибыль.*не подтвержд|марж.*не подтвержд",
"(?i)оборачиваемость|ликвидность|не подтвержд|отдельн"
],
"forbidden_answer_patterns": [
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
"(?i)склад.*ликвидн.*хорош|оборачиваемость.*хорош|залежалость.*низк",
"(?i)чистая прибыль|маржа составляет|это прибыль"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_inventory_position_family",
"explicit_date_inventory_snapshot",
"inventory_liquidity_boundary",
"profit_margin_boundary",
"planner_catalog_alignment"
]
},
{
"step_id": "step_02_all_time_business_overview_does_not_reuse_inventory_snapshot",
"title": "All-time overview after explicit inventory position keeps inventory snapshot unknown",
"question": "Теперь по ООО Альтернатива Плюс за все доступное время дай бизнес-обзор в целом, но не тащи складской срез на 2026-04-16 как текущий или общий all-time склад.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)альтернатива|организац|компани",
"(?i)все доступн|проверенн.*окн|активност",
"(?i)денежн|нетто",
"(?i)склад|остат|inventory",
"(?i)не подтвержд|нужен отдельн|явн.*дат|дату"
],
"forbidden_answer_patterns": [
"(?i)склад(ской|ская|ские|).*2026-04-16",
"(?i)остат(ок|ки).*2026-04-16",
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_inventory_position_family",
"stale_as_of_inventory_snapshot_guard",
"all_time_inventory_boundary",
"planner_catalog_alignment"
]
}
]
}

View File

@ -0,0 +1,80 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase88_business_overview_open_settlement_quality",
"domain": "address_phase88_business_overview_open_settlement_quality",
"title": "Phase 88 business overview open-settlement quality replay",
"description": "Targeted replay for Business Overview Fact-Family Expansion: explicit-date company overview may include checked open-contract settlement concentration, while due-date aging/overdue debt must remain unclaimed and all-time follow-up must not reuse the prior as-of-date debt/open-contract snapshot.",
"bindings": {},
"steps": [
{
"step_id": "step_01_explicit_date_business_overview_with_open_settlement_quality",
"title": "Explicit-date business overview includes checked open-settlement quality boundary",
"question": "Дай бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: деньги, активность, НДС, дебиторка/кредиторка и качество открытых расчетов по договорам на дату. Отдельно скажи, что подтверждено, а что нельзя считать просрочкой или due-date aging без сроков оплаты.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)1с|подтвержд",
"(?i)2020|2020-12-31|31\\.12\\.2020",
"(?i)дебитор|кредитор|долгов",
"(?i)открыт.*расчет|открыт.*договор|договорн",
"(?i)руб|сумм|остат",
"(?i)due-date|срок|просроч|не подтвержд|нельзя считать"
],
"forbidden_answer_patterns": [
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
"(?i)подтвержденн(?:ая|ую|ый|ые)?\\s+просроч",
"(?i)просрочк[а-я\\s]{0,20}(?:есть|имеется|найдена|составляет)",
"(?i)долг.*плох|кредитн.*риск.*высок",
"(?i)чистая прибыль|маржа составляет|это прибыль"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_open_settlement_quality_family",
"explicit_date_debt_snapshot",
"open_contract_concentration_boundary",
"due_date_aging_boundary",
"planner_catalog_alignment"
]
},
{
"step_id": "step_02_all_time_business_overview_does_not_reuse_open_settlement_snapshot",
"title": "All-time overview after open-settlement quality keeps prior debt snapshot bounded",
"question": "Теперь по ООО Альтернатива Плюс за все доступное время дай общий бизнес-обзор, но не тащи срез открытых договоров на 2020-12-31 как текущую просрочку или all-time качество долга.",
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"allowed_reply_types": [
"partial_coverage",
"factual_with_explanation"
],
"required_answer_patterns_all": [
"(?i)альтернатива|организац|компани",
"(?i)все доступн|проверенн.*окн|активност",
"(?i)денежн|нетто",
"(?i)долг|дебитор|кредитор|открыт.*расчет",
"(?i)не подтвержд|нужен отдельн|явн.*дат|дату"
],
"forbidden_answer_patterns": [
"(?i)2020-12-31",
"(?i)31\\.12\\.2020",
"(?i)подтвержденн(?:ая|ую|ый|ые)?\\s+просроч",
"(?i)просрочк[а-я\\s]{0,20}(?:есть|имеется|найдена|составляет)",
"(?i)business_overview_route_template_v1",
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
],
"criticality": "critical",
"semantic_tags": [
"business_overview_open_settlement_quality_family",
"stale_as_of_debt_snapshot_guard",
"all_time_debt_quality_boundary",
"planner_catalog_alignment"
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,683 @@
# Agent semantic source catalog
- truth_harness_steps_total: `58`
- saved_session_questions_total: `81`
- truth_harness_steps_total: `520`
- saved_session_questions_total: `229`
## Reusable truth-harness tags
- `counterparty_documents`: `3`
- `counterparty_shipment_fallback`: `3`
- `inventory_root`: `21`
- `meta_capability`: `3`
- `meta_historical_capability`: `4`
- `meta_memory`: `2`
- `meta_scope`: `4`
- `meta_smalltalk`: `3`
- `same_date_pivot`: `3`
- `same_date_restore`: `4`
- `selected_object`: `11`
- `selected_object_documents`: `3`
- `selected_object_sale`: `1`
- `selected_object_supplier`: `7`
- `settlements_account_60`: `2`
- `settlements_receivables`: `4`
- `vat`: `3`
- `account_60`: `2`
- `account_injection_guard`: `1`
- `activity_age`: `1`
- `activity_assessment`: `1`
- `aggregate_all_time`: `1`
- `aggregate_revenue`: `1`
- `aggregate_year`: `1`
- `alias_grounding`: `7`
- `all_time_after_pivot`: `2`
- `all_time_after_second_pivot`: `2`
- `all_time_after_third_pivot`: `1`
- `all_time_followup`: `10`
- `all_time_scope`: `4`
- `ambiguity_probe`: `1`
- `anomaly_probe`: `1`
- `answer_inspection`: `1`
- `answer_top_block_matches_current_user_intent`: `1`
- `bidirectional_value_flow`: `1`
- `bounded_autonomy`: `47`
- `bounded_retrieval`: `13`
- `bridge_inventory_to_vat`: `3`
- `broad_business_evaluation`: `3`
- `broad_eval_bridge_preserved`: `2`
- `broad_eval_followup_continuity`: `1`
- `broad_evaluation_bridge`: `1`
- `capability_meta`: `3`
- `capability_over_followup`: `2`
- `catalog_drilldown`: `2`
- `catalog_grounding`: `1`
- `catalog_metadata_surface`: `2`
- `clarification_required`: `1`
- `clarification_resume`: `2`
- `company_activity_lifecycle`: `3`
- `company_analytics`: `1`
- `company_authority`: `3`
- `company_authority_probe`: `1`
- `company_clarification`: `2`
- `company_selected`: `11`
- `company_selection`: `2`
- `context_reframe`: `1`
- `context_reuse`: `1`
- `context_seed`: `1`
- `context_setup`: `1`
- `continuity_after_interrupt`: `1`
- `continuity_interrupt`: `1`
- `contracts_followup`: `16`
- `counterparty_carryover`: `1`
- `counterparty_catalog_scope`: `2`
- `counterparty_documents`: `29`
- `counterparty_followup`: `3`
- `counterparty_grounding`: `1`
- `counterparty_item_flow`: `1`
- `counterparty_lifecycle`: `1`
- `counterparty_monthly_net_cash_flow`: `1`
- `counterparty_net_cash_flow`: `5`
- `counterparty_net_value_flow`: `1`
- `counterparty_outgoing_payments`: `1`
- `counterparty_pronoun_resolution`: `15`
- `counterparty_resolution`: `2`
- `counterparty_retarget`: `1`
- `counterparty_root`: `3`
- `counterparty_shipment_fallback`: `6`
- `counterparty_short_retarget`: `1`
- `counterparty_turnover`: `1`
- `counterparty_value_flow`: `1`
- `cross_domain_pivot`: `2`
- `cross_stage_canary`: `1`
- `current_snapshot`: `1`
- `current_turn_action_authority`: `1`
- `current_turn_entity_authority`: `1`
- `customer_analytics`: `1`
- `data_scope_meta`: `2`
- `date_carryover`: `7`
- `date_followup`: `2`
- `date_scope`: `1`
- `debt_polarity`: `1`
- `display_label_integrity`: `3`
- `display_name_integrity`: `1`
- `document_evidence`: `4`
- `document_lane_after_clarification`: `5`
- `document_lane_continuity`: `6`
- `document_lane_execution`: `5`
- `document_pivot`: `2`
- `document_pivot_after_movement`: `1`
- `document_pivot_after_movement_retrieval`: `2`
- `document_pivot_after_retrieval`: `2`
- `document_pivot_after_value_flow`: `2`
- `documents`: `2`
- `documents_by_counterparty`: `15`
- `documents_followup`: `7`
- `documents_pivot`: `2`
- `entity_grounding`: `2`
- `entity_resolution`: `35`
- `exact_not_overwritten`: `2`
- `followup_anchor`: `7`
- `followup_reuse`: `26`
- `followup_short`: `1`
- `fourth_pivot`: `2`
- `garbage_anchor_forbidden`: `1`
- `grounded_context_seed`: `2`
- `grounded_counterparty`: `13`
- `grounded_counterparty_followup`: `12`
- `grounded_discovery_seed`: `1`
- `grounded_self_correction`: `1`
- `grounded_summary`: `2`
- `historical_anchor`: `1`
- `historical_date_anchor`: `3`
- `historical_inventory`: `2`
- `historical_restore`: `1`
- `human_answer`: `4`
- `human_answer_quality`: `2`
- `human_dialog`: `46`
- `hybrid_investigation_followup`: `2`
- `hybrid_investigation_root`: `2`
- `incoming`: `8`
- `incoming_value_flow`: `9`
- `inline_organization_clarification`: `13`
- `integrity_guard`: `57`
- `inventory_aging`: `3`
- `inventory_capability_meta`: `1`
- `inventory_context`: `1`
- `inventory_history_capability`: `1`
- `inventory_provenance`: `3`
- `inventory_root`: `58`
- `inventory_sale_trace`: `1`
- `item_flow_failure_26`: `1`
- `lane_choice_reuse`: `1`
- `late_company_switch`: `2`
- `late_company_switch_back`: `1`
- `late_session_stability`: `2`
- `late_session_tail`: `1`
- `legacy_phase39`: `2`
- `legacy_phase64`: `6`
- `legacy_phase67`: `7`
- `living_scope_selection`: `1`
- `manual_9lieoh`: `11`
- `materialization_gap`: `1`
- `mcp_discovery_bidirectional_value_flow`: `2`
- `mcp_discovery_gate_sanity`: `2`
- `mcp_discovery_response_gate`: `1`
- `mcp_discovery_supplier_payout`: `1`
- `mcp_discovery_value_flow`: `1`
- `meta_capability`: `16`
- `meta_historical_capability`: `10`
- `meta_interrupt`: `1`
- `meta_memory`: `6`
- `meta_return_to_business`: `1`
- `meta_scope`: `12`
- `meta_smalltalk`: `14`
- `meta_verify`: `1`
- `metadata_lane_choice_clarification`: `15`
- `metadata_surface`: `17`
- `mixed_ambiguity`: `15`
- `movement_evidence`: `4`
- `movement_execution`: `1`
- `movement_lane_after_clarification`: `11`
- `movement_lane_after_metadata`: `2`
- `movement_lane_continuity`: `4`
- `movement_lane_execution`: `7`
- `movement_pivot_after_document_retrieval`: `3`
- `movement_pivot_after_value_flow`: `2`
- `movements_pivot`: `2`
- `multi_axis_aggregation`: `1`
- `multi_company_entry`: `2`
- `multi_hop_clarification`: `21`
- `net_switch`: `1`
- `net_value_flow`: `6`
- `neutral_followup`: `17`
- `numeric_counterparty_suffix`: `1`
- `off_domain_living_chat`: `3`
- `open_scope`: `9`
- `open_scope_net`: `3`
- `open_scope_total`: `10`
- `organization_activity_age`: `5`
- `organization_authority`: `7`
- `organization_clarification`: `9`
- `organization_fact_boundary`: `1`
- `organization_followup_reuse`: `20`
- `organization_scope`: `34`
- `organization_scoped`: `4`
- `organization_second_recovery`: `1`
- `outgoing`: `3`
- `outgoing_value_flow`: `2`
- `payables`: `1`
- `payables_snapshot`: `1`
- `payments_followup`: `20`
- `payout_switch`: `5`
- `payout_value_flow`: `2`
- `payout_year_switch`: `3`
- `period_carryover`: `1`
- `period_clarification_resume`: `1`
- `period_cleared`: `2`
- `period_cleared_after_pivot`: `2`
- `period_close_impact`: `1`
- `period_close_risk`: `1`
- `period_first_recovery`: `1`
- `period_followup_reuse`: `5`
- `period_gap_closed`: `2`
- `period_narrowing`: `1`
- `period_scope`: `9`
- `pivot_seed`: `8`
- `planner_catalog_alignment`: `35`
- `polarity_flip`: `1`
- `post_f`: `9`
- `post_f_integrity_hardening`: `6`
- `post_inspection_continuity`: `1`
- `post_pivot_continuity`: `1`
- `precedence`: `1`
- `predecompose_guard`: `1`
- `proactive_scope_offer`: `2`
- `pronoun_followup`: `1`
- `pronoun_pivot_seed`: `6`
- `purchase_date_bundle`: `1`
- `purchase_date_context`: `1`
- `purchase_date_vat_bridge`: `1`
- `purchase_provenance`: `1`
- `ranking`: `2`
- `receivables`: `1`
- `receivables_root`: `1`
- `receivables_snapshot`: `1`
- `referential_document_followup`: `1`
- `referential_followup_seed`: `1`
- `remaining_gaps`: `2`
- `remaining_org_gap_only`: `1`
- `remaining_period_gap_only`: `3`
- `repeated_pivot`: `2`
- `revenue_ranking_failure_14`: `1`
- `route_stability`: `1`
- `sale_trace`: `1`
- `same_date_pivot`: `8`
- `same_date_restore`: `8`
- `same_organization_reuse`: `1`
- `same_period_restore`: `1`
- `same_proof_path_family_shift`: `5`
- `same_scope_new_period`: `1`
- `saved_chain`: `3`
- `scope_offer`: `2`
- `scope_reuse`: `8`
- `second_company_continuity`: `1`
- `second_pivot`: `8`
- `selected_object`: `31`
- `selected_object_context`: `1`
- `selected_object_documents`: `7`
- `selected_object_purchase`: `1`
- `selected_object_sale`: `3`
- `selected_object_supplier`: `16`
- `self_scope`: `3`
- `settlements_60_62`: `1`
- `settlements_account_60`: `5`
- `settlements_mirror_followup`: `1`
- `settlements_payables`: `1`
- `settlements_receivables`: `13`
- `smalltalk_entry`: `4`
- `stale_entity_seed`: `1`
- `stale_inventory_scope`: `1`
- `stale_lifecycle_override`: `1`
- `stale_replay_forbidden`: `3`
- `stale_scope_guard`: `1`
- `stale_temporal_carryover`: `1`
- `supported_route_not_hijacked_by_mcp_discovery`: `1`
- `switch_back_integrity`: `1`
- `tail_authority_proof`: `2`
- `tail_quality`: `1`
- `tax_period`: `1`
- `temporal_tail_not_entity`: `1`
- `third_pivot`: `4`
- `today_scope`: `1`
- `topic_reset`: `5`
- `translit_wording`: `1`
- `unsupported_current_turn_meaning_boundary`: `5`
- `value_flow_comparison`: `13`
- `value_flow_net`: `6`
- `value_flow_pivot`: `4`
- `value_flow_ranking`: `19`
- `value_flow_total`: `16`
- `vat`: `37`
- `vat_colloquial_wording`: `2`
- `vat_failure_11`: `1`
- `vat_failure_13`: `1`
- `vat_followup`: `2`
- `vat_liability_live`: `1`
- `vat_orientation`: `2`
- `very_old_stock`: `1`
- `year_specific`: `1`
- `year_switch`: `16`
- `year_switch_after_document_pivot`: `1`
- `year_switch_after_fourth_pivot`: `2`
- `year_switch_after_pivot`: `4`
- `year_switch_after_reframe`: `1`
- `year_switch_after_repeated_pivot`: `1`
- `year_switch_after_second_pivot`: `2`
- `year_switch_after_third_pivot`: `1`
- `year_switch_followup`: `2`
- `year_tail_not_entity`: `1`
## Reusable truth-harness steps
- `address_truth_harness_inventory_provenance_restore:step_01_inventory_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_inventory_provenance_restore:step_02_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_inventory_provenance_restore:step_03_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `address_truth_harness_inventory_provenance_restore:step_04_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
- `address_truth_harness_phase10_manual_bridge_and_aggregate_mix:step_01_inventory_historical_anchor` | tags: inventory_root, historical_date_anchor, meta_historical_capability | question: остатки на март 2016
- `address_truth_harness_phase10_manual_bridge_and_aggregate_mix:step_02_selected_item_purchase_provenance` | tags: selected_object, inventory_provenance, date_scope, selected_object_supplier | question: По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где взяли это?
- `address_truth_harness_phase10_manual_bridge_and_aggregate_mix:step_03_vat_on_purchase_date_bridge` | tags: bridge_inventory_to_vat, selected_object, purchase_date_bundle, vat | question: ндс можешь прикинуть на дату покупки рабочей станции?
- `address_truth_harness_phase10_manual_bridge_and_aggregate_mix:step_04_colloquial_vat_tax_period` | tags: vat_colloquial_wording, tax_period, vat | question: прикинь какой ндс нам надо заплатить на февраль 2017
- `address_truth_harness_phase10_manual_bridge_and_aggregate_mix:step_05_payables_today` | tags: payables, debt_polarity | question: мы должны комуто денег на сегодня?
- `address_truth_harness_phase10_manual_bridge_and_aggregate_mix:step_06_receivables_role_flip` | tags: receivables, polarity_flip, followup_short, settlements_receivables | question: а нам?
- `address_truth_harness_phase10_manual_bridge_and_aggregate_mix:step_07_top_customer_all_time` | tags: customer_analytics, aggregate_all_time, ranking | question: кто у нас самый доходный клиент за все время
- `address_truth_harness_phase10_manual_bridge_and_aggregate_mix:step_08_top_year_revenue` | tags: company_analytics, aggregate_year, ranking | question: какой у нас самый доходный год
- `address_truth_harness_phase10_manual_bridge_and_aggregate_mix:step_09_very_old_stock` | tags: inventory_aging, very_old_stock, precedence, inventory_root | question: Есть ли остатки товара, которые закупались очень давно
- `address_truth_harness_phase11_manual_followup_meta_quality:step_01_capability_meta_human` | tags: meta_capability, human_answer_quality | question: расскажи что можешь интересного
- `address_truth_harness_phase11_manual_followup_meta_quality:step_02_inventory_historical_root` | tags: inventory_root, historical_date_anchor, meta_historical_capability | question: что там на складе по остаткам на март 2016?
- `address_truth_harness_phase11_manual_followup_meta_quality:step_03_selected_item_purchase_provenance` | tags: selected_object, inventory_provenance, selected_object_supplier | question: по выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где взяли это?
- `address_truth_harness_phase11_manual_followup_meta_quality:step_04_meta_verify_interrupt` | tags: meta_verify, continuity_interrupt | question: у тебя написано кто контрагент: рабочая станция - это ошибка?
- `address_truth_harness_phase11_manual_followup_meta_quality:step_05_vat_on_purchase_date_after_interrupt` | tags: bridge_inventory_to_vat, selected_object, continuity_after_interrupt, vat | question: ндс можешь прикинуть на дату покупки рабочей станции?
- `address_truth_harness_phase11_manual_followup_meta_quality:step_06_colloquial_vat_march_2020` | tags: vat_colloquial_wording, predecompose_guard, vat | question: а какой ндс мы должны сгрузить на март 2020?
- `address_truth_harness_phase11_manual_followup_meta_quality:step_07_vat_february_2017_live` | tags: vat_liability_live, route_stability, vat | question: прикинь какой ндс нам надо заплатить на февраль 2017
- `address_truth_harness_phase11_manual_followup_meta_quality:step_08_contract_delta_capability_meta` | tags: meta_capability, capability_over_followup | question: ты умеешь считать дельту по договорам?
- `address_truth_harness_phase11_manual_followup_meta_quality:step_09_counterparty_docs_anchor` | tags: counterparty_root, documents, counterparty_documents | question: по Чепурнову покажи все доки
- `address_truth_harness_phase11_manual_followup_meta_quality:step_10_short_counterparty_retarget_name` | tags: counterparty_followup, display_name_integrity | question: а по свк
- `address_truth_harness_phase12_wider_saved_session_pool:step_01_smalltalk_with_scope_offer` | tags: meta_smalltalk, proactive_scope_offer | question: приветик - че как там дела
- `address_truth_harness_phase12_wider_saved_session_pool:step_02_choose_organization` | tags: company_selected, organization_authority | question: Альтернатива Плюс
- `address_truth_harness_phase12_wider_saved_session_pool:step_03_capability_meta_human` | tags: meta_capability, human_answer_quality | question: расскажи что можешь интересного
- `address_truth_harness_phase12_wider_saved_session_pool:step_04_inventory_root_today` | tags: inventory_root, company_authority | question: кайф - что там на складе по остаткам?
- `address_truth_harness_phase12_wider_saved_session_pool:step_05_inventory_history_capability` | tags: meta_historical_capability, inventory_root, meta_capability | question: а исторические остатки на другие даты умеешь?
- `address_truth_harness_phase12_wider_saved_session_pool:step_06_inventory_march_2016` | tags: inventory_root, historical_date_anchor | question: март 2016
- `address_truth_harness_phase12_wider_saved_session_pool:step_07_selected_item_purchase_provenance` | tags: selected_object, inventory_provenance, selected_object_supplier | question: По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где взяли это?
- `address_truth_harness_phase12_wider_saved_session_pool:step_08_selected_item_sale_trace` | tags: selected_object, inventory_sale_trace, selected_object_sale | question: а кому продали?
- `address_truth_harness_phase12_wider_saved_session_pool:step_09_vat_on_purchase_date_bridge` | tags: bridge_inventory_to_vat, selected_object, vat | question: ндс можешь прикинуть на дату покупки рабочей станции?
- `address_truth_harness_phase12_wider_saved_session_pool:step_10_best_customer_all_time` | tags: aggregate_revenue, cross_domain_pivot | question: кто у нас самый доходный клиент за все время
- `address_truth_harness_phase12_wider_saved_session_pool:step_11_receivables_may_2017` | tags: settlements_receivables, date_carryover | question: кто нам должен денег на май 2017
- `address_truth_harness_phase12_wider_saved_session_pool:step_12_vat_same_period_followup` | tags: vat_followup, same_period_restore, vat | question: а какой ндс мы должны примерно заплатить за этот период?
- `address_truth_harness_phase12_wider_saved_session_pool:step_13_payables_today` | tags: settlements_payables, today_scope | question: мы должны комуто денег на сегодня?
- `address_truth_harness_phase12_wider_saved_session_pool:step_14_receivables_mirror_today` | tags: settlements_mirror_followup, same_date_restore, settlements_receivables | question: а нам?
- `address_truth_harness_phase12_wider_saved_session_pool:step_15_contract_delta_capability_meta` | tags: meta_capability, capability_over_followup | question: ты умеешь считать дельту по договорам?
- `address_truth_harness_phase12_wider_saved_session_pool:step_16_counterparty_docs_root` | tags: counterparty_root, documents, counterparty_documents | question: по чепурнову покажи все доки
- `address_truth_harness_phase12_wider_saved_session_pool:step_17_short_counterparty_followup` | tags: counterparty_followup, display_label_integrity | question: а по свк
- `address_truth_harness_phase12_wider_saved_session_pool:step_18_open_items_account_60` | tags: settlements_account_60 | question: хвосты покажи по счету 60 на август 2022
- `address_truth_harness_phase12_wider_saved_session_pool:step_19_inventory_aging_old_purchases` | tags: inventory_aging, late_session_stability, inventory_root | question: Есть ли остатки товара, которые закупались очень давно
- `address_truth_harness_phase12_wider_saved_session_pool:step_20_company_activity_age` | tags: organization_activity_age, tail_authority_proof | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `address_truth_harness_phase13_hybrid_followup_authority:step_01_supplier_tails_root` | tags: hybrid_investigation_root, settlements_60_62, anomaly_probe | question: Разложи хвосты по поставщикам: где разрыв между оплатой и документами выглядит системным.
- `address_truth_harness_phase13_hybrid_followup_authority:step_02_narrow_to_june_2020_account_60` | tags: hybrid_investigation_followup, period_narrowing, account_60, period_close_risk | question: Сузь до периода 2020-06 и счета 60, покажи только случаи с максимальным риском закрытия периода.
- `address_truth_harness_phase13_hybrid_followup_authority:step_03_period_close_impact_followup` | tags: hybrid_investigation_followup, period_close_impact, context_reuse | question: Что в текущих аномалиях сильнее всего повлияет на закрытие периода, если ничего не исправлять?
- `address_truth_harness_phase13_hybrid_followup_authority:step_04_translit_supplier_tails_root` | tags: hybrid_investigation_root, translit_wording, account_60 | question: Prover schet 60 za 2020-06, gde taili postavshikov i kakie dokumenty ne zakryvayut oplaty.
- `address_truth_harness_phase14_counterparty_tail_resume:step_01_data_scope_meta` | tags: data_scope_meta, multi_company_entry, meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase14_counterparty_tail_resume:step_02_choose_organization_after_clarification` | tags: organization_authority, company_selected | question: Альтернатива Плюс
- `address_truth_harness_phase14_counterparty_tail_resume:step_03_counterparty_docs_after_choice` | tags: counterparty_root, company_selected, counterparty_documents | question: по чепурнову покажи все доки
- `address_truth_harness_phase14_counterparty_tail_resume:step_04_short_counterparty_followup` | tags: counterparty_followup, display_label_integrity | question: а по свк
- `address_truth_harness_phase14_counterparty_tail_resume:step_05_inventory_today_after_counterparty_tail` | tags: inventory_root, cross_domain_pivot | question: а сейчас у нас есть что на складе?
- `address_truth_harness_phase14_counterparty_tail_resume:step_06_open_items_account_60_august_2022` | tags: settlements_account_60, late_session_tail | question: хвосты покажи по счету 60 на август 2022
- `address_truth_harness_phase14_counterparty_tail_resume:step_07_inventory_aging_old_purchases` | tags: inventory_aging, late_session_stability, inventory_root | question: Есть ли остатки товара, которые закупались очень давно
- `address_truth_harness_phase14_counterparty_tail_resume:step_08_inventory_may_2020_after_aging` | tags: inventory_root, historical_restore | question: Какие конкретно номенклатуры формируют остаток по складу на май 2020
- `address_truth_harness_phase14_counterparty_tail_resume:step_09_company_activity_age_tail` | tags: organization_activity_age, tail_authority_proof | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `address_truth_harness_phase14_counterparty_tail_resume:step_10_company_activity_assessment_tail` | tags: activity_assessment, tail_quality | question: Как ты оценишь деятельность компании?
- `address_truth_harness_phase15_answer_inspection_followup:step_01_smalltalk_scope_offer` | tags: smalltalk_entry, scope_offer, meta_smalltalk | question: приветик - че как там дела
- `address_truth_harness_phase15_answer_inspection_followup:step_02_choose_organization` | tags: organization_authority | question: Альтернатива Плюс
- `address_truth_harness_phase15_answer_inspection_followup:step_03_inventory_march_2016` | tags: inventory_root, current_snapshot | question: что там на складе по остаткам?
- `address_truth_harness_phase15_answer_inspection_followup:step_04_inventory_history_capability` | tags: inventory_history_capability, context_setup, meta_capability, meta_historical_capability, inventory_root | question: а исторические остатки на другие даты умеешь?
- `address_truth_harness_phase15_answer_inspection_followup:step_05_inventory_march_2016` | tags: inventory_root, historical_anchor | question: март 2016
- `address_truth_harness_phase15_answer_inspection_followup:step_06_selected_item_purchase_provenance` | tags: selected_object, purchase_provenance, selected_object_supplier | question: По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где взяли это?
- `address_truth_harness_phase15_answer_inspection_followup:step_07_selected_item_sale_trace` | tags: selected_object, sale_trace, selected_object_sale | question: а кому продали?
- `address_truth_harness_phase15_answer_inspection_followup:step_08_answer_inspection_counterparty_label` | tags: answer_inspection, selected_object_context, grounded_self_correction | question: у тебя написано кто контрагент: рабочая станция - это ошибка?
- `address_truth_harness_phase15_answer_inspection_followup:step_09_vat_on_purchase_date_after_inspection` | tags: purchase_date_vat_bridge, selected_object, post_inspection_continuity, vat | question: ндс можешь прикинуть на дату покупки рабочей станции?
- `address_truth_harness_phase16_multicompany_late_pivot:step_01_smalltalk_scope_offer` | tags: smalltalk_entry, scope_offer, meta_smalltalk | question: приветик, как дела?
- `address_truth_harness_phase16_multicompany_late_pivot:step_02_choose_alternative_plus` | tags: organization_authority, company_selected | question: Альтернатива Плюс
- `address_truth_harness_phase16_multicompany_late_pivot:step_03_inventory_today_alt` | tags: inventory_root, company_authority | question: что у нас сейчас на складе по остаткам?
- `address_truth_harness_phase16_multicompany_late_pivot:step_04_switch_to_raym` | tags: late_company_switch, organization_authority | question: теперь давай по РАЙМ
- `address_truth_harness_phase16_multicompany_late_pivot:step_05_inventory_today_raym` | tags: inventory_root, late_company_switch | question: а по этой компании что сейчас на складе?
- `address_truth_harness_phase16_multicompany_late_pivot:step_06_capability_meta_after_switch` | tags: capability_meta, meta_interrupt, meta_capability | question: а что ты вообще умеешь?
- `address_truth_harness_phase16_multicompany_late_pivot:step_07_receivables_march_2020_raym` | tags: receivables_root, meta_return_to_business, settlements_receivables | question: а по этой компании кто нам должен на март 2020?
- `address_truth_harness_phase16_multicompany_late_pivot:step_08_same_date_inventory_raym` | tags: same_date_pivot, inventory_root, second_company_continuity | question: а по этой же дате остатки на складе?
- `address_truth_harness_phase16_multicompany_late_pivot:step_09_switch_back_to_alt` | tags: late_company_switch_back, organization_authority | question: ок, верни обратно Альтернативу Плюс
- `address_truth_harness_phase16_multicompany_late_pivot:step_10_activity_age_after_switch_back` | tags: activity_age, switch_back_integrity | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_01_smalltalk_entry` | tags: smalltalk_entry, meta_smalltalk | question: приветик - че как там дела
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_02_capability_meta_entry` | tags: capability_meta, meta_capability | question: расскажи что можешь интересного
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_03_inventory_root_requires_company` | tags: inventory_root, clarification_required | question: кайф - что там на складе по остаткам?
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_04_company_choice_resumes_inventory_root` | tags: clarification_resume, inventory_root | question: АЛЬТЕРНАТИВА
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_05_historical_inventory_capability` | tags: inventory_capability_meta, meta_capability, meta_historical_capability, inventory_root | question: а исторические остатки на другие даты умеешь?
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_06_inventory_july_2017_after_capability` | tags: historical_inventory, date_followup, inventory_root | question: давай на июль 2017
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_07_inventory_march_2016` | tags: historical_inventory, date_followup, inventory_root | question: март 2016
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_08_counterparty_documents_chepurnov` | tags: counterparty_documents | question: по чепурнову покажи все доки
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_09_short_counterparty_retarget_svk` | tags: counterparty_short_retarget, display_label_integrity | question: а по свк
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail:step_10_counterparty_item_flow_chepurnov` | tags: counterparty_item_flow, stale_temporal_carryover, counterparty_shipment_fallback | question: что нам отгружал чепурнов? какой товар или услугу?
- `address_truth_harness_phase18_semantic_dialog_authority:step_01_human_smalltalk_entry` | tags: smalltalk_entry, human_answer, meta_smalltalk | question: приветик - че как там дела
- `address_truth_harness_phase18_semantic_dialog_authority:step_02_capability_meta` | tags: capability_meta, human_answer, meta_capability | question: расскажи что можешь интересного
- `address_truth_harness_phase18_semantic_dialog_authority:step_03_data_scope_meta` | tags: data_scope_meta, multi_company_entry, meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase18_semantic_dialog_authority:step_04_choose_organization` | tags: organization_authority, company_selected | question: Альтернатива Плюс
- `address_truth_harness_phase18_semantic_dialog_authority:step_05_counterparty_documents_root` | tags: counterparty_documents, stale_entity_seed | question: по чепурнову покажи все доки
- `address_truth_harness_phase18_semantic_dialog_authority:step_06_svk_turnover_overrides_stale_documents` | tags: current_turn_entity_authority, current_turn_action_authority, answer_top_block_matches_current_user_intent | question: какой оборот был свк
- `address_truth_harness_phase18_semantic_dialog_authority:step_07_unsupported_current_meaning_boundary` | tags: off_domain_living_chat, stale_replay_forbidden | question: а чем капибара отличается от утки?
- `address_truth_harness_phase19_mcp_discovery_response_gate:step_01_human_smalltalk_sanity` | tags: human_answer, mcp_discovery_gate_sanity, meta_smalltalk | question: привет, ты на связи?
- `address_truth_harness_phase19_mcp_discovery_response_gate:step_02_supported_counterparty_documents_stays_exact` | tags: counterparty_documents, supported_route_not_hijacked_by_mcp_discovery | question: покажи документы по свк за 2020
- `address_truth_harness_phase19_mcp_discovery_response_gate:step_03_counterparty_lifecycle_uses_guarded_discovery` | tags: mcp_discovery_response_gate, counterparty_lifecycle, unsupported_current_turn_meaning_boundary, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=lifecycle, selected_matches_top=True | question: сколько лет мы работаем с Группа СВК?
- `address_truth_harness_phase19_mcp_discovery_response_gate:step_04_counterparty_value_flow_uses_guarded_discovery` | tags: mcp_discovery_value_flow, counterparty_turnover, unsupported_current_turn_meaning_boundary, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: какой денежный поток был у Группа СВК за 2020 год?
- `address_truth_harness_phase19_mcp_discovery_response_gate:step_05_counterparty_supplier_payout_uses_guarded_discovery` | tags: mcp_discovery_supplier_payout, counterparty_outgoing_payments, unsupported_current_turn_meaning_boundary, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: сколько мы заплатили Группа СВК за 2020 год?
- `address_truth_harness_phase19_mcp_discovery_response_gate:step_06_counterparty_bidirectional_net_flow_uses_guarded_discovery` | tags: mcp_discovery_bidirectional_value_flow, counterparty_net_cash_flow, unsupported_current_turn_meaning_boundary, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?
- `address_truth_harness_phase19_mcp_discovery_response_gate:step_07_counterparty_bidirectional_monthly_net_flow_uses_guarded_discovery` | tags: mcp_discovery_bidirectional_value_flow, counterparty_monthly_net_cash_flow, multi_axis_aggregation, unsupported_current_turn_meaning_boundary, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: какое помесячное нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили по месяцам?
- `address_truth_harness_phase19_mcp_discovery_response_gate:step_08_off_domain_living_chat_not_hijacked` | tags: off_domain_living_chat, stale_replay_forbidden | question: а чем капибара отличается от утки?
- `address_truth_harness_phase20_continuity_stabilization:step_01_top_client_all_time` | tags: value_flow_ranking, temporal_tail_not_entity | question: кто у нас самый доходный клиент за все время?
- `address_truth_harness_phase20_continuity_stabilization:step_02_top_year_all_time` | tags: value_flow_ranking, year_tail_not_entity | question: какой у нас самый доходный год?
- `address_truth_harness_phase20_continuity_stabilization:step_03_receivables_as_of_may_2017` | tags: receivables_snapshot, exact_not_overwritten, settlements_receivables | question: кто нам должен денег на май 2017?
- `address_truth_harness_phase20_continuity_stabilization:step_04_vat_for_same_period` | tags: vat_followup, period_carryover, vat | question: а какой ндс мы должны примерно заплатить за этот период?
- `address_truth_harness_phase20_continuity_stabilization:step_05_payables_today` | tags: payables_snapshot, exact_not_overwritten | question: мы должны комуто денег на сегодня?
- `address_truth_harness_phase20_continuity_stabilization:step_06_receivables_pronoun_followup` | tags: pronoun_followup, garbage_anchor_forbidden | question: а нам?
- `address_truth_harness_phase21_net_followup_after_broad_eval:step_01_company_activity_lifecycle` | tags: company_activity_lifecycle, context_seed, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=lifecycle, selected_matches_top=True | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `address_truth_harness_phase21_net_followup_after_broad_eval:step_02_broad_company_evaluation` | tags: broad_evaluation_bridge | question: Как ты оценишь деятельность компании?
- `address_truth_harness_phase21_net_followup_after_broad_eval:step_03_net_flow_after_broad_eval` | tags: counterparty_net_cash_flow, stale_lifecycle_override, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?
- `address_truth_harness_phase22_broad_business_evaluation_bridge:step_01_company_activity_lifecycle` | tags: company_activity_lifecycle, grounded_context_seed, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=lifecycle, selected_matches_top=True | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `address_truth_harness_phase22_broad_business_evaluation_bridge:step_02_broad_business_evaluation` | tags: broad_business_evaluation, grounded_summary | question: Как ты оценишь деятельность компании?
- `address_truth_harness_phase22_broad_business_evaluation_bridge:step_03_net_flow_after_broad_eval` | tags: counterparty_net_cash_flow, broad_eval_bridge_preserved, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?
- `address_truth_harness_phase23_broad_eval_short_followup:step_01_net_flow_2020` | tags: counterparty_net_cash_flow, grounded_discovery_seed | question: какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?
- `address_truth_harness_phase23_broad_eval_short_followup:step_02_broad_business_evaluation` | tags: broad_business_evaluation, context_reframe | question: Как ты оценишь деятельность компании?
- `address_truth_harness_phase23_broad_eval_short_followup:step_03_short_year_switch_after_broad_eval` | tags: broad_eval_followup_continuity, year_switch_after_reframe | question: а теперь за 2021?
- `address_truth_harness_phase24_metadata_lane_choice_loop:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase24_metadata_lane_choice_loop:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase24_metadata_lane_choice_loop:step_03_choose_movement_lane` | tags: lane_choice_reuse, movement_lane_after_clarification | question: по движениям
- `address_truth_harness_phase25_entity_resolution_chain:step_01_resolve_counterparty_from_catalog` | tags: entity_resolution, catalog_grounding, bounded_autonomy | question: найди в 1С контрагента Группа СВК
- `address_truth_harness_phase26_entity_followup_chain:step_01_resolve_counterparty_alias` | tags: entity_resolution, alias_grounding, followup_anchor | question: найди в 1С контрагента СВК
- `address_truth_harness_phase26_entity_followup_chain:step_02_documents_by_resolved_entity_followup` | tags: entity_resolution, document_evidence, followup_reuse | question: по нему документы за 2020 год
- `address_truth_harness_phase26_entity_followup_chain:step_03_movements_by_resolved_entity_followup` | tags: entity_resolution, movement_evidence, followup_reuse | question: а теперь по нему движения за 2020 год
- `address_truth_harness_phase27_entity_value_followup_chain:step_01_resolve_counterparty_alias` | tags: entity_resolution, alias_grounding, followup_anchor | question: найди в 1С контрагента СВК
- `address_truth_harness_phase27_entity_value_followup_chain:step_02_value_flow_by_resolved_entity_followup` | tags: entity_resolution, counterparty_value_flow, followup_reuse | question: сколько получили по нему за 2020 год
- `address_truth_harness_phase27_entity_value_followup_chain:step_03_net_flow_by_resolved_entity_followup` | tags: entity_resolution, counterparty_net_value_flow, followup_reuse | question: а какое нетто по нему за 2020 год
- `address_truth_harness_phase28_entity_value_retarget_chain:step_01_resolve_counterparty_alias` | tags: entity_resolution, alias_grounding, followup_anchor | question: найди в 1С контрагента СВК
- `address_truth_harness_phase28_entity_value_retarget_chain:step_02_incoming_by_resolved_entity` | tags: entity_resolution, incoming_value_flow, followup_reuse | question: сколько получили по нему за 2020 год
- `address_truth_harness_phase28_entity_value_retarget_chain:step_03_payout_switch_by_resolved_entity` | tags: entity_resolution, payout_switch, followup_reuse, date_carryover | question: а теперь сколько заплатили?
- `address_truth_harness_phase28_entity_value_retarget_chain:step_04_year_switch_on_payout` | tags: entity_resolution, payout_year_switch, followup_reuse | question: а за 2021?
- `address_truth_harness_phase28_entity_value_retarget_chain:step_05_net_switch_after_payout` | tags: entity_resolution, net_switch, followup_reuse, date_carryover | question: а какое нетто?
- `address_truth_harness_phase29_value_flow_to_documents_chain:step_01_resolve_counterparty_alias` | tags: entity_resolution, alias_grounding, followup_anchor | question: найди в 1С контрагента СВК
- `address_truth_harness_phase29_value_flow_to_documents_chain:step_02_incoming_by_resolved_entity` | tags: entity_resolution, incoming_value_flow, followup_reuse | question: сколько получили по нему за 2020 год
- `address_truth_harness_phase29_value_flow_to_documents_chain:step_03_payout_switch_by_resolved_entity` | tags: entity_resolution, payout_switch, followup_reuse, date_carryover | question: а теперь сколько заплатили?
- `address_truth_harness_phase29_value_flow_to_documents_chain:step_04_year_switch_on_payout` | tags: entity_resolution, payout_year_switch, followup_reuse | question: а за 2021?
- `address_truth_harness_phase29_value_flow_to_documents_chain:step_05_documents_after_value_flow` | tags: entity_resolution, document_evidence, value_flow_pivot, followup_reuse | question: а по документам?
- `address_truth_harness_phase30_value_flow_to_movements_chain:step_01_resolve_counterparty_alias` | tags: entity_resolution, alias_grounding, followup_anchor | question: найди в 1С контрагента СВК
- `address_truth_harness_phase30_value_flow_to_movements_chain:step_02_incoming_by_resolved_entity` | tags: entity_resolution, incoming_value_flow, followup_reuse | question: сколько получили по нему за 2020 год
- `address_truth_harness_phase30_value_flow_to_movements_chain:step_03_payout_switch_by_resolved_entity` | tags: entity_resolution, payout_switch, followup_reuse, date_carryover | question: а теперь сколько заплатили?
- `address_truth_harness_phase30_value_flow_to_movements_chain:step_04_year_switch_on_payout` | tags: entity_resolution, payout_year_switch, followup_reuse | question: а за 2021?
- `address_truth_harness_phase30_value_flow_to_movements_chain:step_05_movements_after_value_flow` | tags: entity_resolution, movement_evidence, value_flow_pivot, followup_reuse | question: а по движениям?
- `address_truth_harness_phase31_entity_ambiguity_probe:step_01_probe_ambiguous_counterparty_search` | tags: entity_resolution, ambiguity_probe, bounded_autonomy | question: найди в 1С контрагента СВК
- `address_truth_harness_phase32_planner_selected_chain_end_to_end:step_01_resolve_counterparty_alias` | tags: entity_resolution, alias_grounding, followup_anchor, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=entity_resolution, selected_matches_top=True | question: найди в 1С контрагента СВК
- `address_truth_harness_phase32_planner_selected_chain_end_to_end:step_02_incoming_by_resolved_entity` | tags: entity_resolution, incoming_value_flow, followup_reuse, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: сколько получили по нему за 2020 год
- `address_truth_harness_phase32_planner_selected_chain_end_to_end:step_03_payout_switch_by_resolved_entity` | tags: entity_resolution, payout_switch, followup_reuse, date_carryover, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: а теперь сколько заплатили?
- `address_truth_harness_phase32_planner_selected_chain_end_to_end:step_04_net_after_payout` | tags: entity_resolution, net_value_flow, followup_reuse, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: а какое нетто?
- `address_truth_harness_phase32_planner_selected_chain_end_to_end:step_05_documents_after_net` | tags: entity_resolution, document_evidence, value_flow_pivot, followup_reuse, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=document_evidence, selected_matches_top=True | question: а по документам?
- `address_truth_harness_phase32_planner_selected_chain_end_to_end:step_06_movements_after_documents` | tags: entity_resolution, movement_evidence, document_pivot, followup_reuse, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=movement_evidence, selected_matches_top=True | question: а по движениям?
- `address_truth_harness_phase33_open_scope_value_flow_comparison:step_01_compare_incoming_vs_outgoing_for_org` | tags: value_flow_comparison, open_scope, organization_scoped, bounded_autonomy | question: что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?
- `address_truth_harness_phase34_open_scope_value_flow_totals:step_01_incoming_total_for_org` | tags: value_flow_total, incoming, open_scope, organization_scoped, bounded_autonomy | question: Сколько входящих денег за 2020 год по ООО Альтернатива Плюс?
- `address_truth_harness_phase34_open_scope_value_flow_totals:step_02_outgoing_total_for_org` | tags: value_flow_total, outgoing, open_scope, organization_scoped, bounded_autonomy | question: Сколько исходящих денег за 2020 год по ООО Альтернатива Плюс?
- `address_truth_harness_phase35_open_scope_org_clarification:step_01_generic_incoming_total_requires_organization` | tags: value_flow_total, incoming, open_scope, organization_clarification, bounded_autonomy | question: Сколько входящих денег за 2020 год?
- `address_truth_harness_phase35_open_scope_org_clarification:step_02_org_clarification_resumes_incoming_total` | tags: value_flow_total, incoming, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase35_open_scope_org_clarification:step_03_outgoing_followup_reuses_org_and_period` | tags: value_flow_total, outgoing, organization_followup_reuse, bounded_autonomy | question: а исходящих?
- `address_truth_harness_phase36_open_scope_year_switch_after_org_clarification:step_01_generic_incoming_total_requires_organization` | tags: value_flow_total, incoming, open_scope, organization_clarification, bounded_autonomy | question: Сколько входящих денег за 2020 год?
- `address_truth_harness_phase36_open_scope_year_switch_after_org_clarification:step_02_org_clarification_resumes_incoming_total` | tags: value_flow_total, incoming, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase36_open_scope_year_switch_after_org_clarification:step_03_year_switch_reuses_org_and_axis` | tags: value_flow_total, incoming, year_switch, organization_followup_reuse, bounded_autonomy | question: а за 2021?
- `address_truth_harness_phase36_open_scope_year_switch_after_org_clarification:step_04_outgoing_followup_reuses_org_and_new_year` | tags: value_flow_total, outgoing, year_switch, organization_followup_reuse, bounded_autonomy | question: а исходящих?
- `address_truth_harness_phase37_open_scope_comparison_org_clarification:step_01_open_scope_comparison_requires_organization` | tags: value_flow_comparison, open_scope, organization_clarification, bounded_autonomy | question: Что больше за 2020 год: входящих денег или исходящих?
- `address_truth_harness_phase37_open_scope_comparison_org_clarification:step_02_org_clarification_resumes_comparison` | tags: value_flow_comparison, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase37_open_scope_comparison_org_clarification:step_03_year_switch_reuses_org_and_comparison` | tags: value_flow_comparison, year_switch, organization_followup_reuse, bounded_autonomy | question: а за 2021?
- `address_truth_harness_phase38_open_scope_net_org_clarification:step_01_open_scope_net_requires_organization` | tags: value_flow_net, open_scope, organization_clarification, bounded_autonomy | question: Какое нетто по деньгам за 2020 год: сколько получили и сколько заплатили?
- `address_truth_harness_phase38_open_scope_net_org_clarification:step_02_org_clarification_resumes_net` | tags: value_flow_net, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase38_open_scope_net_org_clarification:step_03_year_switch_reuses_org_and_net` | tags: value_flow_net, year_switch, organization_followup_reuse, bounded_autonomy | question: а за 2021?
- `address_truth_harness_phase39_open_scope_ranking_org_clarification:step_01_open_scope_ranking_requires_organization` | tags: value_flow_ranking, open_scope, organization_clarification, bounded_autonomy | question: Кто больше всего принес денег за 2020 год?
- `address_truth_harness_phase39_open_scope_ranking_org_clarification:step_02_org_clarification_resumes_ranking` | tags: value_flow_ranking, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase39_open_scope_ranking_org_clarification:step_03_year_switch_reuses_org_and_ranking` | tags: value_flow_ranking, year_switch, organization_followup_reuse, bounded_autonomy | question: а за 2021?
- `address_truth_harness_phase40_open_scope_all_time_followup:step_01_year_total_for_org` | tags: value_flow_total, incoming, organization_scoped, year_specific, bounded_autonomy | question: Сколько входящих денег за 2017 год по ООО Альтернатива Плюс?
- `address_truth_harness_phase40_open_scope_all_time_followup:step_02_all_time_followup_reuses_org_without_stale_year` | tags: value_flow_total, incoming, open_scope, all_time_scope, organization_followup_reuse, bounded_autonomy | question: сколько вообще денег мы заработали за все время?
- `address_truth_harness_phase41_saved_chain_all_time_revenue_followup:step_01_top_revenue_year` | tags: value_flow_ranking, self_scope, saved_chain, bounded_autonomy | question: какой у нас самый доходный год
- `address_truth_harness_phase41_saved_chain_all_time_revenue_followup:step_02_year_followup_2017` | tags: value_flow_total, year_switch, self_scope, saved_chain, bounded_autonomy | question: а за 2017 мы скок заработали?
- `address_truth_harness_phase41_saved_chain_all_time_revenue_followup:step_03_all_time_followup` | tags: value_flow_total, all_time_scope, self_scope, saved_chain, bounded_autonomy | question: сколько вообще денег мы заработали за все время?
- `address_truth_harness_phase42_catalog_metadata_drilldown:step_01_catalog_metadata_surface` | tags: catalog_metadata_surface, counterparty_catalog_scope | question: какие справочники 1С есть по контрагентам?
- `address_truth_harness_phase42_catalog_metadata_drilldown:step_02_neutral_followup_catalog_drilldown` | tags: catalog_drilldown, neutral_followup | question: давай дальше
- `address_truth_harness_phase43_multi_hop_ranking_clarification_loop:step_01_ranking_requires_org_and_period` | tags: value_flow_ranking, multi_hop_clarification, organization_scope, period_scope, bounded_autonomy | question: Кто больше всего принес денег?
- `address_truth_harness_phase43_multi_hop_ranking_clarification_loop:step_02_org_only_clarification_keeps_same_loop` | tags: value_flow_ranking, multi_hop_clarification, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase43_multi_hop_ranking_clarification_loop:step_03_period_clarification_completes_same_ranking_loop` | tags: value_flow_ranking, multi_hop_clarification, period_followup_reuse, bounded_autonomy | question: за 2020 год
- `address_truth_harness_phase44_multi_hop_comparison_clarification_loop:step_01_comparison_requires_org_and_period` | tags: value_flow_comparison, multi_hop_clarification, organization_scope, period_scope, bounded_autonomy | question: Что больше: входящих денег или исходящих?
- `address_truth_harness_phase44_multi_hop_comparison_clarification_loop:step_02_org_only_clarification_keeps_same_comparison_loop` | tags: value_flow_comparison, multi_hop_clarification, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase44_multi_hop_comparison_clarification_loop:step_03_period_clarification_completes_same_comparison_loop` | tags: value_flow_comparison, multi_hop_clarification, period_followup_reuse, bounded_autonomy | question: за 2020 год
- `address_truth_harness_phase45_multi_hop_open_total_clarification_loop:step_01_open_total_requires_org_and_period` | tags: value_flow_total, multi_hop_clarification, organization_scope, period_scope, bounded_autonomy | question: Сколько входящих денег?
- `address_truth_harness_phase45_multi_hop_open_total_clarification_loop:step_02_org_only_clarification_keeps_same_total_loop` | tags: value_flow_total, multi_hop_clarification, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase45_multi_hop_open_total_clarification_loop:step_03_period_clarification_completes_same_total_loop` | tags: value_flow_total, multi_hop_clarification, period_followup_reuse, bounded_autonomy | question: за 2020 год
- `address_truth_harness_phase46_multi_hop_open_net_clarification_loop:step_01_open_net_requires_org_and_period` | tags: value_flow_net, multi_hop_clarification, organization_scope, period_scope, bounded_autonomy | question: Какое нетто по деньгам: сколько получили и сколько заплатили?
- `address_truth_harness_phase46_multi_hop_open_net_clarification_loop:step_02_org_only_clarification_keeps_same_net_loop` | tags: value_flow_net, multi_hop_clarification, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase46_multi_hop_open_net_clarification_loop:step_03_period_clarification_completes_same_net_loop` | tags: value_flow_net, multi_hop_clarification, period_followup_reuse, bounded_autonomy | question: за 2020 год
- `address_truth_harness_phase47_multi_hop_open_net_period_first_clarification_loop:step_01_open_net_requires_org_and_period` | tags: open_scope_net, multi_hop_clarification, organization_scope, period_scope, bounded_autonomy | question: Какое нетто по деньгам: сколько получили и сколько заплатили?
- `address_truth_harness_phase47_multi_hop_open_net_period_first_clarification_loop:step_02_period_only_clarification_keeps_same_net_loop` | tags: open_scope_net, multi_hop_clarification, period_followup_reuse, bounded_autonomy | question: за 2020 год
- `address_truth_harness_phase47_multi_hop_open_net_period_first_clarification_loop:step_03_org_clarification_completes_same_net_loop` | tags: open_scope_net, multi_hop_clarification, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase48_multi_hop_open_total_all_time_clarification_loop:step_01_open_total_requires_org_and_period` | tags: open_scope_total, multi_hop_clarification, organization_scope, period_scope, bounded_autonomy | question: Сколько вообще входящих денег было?
- `address_truth_harness_phase48_multi_hop_open_total_all_time_clarification_loop:step_02_org_only_clarification_keeps_same_total_loop` | tags: open_scope_total, multi_hop_clarification, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase48_multi_hop_open_total_all_time_clarification_loop:step_03_all_time_clarification_completes_same_total_loop` | tags: open_scope_total, all_time_scope, multi_hop_clarification, bounded_autonomy | question: за все время
- `address_truth_harness_phase49_multi_hop_open_ranking_all_time_clarification_loop:step_01_open_ranking_requires_org_and_period` | tags: value_flow_ranking, multi_hop_clarification, organization_scope, period_scope, bounded_autonomy | question: Кто больше всего принес денег?
- `address_truth_harness_phase49_multi_hop_open_ranking_all_time_clarification_loop:step_02_org_only_clarification_keeps_same_ranking_loop` | tags: value_flow_ranking, multi_hop_clarification, organization_followup_reuse, bounded_autonomy | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase49_multi_hop_open_ranking_all_time_clarification_loop:step_03_all_time_clarification_completes_same_ranking_loop` | tags: value_flow_ranking, all_time_scope, multi_hop_clarification, bounded_autonomy | question: за все время
- `address_truth_harness_phase4_coverage_evidence_mix:step_01_counterparty_documents` | tags: counterparty_documents | question: покажи все документы по чепурнову
- `address_truth_harness_phase4_coverage_evidence_mix:step_02_counterparty_shipments_or_fallback` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
- `address_truth_harness_phase4_coverage_evidence_mix:step_03_inventory_reset_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase4_coverage_evidence_mix:step_04_selected_item_supplier_temporal_limit` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase4_coverage_evidence_mix:step_05_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
- `address_truth_harness_phase4_inventory_answer_shape_continuity:step_01_inventory_root_requires_company` | tags: inventory_root, company_clarification | question: какие остатки на складе на март 2021
- `address_truth_harness_phase4_inventory_answer_shape_continuity:step_02_choose_company` | tags: meta_scope, company_selection | question: давай по Альтернативе Плюс
- `address_truth_harness_phase4_inventory_answer_shape_continuity:step_03_inventory_root_after_company` | tags: inventory_root | question: тогда покажи остатки на март 2021
- `address_truth_harness_phase4_inventory_answer_shape_continuity:step_04_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase4_inventory_answer_shape_continuity:step_05_selected_item_purchase_date` | tags: selected_object, selected_object_supplier | question: а по этой позиции когда была закупка?
- `address_truth_harness_phase4_inventory_answer_shape_continuity:step_06_selected_item_documents` | tags: selected_object, selected_object_documents | question: покажи документы по этой позиции
- `address_truth_harness_phase4_inventory_answer_shape_continuity:step_07_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
- `address_truth_harness_phase4_inventory_answer_shape_continuity:step_08_memory_recap_business_only` | tags: meta_memory | question: а что мы уже выяснили по этой позиции?
- `address_truth_harness_phase50_metadata_movement_org_period_clarification_loop:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase50_metadata_movement_org_period_clarification_loop:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase50_metadata_movement_org_period_clarification_loop:step_03_movement_lane_requires_org_and_period` | tags: movement_lane_after_clarification, remaining_gaps, organization_scope, period_scope | question: по движениям
- `address_truth_harness_phase50_metadata_movement_org_period_clarification_loop:step_04_org_only_clarification_keeps_same_movement_loop` | tags: movement_lane_after_clarification, organization_followup_reuse, period_scope | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase51_metadata_lane_choice_with_org_inline:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase51_metadata_lane_choice_with_org_inline:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase51_metadata_lane_choice_with_org_inline:step_03_inline_lane_choice_with_org_keeps_only_period_gap` | tags: movement_lane_after_clarification, inline_organization_clarification, remaining_period_gap_only | question: по движениям по ООО Альтернатива Плюс
- `address_truth_harness_phase52_metadata_movement_full_recovery:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase52_metadata_movement_full_recovery:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase52_metadata_movement_full_recovery:step_03_inline_lane_choice_with_org_keeps_only_period_gap` | tags: movement_lane_after_clarification, inline_organization_clarification, remaining_period_gap_only | question: по движениям по ООО Альтернатива Плюс
- `address_truth_harness_phase52_metadata_movement_full_recovery:step_04_period_clarification_executes_same_movement_loop` | tags: movement_lane_execution, period_gap_closed, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase53_metadata_movement_period_first_recovery:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase53_metadata_movement_period_first_recovery:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase53_metadata_movement_period_first_recovery:step_03_movement_lane_requires_both_gaps` | tags: movement_lane_after_clarification, remaining_gaps | question: по движениям
- `address_truth_harness_phase53_metadata_movement_period_first_recovery:step_04_period_first_keeps_only_organization_gap` | tags: period_first_recovery, remaining_org_gap_only | question: за 2020 год
- `address_truth_harness_phase53_metadata_movement_period_first_recovery:step_05_organization_closes_same_loop_and_executes_retrieval` | tags: organization_second_recovery, bounded_retrieval | question: по ООО Альтернатива Плюс
- `address_truth_harness_phase54_metadata_document_full_recovery:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase54_metadata_document_full_recovery:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase54_metadata_document_full_recovery:step_03_inline_document_choice_with_org_keeps_only_period_gap` | tags: document_lane_after_clarification, inline_organization_clarification, remaining_period_gap_only | question: по документам по ООО Альтернатива Плюс
- `address_truth_harness_phase54_metadata_document_full_recovery:step_04_period_clarification_executes_same_document_loop` | tags: document_lane_execution, period_gap_closed, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase55_metadata_movement_year_switch_after_retrieval:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase55_metadata_movement_year_switch_after_retrieval:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase55_metadata_movement_year_switch_after_retrieval:step_03_inline_lane_choice_with_org_keeps_only_period_gap` | tags: movement_lane_after_clarification, inline_organization_clarification | question: по движениям по ООО Альтернатива Плюс
- `address_truth_harness_phase55_metadata_movement_year_switch_after_retrieval:step_04_period_clarification_executes_same_movement_loop` | tags: movement_lane_execution, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase55_metadata_movement_year_switch_after_retrieval:step_05_year_switch_keeps_same_movement_loop` | tags: year_switch_followup, movement_lane_continuity, same_organization_reuse | question: а теперь за 2021?
- `address_truth_harness_phase56_metadata_retrieval_to_document_pivot:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase56_metadata_retrieval_to_document_pivot:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase56_metadata_retrieval_to_document_pivot:step_03_inline_lane_choice_with_org_keeps_only_period_gap` | tags: movement_lane_after_clarification, inline_organization_clarification | question: по движениям по ООО Альтернатива Плюс
- `address_truth_harness_phase56_metadata_retrieval_to_document_pivot:step_04_period_clarification_executes_same_movement_loop` | tags: movement_lane_execution, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase56_metadata_retrieval_to_document_pivot:step_05_document_pivot_keeps_same_scope` | tags: document_pivot_after_retrieval, scope_reuse, same_proof_path_family_shift | question: а теперь по документам?
- `address_truth_harness_phase57_metadata_movement_all_time_after_retrieval:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase57_metadata_movement_all_time_after_retrieval:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase57_metadata_movement_all_time_after_retrieval:step_03_inline_lane_choice_with_org_keeps_only_period_gap` | tags: movement_lane_after_clarification, inline_organization_clarification | question: по движениям по ООО Альтернатива Плюс
- `address_truth_harness_phase57_metadata_movement_all_time_after_retrieval:step_04_period_clarification_executes_same_movement_loop` | tags: movement_lane_execution, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase57_metadata_movement_all_time_after_retrieval:step_05_all_time_followup_keeps_same_movement_loop` | tags: all_time_followup, movement_lane_continuity, period_cleared | question: а теперь за все время?
- `address_truth_harness_phase58_metadata_document_to_movement_pivot:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase58_metadata_document_to_movement_pivot:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase58_metadata_document_to_movement_pivot:step_03_document_lane_with_org_keeps_only_period_gap` | tags: document_lane_after_clarification, inline_organization_clarification | question: по документам по ООО Альтернатива Плюс
- `address_truth_harness_phase58_metadata_document_to_movement_pivot:step_04_period_clarification_executes_same_document_loop` | tags: document_lane_execution, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase58_metadata_document_to_movement_pivot:step_05_movement_pivot_keeps_same_scope` | tags: movement_pivot_after_document_retrieval, scope_reuse, same_proof_path_family_shift | question: а теперь по движениям?
- `address_truth_harness_phase59_metadata_document_all_time_after_retrieval:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase59_metadata_document_all_time_after_retrieval:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase59_metadata_document_all_time_after_retrieval:step_03_inline_document_choice_with_org_keeps_only_period_gap` | tags: document_lane_after_clarification, inline_organization_clarification | question: по документам по ООО Альтернатива Плюс
- `address_truth_harness_phase59_metadata_document_all_time_after_retrieval:step_04_period_clarification_executes_same_document_loop` | tags: document_lane_execution, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase59_metadata_document_all_time_after_retrieval:step_05_all_time_followup_keeps_same_document_loop` | tags: all_time_followup, document_lane_continuity, period_cleared | question: а теперь за все время?
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_01_smalltalk` | tags: meta_smalltalk | question: привет, как дела?
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_03_inventory_root_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_04_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_05_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_06_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_07_organization_fact_boundary` | tags: meta_scope, organization_fact_boundary | question: а какой возраст у Альтернативы Плюс?
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_08_capability_meta_interrupt` | tags: meta_capability | question: что ты умеешь?
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_09_memory_recap_after_interrupts` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_10_receivables_march_2020` | tags: settlements_receivables | question: кто нам должен на март 2020
- `address_truth_harness_phase5_assistantservice_boundary_transition_mix:step_11_inventory_same_date_cross_domain` | tags: inventory_root, same_date_pivot | question: остатки по складу на эту же дату
- `address_truth_harness_phase5_company_selection_and_activity_age:step_01_smalltalk` | tags: meta_smalltalk | question: привет, как дела?
- `address_truth_harness_phase5_company_selection_and_activity_age:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase5_company_selection_and_activity_age:step_03_inventory_root_requires_company` | tags: inventory_root, company_clarification | question: какие остатки на складе на март 2021
- `address_truth_harness_phase5_company_selection_and_activity_age:step_04_choose_company` | tags: meta_scope, company_selection | question: давай по Альтернативе Плюс
- `address_truth_harness_phase5_company_selection_and_activity_age:step_05_inventory_root_after_company` | tags: inventory_root, company_selected | question: тогда покажи остатки на март 2021
- `address_truth_harness_phase5_company_selection_and_activity_age:step_06_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase5_company_selection_and_activity_age:step_07_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `address_truth_harness_phase5_company_selection_and_activity_age:step_08_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
- `address_truth_harness_phase5_company_selection_and_activity_age:step_09_company_activity_age` | tags: organization_activity_age, company_selected | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `address_truth_harness_phase5_company_selection_and_activity_age:step_10_capability_meta_interrupt` | tags: meta_capability | question: что ты умеешь?
- `address_truth_harness_phase5_company_selection_and_activity_age:step_11_memory_recap_after_interrupts` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `address_truth_harness_phase5_company_selection_and_activity_age:step_12_receivables_march_2020` | tags: settlements_receivables | question: кто нам должен на март 2020
- `address_truth_harness_phase5_company_selection_and_activity_age:step_13_inventory_same_date_cross_domain` | tags: inventory_root, same_date_pivot | question: остатки по складу на эту же дату
- `address_truth_harness_phase5_meta_memory_mix:step_01_inventory_root_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase5_meta_memory_mix:step_02_inventory_history_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `address_truth_harness_phase5_meta_memory_mix:step_03_data_scope_meta_interrupt` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase5_meta_memory_mix:step_04_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase5_meta_memory_mix:step_05_capability_meta_interrupt` | tags: meta_capability | question: что ты умеешь?
- `address_truth_harness_phase5_meta_memory_mix:step_06_memory_recap_after_interrupts` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `address_truth_harness_phase60_metadata_document_pivot_year_switch:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase60_metadata_document_pivot_year_switch:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase60_metadata_document_pivot_year_switch:step_03_inline_lane_choice_with_org_keeps_only_period_gap` | tags: movement_lane_after_clarification, inline_organization_clarification | question: по движениям по ООО Альтернатива Плюс
- `address_truth_harness_phase60_metadata_document_pivot_year_switch:step_04_period_clarification_executes_same_movement_loop` | tags: movement_lane_execution, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase60_metadata_document_pivot_year_switch:step_05_document_pivot_keeps_same_scope` | tags: document_pivot_after_retrieval, scope_reuse | question: а теперь по документам?
- `address_truth_harness_phase60_metadata_document_pivot_year_switch:step_06_year_switch_keeps_document_lane` | tags: year_switch_followup, document_lane_continuity, post_pivot_continuity | question: а теперь за 2021?
- `address_truth_harness_phase61_metadata_movement_pivot_year_switch:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase61_metadata_movement_pivot_year_switch:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase61_metadata_movement_pivot_year_switch:step_03_document_lane_with_org_keeps_only_period_gap` | tags: document_lane_after_clarification, inline_organization_clarification | question: по документам по ООО Альтернатива Плюс
- `address_truth_harness_phase61_metadata_movement_pivot_year_switch:step_04_period_clarification_executes_same_document_loop` | tags: document_lane_execution, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase61_metadata_movement_pivot_year_switch:step_05_movement_pivot_keeps_same_scope` | tags: movement_pivot_after_document_retrieval, scope_reuse, same_proof_path_family_shift | question: а теперь по движениям?
- `address_truth_harness_phase61_metadata_movement_pivot_year_switch:step_06_year_switch_keeps_movement_lane` | tags: movement_lane_continuity, year_switch_after_pivot, same_scope_new_period | question: а теперь за 2021?
- `address_truth_harness_phase62_metadata_movement_pivot_all_time:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase62_metadata_movement_pivot_all_time:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase62_metadata_movement_pivot_all_time:step_03_document_lane_with_org_keeps_only_period_gap` | tags: document_lane_after_clarification, inline_organization_clarification | question: по документам по ООО Альтернатива Плюс
- `address_truth_harness_phase62_metadata_movement_pivot_all_time:step_04_period_clarification_executes_same_document_loop` | tags: document_lane_execution, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase62_metadata_movement_pivot_all_time:step_05_movement_pivot_keeps_same_scope` | tags: movement_pivot_after_document_retrieval, scope_reuse, same_proof_path_family_shift | question: а теперь по движениям?
- `address_truth_harness_phase62_metadata_movement_pivot_all_time:step_06_all_time_followup_keeps_movement_lane` | tags: all_time_followup, movement_lane_continuity, period_cleared_after_pivot | question: а теперь за все время?
- `address_truth_harness_phase63_metadata_document_pivot_all_time:step_01_metadata_ambiguity_surface` | tags: metadata_surface, mixed_ambiguity, vat | question: какие объекты 1С есть по НДС?
- `address_truth_harness_phase63_metadata_document_pivot_all_time:step_02_neutral_followup_requires_lane_choice` | tags: metadata_lane_choice_clarification, neutral_followup | question: давай дальше
- `address_truth_harness_phase63_metadata_document_pivot_all_time:step_03_movement_lane_with_org_keeps_only_period_gap` | tags: movement_lane_after_clarification, inline_organization_clarification | question: по движениям по ООО Альтернатива Плюс
- `address_truth_harness_phase63_metadata_document_pivot_all_time:step_04_period_clarification_executes_same_movement_loop` | tags: movement_lane_execution, bounded_retrieval | question: за 2020 год
- `address_truth_harness_phase63_metadata_document_pivot_all_time:step_05_document_pivot_keeps_same_scope` | tags: document_pivot_after_movement_retrieval, scope_reuse, same_proof_path_family_shift | question: а теперь по документам?
- `address_truth_harness_phase63_metadata_document_pivot_all_time:step_06_all_time_followup_keeps_document_lane` | tags: all_time_followup, document_lane_continuity, period_cleared_after_pivot | question: а теперь за все время?
- `address_truth_harness_phase64_human_vat_investigation_dialog:step_01_metadata_orientation` | tags: metadata_surface, vat_orientation, human_dialog, vat | question: Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?
- `address_truth_harness_phase64_human_vat_investigation_dialog:step_02_choose_movements_with_org` | tags: movement_lane_after_metadata, inline_organization_clarification, human_dialog | question: Хорошо, тогда покажи движения по ООО Альтернатива Плюс.
- `address_truth_harness_phase64_human_vat_investigation_dialog:step_03_execute_movement_slice` | tags: movement_lane_execution, bounded_retrieval, human_dialog | question: За 2020 год.
- `address_truth_harness_phase64_human_vat_investigation_dialog:step_04_document_pivot_same_scope` | tags: document_pivot_after_movement_retrieval, scope_reuse, human_dialog | question: А теперь по документам?
- `address_truth_harness_phase64_human_vat_investigation_dialog:step_05_document_year_switch` | tags: document_lane_continuity, year_switch_after_pivot, human_dialog | question: А теперь за 2021 год?
- `address_truth_harness_phase64_human_vat_investigation_dialog:step_06_document_all_time_followup` | tags: document_lane_continuity, all_time_followup, human_dialog | question: А теперь за все время?
- `address_truth_harness_phase65_human_svk_money_dialog:step_01_ground_counterparty` | tags: entity_grounding, counterparty_resolution, human_dialog | question: Хочу проверить одного контрагента. Найди в 1С Группу СВК.
- `address_truth_harness_phase65_human_svk_money_dialog:step_02_incoming_value_flow` | tags: incoming_value_flow, grounded_counterparty_followup, human_dialog | question: Посмотри, сколько денег мы получили от него за 2020 год.
- `address_truth_harness_phase65_human_svk_money_dialog:step_03_outgoing_value_flow` | tags: outgoing_value_flow, grounded_counterparty_followup, human_dialog | question: А теперь сколько мы ему заплатили?
- `address_truth_harness_phase65_human_svk_money_dialog:step_04_net_value_flow` | tags: net_value_flow, grounded_counterparty_followup, human_dialog | question: А какое получилось нетто?
- `address_truth_harness_phase65_human_svk_money_dialog:step_05_document_pivot` | tags: document_pivot_after_value_flow, grounded_counterparty_followup, human_dialog | question: А по документам?
- `address_truth_harness_phase65_human_svk_money_dialog:step_06_movement_pivot` | tags: movement_pivot_after_value_flow, grounded_counterparty_followup, human_dialog | question: А по движениям?
- `address_truth_harness_phase65_human_svk_money_dialog:step_07_year_switch_same_counterparty` | tags: year_switch, grounded_counterparty_followup, human_dialog | question: А теперь за 2021 год?
- `address_truth_harness_phase66_human_org_open_scope_dialog:step_01_open_scope_incoming_total` | tags: open_scope_total, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?
- `address_truth_harness_phase66_human_org_open_scope_dialog:step_02_all_time_same_open_scope` | tags: organization_clarification, open_scope_total, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: По ООО Альтернатива Плюс.
- `address_truth_harness_phase66_human_org_open_scope_dialog:step_03_all_time_same_open_scope` | tags: all_time_followup, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: Понял, тогда за все время.
- `address_truth_harness_phase66_human_org_open_scope_dialog:step_04_bidirectional_comparison` | tags: value_flow_comparison, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?
- `address_truth_harness_phase66_human_org_open_scope_dialog:step_05_comparison_year_switch` | tags: value_flow_comparison, year_switch, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?
- `address_truth_harness_phase66_human_org_open_scope_dialog:step_06_ranking_top_counterparty` | tags: value_flow_ranking, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_ranking, selected_matches_top=True | question: И кто больше всего принес денег этой организации в 2020 году?
- `address_truth_harness_phase66_human_org_open_scope_dialog:step_07_ranking_year_switch` | tags: value_flow_ranking, year_switch, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_ranking, selected_matches_top=True | question: А в 2021 году?
- `address_truth_harness_phase67_svk_grounded_counterparty_integrity:step_01_ground_counterparty` | tags: entity_grounding, counterparty_resolution, integrity_guard | question: Хочу проверить одного контрагента. Найди в 1С Группу СВК.
- `address_truth_harness_phase67_svk_grounded_counterparty_integrity:step_02_incoming_2020` | tags: incoming_value_flow, grounded_counterparty_followup, integrity_guard | question: Посмотри, сколько денег мы получили от него за 2020 год.
- `address_truth_harness_phase67_svk_grounded_counterparty_integrity:step_03_payout_2020_same_counterparty` | tags: outgoing_value_flow, grounded_counterparty_followup, integrity_guard | question: А теперь сколько мы ему заплатили?
- `address_truth_harness_phase67_svk_grounded_counterparty_integrity:step_04_net_same_counterparty` | tags: net_value_flow, grounded_counterparty_followup, integrity_guard | question: А какое получилось нетто?
- `address_truth_harness_phase67_svk_grounded_counterparty_integrity:step_05_documents_same_counterparty` | tags: document_pivot_after_value_flow, grounded_counterparty_followup, integrity_guard | question: А по документам?
- `address_truth_harness_phase67_svk_grounded_counterparty_integrity:step_06_movements_same_counterparty` | tags: movement_pivot_after_value_flow, grounded_counterparty_followup, integrity_guard | question: А по движениям?
- `address_truth_harness_phase67_svk_grounded_counterparty_integrity:step_07_year_switch_same_counterparty` | tags: year_switch, grounded_counterparty_followup, integrity_guard | question: А теперь за 2021 год?
- `address_truth_harness_phase68_referential_document_followup_integrity:step_01_documents_by_counterparty` | tags: documents_by_counterparty, referential_followup_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase68_referential_document_followup_integrity:step_02_referential_document_followup` | tags: referential_document_followup, counterparty_carryover, integrity_guard | question: Кроме этого документа есть еще что-то?
- `address_truth_harness_phase69_document_to_payments_pronoun_pivot:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pronoun_pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase69_document_to_payments_pronoun_pivot:step_02_payments_by_pronoun_followup` | tags: payments_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase6_provider_axis_mix:step_01_smalltalk` | tags: meta_smalltalk | question: привет, как дела?
- `address_truth_harness_phase6_provider_axis_mix:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase6_provider_axis_mix:step_03_capability_meta` | tags: meta_capability | question: что ты можешь по 1С?
- `address_truth_harness_phase6_provider_axis_mix:step_04_inventory_root_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase6_provider_axis_mix:step_05_historical_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `address_truth_harness_phase70_document_to_contracts_pronoun_pivot:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pronoun_pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase70_document_to_contracts_pronoun_pivot:step_02_contracts_by_pronoun_followup` | tags: contracts_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase71_document_to_payments_year_switch:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pronoun_pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase71_document_to_payments_year_switch:step_02_payments_by_pronoun_followup` | tags: payments_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase71_document_to_payments_year_switch:step_03_year_switch_after_payments_pivot` | tags: year_switch_after_pivot, payments_followup, integrity_guard | question: А за 2021?
- `address_truth_harness_phase72_document_to_contracts_year_switch:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pronoun_pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase72_document_to_contracts_year_switch:step_02_contracts_by_pronoun_followup` | tags: contracts_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase72_document_to_contracts_year_switch:step_03_year_switch_after_contracts_pivot` | tags: year_switch_after_pivot, contracts_followup, integrity_guard | question: А за 2021?
- `address_truth_harness_phase73_document_to_contracts_all_time:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pronoun_pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase73_document_to_contracts_all_time:step_02_contracts_by_pronoun_followup` | tags: contracts_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase73_document_to_contracts_all_time:step_03_all_time_after_contracts_pivot` | tags: all_time_after_pivot, contracts_followup, integrity_guard | question: А за все время?
- `address_truth_harness_phase74_document_to_payments_all_time:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pronoun_pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase74_document_to_payments_all_time:step_02_payments_by_pronoun_followup` | tags: payments_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase74_document_to_payments_all_time:step_03_all_time_after_payments_pivot` | tags: all_time_after_pivot, payments_followup, integrity_guard | question: А за все время?
- `address_truth_harness_phase75_contracts_to_payments_year_switch:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase75_contracts_to_payments_year_switch:step_02_contracts_by_pronoun_followup` | tags: contracts_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase75_contracts_to_payments_year_switch:step_03_payments_after_contracts_pivot` | tags: payments_followup, second_pivot, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase75_contracts_to_payments_year_switch:step_04_year_switch_after_second_pivot` | tags: year_switch_after_second_pivot, payments_followup, integrity_guard | question: А за 2021?
- `address_truth_harness_phase76_contracts_to_payments_all_time:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase76_contracts_to_payments_all_time:step_02_contracts_by_pronoun_followup` | tags: contracts_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase76_contracts_to_payments_all_time:step_03_payments_after_contracts_pivot` | tags: payments_followup, second_pivot, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase76_contracts_to_payments_all_time:step_04_all_time_after_second_pivot` | tags: all_time_after_second_pivot, payments_followup, integrity_guard | question: А за все время?
- `address_truth_harness_phase77_payments_to_contracts_year_switch:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase77_payments_to_contracts_year_switch:step_02_payments_by_pronoun_followup` | tags: payments_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase77_payments_to_contracts_year_switch:step_03_contracts_after_payments_pivot` | tags: contracts_followup, second_pivot, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase77_payments_to_contracts_year_switch:step_04_year_switch_after_second_pivot` | tags: year_switch_after_second_pivot, contracts_followup, integrity_guard | question: А за 2021?
- `address_truth_harness_phase78_payments_to_contracts_all_time:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase78_payments_to_contracts_all_time:step_02_payments_by_pronoun_followup` | tags: payments_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase78_payments_to_contracts_all_time:step_03_contracts_after_payments_pivot` | tags: contracts_followup, second_pivot, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase78_payments_to_contracts_all_time:step_04_all_time_after_second_pivot` | tags: all_time_after_second_pivot, contracts_followup, integrity_guard | question: А за все время?
- `address_truth_harness_phase79_payments_to_contracts_to_documents_year_switch:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase79_payments_to_contracts_to_documents_year_switch:step_02_payments_by_pronoun_followup` | tags: payments_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase79_payments_to_contracts_to_documents_year_switch:step_03_contracts_after_payments_pivot` | tags: contracts_followup, second_pivot, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase79_payments_to_contracts_to_documents_year_switch:step_04_documents_after_contracts_pivot` | tags: documents_followup, third_pivot, integrity_guard | question: А по нему документы?
- `address_truth_harness_phase79_payments_to_contracts_to_documents_year_switch:step_05_year_switch_after_third_pivot` | tags: year_switch_after_third_pivot, documents_followup, integrity_guard | question: А за 2021?
- `address_truth_harness_phase7_acceptance_gate_mix:step_01_inventory_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
- `address_truth_harness_phase7_acceptance_gate_mix:step_02_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase7_acceptance_gate_mix:step_03_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
@ -50,7 +685,6 @@
- `address_truth_harness_phase7_acceptance_gate_mix:step_05_data_scope_meta_interrupt` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase7_acceptance_gate_mix:step_06_historical_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `address_truth_harness_phase7_meta_domain_mix:step_01_smalltalk` | tags: meta_smalltalk | question: привет, как дела?
- `address_truth_harness_phase7_meta_domain_mix:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase7_meta_domain_mix:step_01_counterparty_documents` | tags: counterparty_documents | question: покажи все документы по чепурнову
- `address_truth_harness_phase7_meta_domain_mix:step_02_counterparty_shipments_or_fallback` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
- `address_truth_harness_phase7_meta_domain_mix:step_01_inventory_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
@ -63,6 +697,111 @@
- `address_truth_harness_phase7_meta_domain_mix:step_03_inventory_same_date` | tags: inventory_root, same_date_pivot | question: остатки по складу на эту же дату
- `address_truth_harness_phase7_meta_domain_mix:step_06_historical_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `address_truth_harness_phase7_meta_domain_mix:step_04_open_items_account_60` | tags: settlements_account_60 | question: хвосты покажи по счету 60 на август 2022
- `address_truth_harness_phase7_meta_domain_mix:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase80_payments_to_contracts_to_documents_all_time:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase80_payments_to_contracts_to_documents_all_time:step_02_payments_by_pronoun_followup` | tags: payments_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase80_payments_to_contracts_to_documents_all_time:step_03_contracts_after_payments_pivot` | tags: contracts_followup, second_pivot, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase80_payments_to_contracts_to_documents_all_time:step_04_documents_after_contracts_pivot` | tags: documents_followup, third_pivot, integrity_guard | question: А по нему документы?
- `address_truth_harness_phase80_payments_to_contracts_to_documents_all_time:step_05_all_time_after_third_pivot` | tags: all_time_after_third_pivot, documents_followup, integrity_guard | question: А за все время?
- `address_truth_harness_phase81_payments_contracts_documents_back_to_payments_year_switch:step_01_documents_by_counterparty` | tags: documents_by_counterparty, pivot_seed, integrity_guard, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase81_payments_contracts_documents_back_to_payments_year_switch:step_02_payments_by_pronoun_followup` | tags: payments_followup, counterparty_pronoun_resolution, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase81_payments_contracts_documents_back_to_payments_year_switch:step_03_contracts_after_payments_pivot` | tags: contracts_followup, second_pivot, integrity_guard | question: А по нему договоры?
- `address_truth_harness_phase81_payments_contracts_documents_back_to_payments_year_switch:step_04_documents_after_contracts_pivot` | tags: documents_followup, third_pivot, integrity_guard | question: А по нему документы?
- `address_truth_harness_phase81_payments_contracts_documents_back_to_payments_year_switch:step_05_payments_after_third_pivot` | tags: payments_followup, fourth_pivot, integrity_guard | question: А по нему платежи?
- `address_truth_harness_phase81_payments_contracts_documents_back_to_payments_year_switch:step_06_year_switch_after_fourth_pivot` | tags: year_switch_after_fourth_pivot, payments_followup, integrity_guard | question: А за 2021?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_01_documents_by_counterparty` | tags: post_f_integrity_hardening, documents_by_counterparty, pivot_seed, human_dialog, counterparty_documents | question: Покажи документы по Жуковке 51.
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_02_payments_by_pronoun_followup` | tags: post_f_integrity_hardening, payments_followup, counterparty_pronoun_resolution, human_dialog | question: Хорошо, а теперь платежи по нему тоже покажи.
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_03_contracts_after_payments_pivot` | tags: post_f_integrity_hardening, contracts_followup, second_pivot, human_dialog | question: А по нему договоры?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_04_documents_after_contracts_pivot` | tags: post_f_integrity_hardening, documents_followup, third_pivot, human_dialog | question: А по нему документы?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_05_payments_after_third_pivot` | tags: post_f_integrity_hardening, payments_followup, fourth_pivot, human_dialog | question: А по нему платежи?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_06_year_switch_after_fourth_pivot` | tags: post_f_integrity_hardening, year_switch_after_fourth_pivot, payments_followup, human_dialog | question: А за 2021?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_07_open_scope_incoming_total` | tags: organization_scope, open_scope_total, topic_reset, human_dialog | question: С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_08_open_scope_org_clarification` | tags: organization_scope, open_scope_total, organization_clarification, human_dialog | question: По ООО Альтернатива Плюс.
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_09_open_scope_all_time_followup` | tags: organization_scope, all_time_followup, human_dialog | question: Понял, тогда за все время.
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_10_bidirectional_comparison` | tags: organization_scope, value_flow_comparison, human_dialog | question: Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_11_comparison_year_switch` | tags: organization_scope, value_flow_comparison, year_switch, human_dialog | question: А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_12_ranking_top_counterparty` | tags: organization_scope, value_flow_ranking, human_dialog | question: И кто больше всего принес денег этой организации в 2020 году?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_13_ranking_year_switch` | tags: organization_scope, value_flow_ranking, year_switch, human_dialog | question: А в 2021 году?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_14_ground_named_counterparty` | tags: grounded_counterparty, topic_reset, human_dialog | question: Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_15_counterparty_incoming_2020` | tags: grounded_counterparty, incoming_value_flow, human_dialog | question: Сколько получили по нему за 2020 год?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_16_counterparty_payout_followup` | tags: grounded_counterparty, payout_value_flow, human_dialog | question: А теперь сколько заплатили?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_17_counterparty_net_followup` | tags: grounded_counterparty, net_value_flow, human_dialog | question: А какое нетто?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_18_counterparty_documents_pivot` | tags: grounded_counterparty, documents_pivot, human_dialog, counterparty_documents | question: А по документам?
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog:step_19_counterparty_movements_pivot` | tags: grounded_counterparty, movements_pivot, human_dialog | question: А по движениям?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_01_human_smalltalk_sanity` | tags: human_answer, mcp_discovery_gate_sanity, meta_smalltalk | question: привет, ты на связи?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_01_resolve_counterparty_alias` | tags: entity_resolution, alias_grounding, followup_anchor, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=entity_resolution, selected_matches_top=True | question: найди в 1С контрагента СВК
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_02_incoming_by_resolved_entity` | tags: entity_resolution, incoming_value_flow, followup_reuse, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: сколько получили по нему за 2020 год
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_03_payout_switch_by_resolved_entity` | tags: entity_resolution, payout_switch, followup_reuse, date_carryover, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: а теперь сколько заплатили?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_04_net_after_payout` | tags: entity_resolution, net_value_flow, followup_reuse, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: а какое нетто?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_05_documents_after_net` | tags: entity_resolution, document_evidence, value_flow_pivot, followup_reuse, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=document_evidence, selected_matches_top=True | question: а по документам?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_06_movements_after_documents` | tags: entity_resolution, movement_evidence, document_pivot, followup_reuse, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=movement_evidence, selected_matches_top=True | question: а по движениям?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_01_open_scope_incoming_total` | tags: open_scope_total, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_02_all_time_same_open_scope` | tags: organization_clarification, open_scope_total, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: По ООО Альтернатива Плюс.
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_03_all_time_same_open_scope` | tags: all_time_followup, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow, selected_matches_top=True | question: Понял, тогда за все время.
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_04_bidirectional_comparison` | tags: value_flow_comparison, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_05_comparison_year_switch` | tags: value_flow_comparison, year_switch, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_comparison, selected_matches_top=True | question: А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_06_ranking_top_counterparty` | tags: value_flow_ranking, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_ranking, selected_matches_top=True | question: И кто больше всего принес денег этой организации в 2020 году?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_07_ranking_year_switch` | tags: value_flow_ranking, year_switch, organization_scope, human_dialog, planner_catalog_alignment | catalog_alignment: status=selected_matches_top, top=value_flow_ranking, selected_matches_top=True | question: А в 2021 году?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_01_company_activity_lifecycle` | tags: company_activity_lifecycle, grounded_context_seed | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_02_broad_business_evaluation` | tags: broad_business_evaluation, grounded_summary | question: Как ты оценишь деятельность компании?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_03_net_flow_after_broad_eval` | tags: counterparty_net_cash_flow, broad_eval_bridge_preserved | question: какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_01_catalog_metadata_surface` | tags: catalog_metadata_surface, counterparty_catalog_scope | question: какие справочники 1С есть по контрагентам?
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_02_neutral_followup_catalog_drilldown` | tags: catalog_drilldown, neutral_followup | question: давай дальше
- `address_truth_harness_phase83_planner_brain_alignment_mix:step_08_off_domain_living_chat_not_hijacked` | tags: off_domain_living_chat, stale_replay_forbidden | question: а чем капибара отличается от утки?
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_01_smalltalk` | tags: meta_smalltalk | question: привет, как дела?
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_03_counterparty_documents` | tags: counterparty_documents | question: покажи все документы по чепурнову
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_04_counterparty_shipments` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_05_inventory_root_after_counterparty` | tags: inventory_root, company_authority_probe | question: какие остатки на складе на март 2021
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_06_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_07_capability_meta` | tags: meta_capability | question: что ты умеешь?
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_08_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_09_memory_recap` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_10_receivables_march_2020` | tags: settlements_receivables | question: кто нам должен на март 2020
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_11_inventory_same_date_after_receivables` | tags: inventory_root, same_date_pivot | question: остатки по складу на эту же дату
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_12_historical_inventory_capability` | tags: meta_historical_capability, inventory_root | question: а исторические остатки ты можешь дать?
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_13_open_items_account_60` | tags: settlements_account_60 | question: хвосты покажи по счету 60 на август 2022
- `address_truth_harness_phase8_manual_runtime_authority_mix:step_14_company_activity_age` | tags: organization_activity_age, company_selected | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `address_truth_harness_phase9_proactive_scope_offer:step_01_smalltalk_with_scope_offer` | tags: meta_smalltalk, proactive_scope_offer | question: привет, как дела?
- `address_truth_harness_phase9_proactive_scope_offer:step_02_choose_organization` | tags: company_selected, living_scope_selection | question: Альтернатива Плюс
- `address_truth_harness_phase9_proactive_scope_offer:step_03_company_activity_age` | tags: organization_activity_age, company_selected | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `address_truth_harness_phase9_proactive_scope_offer:step_04_receivables_root` | tags: settlements_receivables, company_selected | question: кто нам должен на март 2020?
- `address_truth_harness_phase9_proactive_scope_offer:step_05_inventory_same_date` | tags: inventory_root, same_date_pivot, company_authority | question: остатки по складу на эту же дату
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_01_vat_metadata_orientation` | tags: legacy_phase64, metadata_surface, vat_orientation, cross_stage_canary, vat | question: Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_02_vat_movements_org_needs_period` | tags: legacy_phase64, movement_lane_after_metadata, organization_scope, vat | question: Хорошо, тогда покажи движения по ООО Альтернатива Плюс.
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_03_vat_movements_2020` | tags: legacy_phase64, movement_execution, period_clarification_resume, vat | question: За 2020 год.
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_04_vat_documents_pivot` | tags: legacy_phase64, document_pivot_after_movement, scope_reuse, vat | question: А теперь по документам?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_05_vat_documents_year_switch` | tags: legacy_phase64, year_switch_after_document_pivot, vat | question: А теперь за 2021 год?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_06_vat_documents_all_time` | tags: legacy_phase64, all_time_followup, document_lane_continuity, vat | question: А теперь за все время?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_07_reset_to_numeric_counterparty_documents` | tags: post_f, numeric_counterparty_suffix, account_injection_guard, topic_reset, counterparty_documents, vat | question: С НДС закончили. Новая тема: покажи документы по Жуковке 51.
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_08_numeric_counterparty_payments` | tags: post_f, counterparty_pronoun_resolution, payments_followup | question: Хорошо, а теперь платежи по нему тоже покажи.
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_09_numeric_counterparty_contracts` | tags: post_f, contracts_followup, repeated_pivot | question: А по нему договоры?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_10_numeric_counterparty_documents_again` | tags: post_f, documents_followup, repeated_pivot, counterparty_documents | question: А по нему документы?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_11_numeric_counterparty_year_switch` | tags: post_f, year_switch_after_repeated_pivot | question: А за 2021?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_12_open_org_money_requires_clarification` | tags: post_f, open_scope_total, organization_clarification, topic_reset | question: С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_13_open_org_clarification_answer` | tags: post_f, organization_scope, clarification_resume | question: По ООО Альтернатива Плюс.
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_14_open_org_all_time` | tags: post_f, organization_scope, all_time_followup | question: Понял, тогда за все время.
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_15_open_org_bidirectional_2020` | tags: post_f, bidirectional_value_flow, organization_scope | question: Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_16_open_org_ranking_2020` | tags: legacy_phase39, value_flow_ranking, organization_scope | question: И кто больше всего принес денег этой организации в 2020 году?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_17_open_org_ranking_year_switch` | tags: legacy_phase39, year_switch, organization_scope | question: А в 2021 году?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_18_ground_svk_counterparty` | tags: legacy_phase67, grounded_counterparty, topic_reset | question: Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_19_svk_incoming_2020` | tags: legacy_phase67, grounded_counterparty, incoming_value_flow | question: Сколько получили по нему за 2020 год?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_20_svk_payout` | tags: legacy_phase67, grounded_counterparty, payout_value_flow | question: А теперь сколько заплатили?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_21_svk_net` | tags: legacy_phase67, grounded_counterparty, net_value_flow | question: А какое нетто?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_22_svk_documents_pivot` | tags: legacy_phase67, grounded_counterparty, documents_pivot | question: А по документам?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_23_svk_movements_pivot` | tags: legacy_phase67, grounded_counterparty, movements_pivot | question: А по движениям?
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424:step_24_svk_year_switch` | tags: legacy_phase67, grounded_counterparty, year_switch | question: А теперь тот же смысл за 2021 год.
- `address_truth_harness_post_f_manual_failures_20260424:step_01_inventory_root` | tags: manual_9lieoh, inventory_context, inventory_root | question: кайф - что там на складе по остаткам?
- `address_truth_harness_post_f_manual_failures_20260424:step_02_choose_org` | tags: manual_9lieoh, organization_scope | question: АЛЬТЕРНАТИВА
- `address_truth_harness_post_f_manual_failures_20260424:step_03_inventory_march_2016` | tags: manual_9lieoh, purchase_date_context | question: март 2016
- `address_truth_harness_post_f_manual_failures_20260424:step_04_workstation_purchase` | tags: manual_9lieoh, selected_object_purchase, selected_object | question: По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где взяли это?
- `address_truth_harness_post_f_manual_failures_20260424:step_05_vat_purchase_date` | tags: manual_9lieoh, vat_failure_11, materialization_gap, vat | question: ндс можешь прикинуть на дату покупки рабочей станции?
- `address_truth_harness_post_f_manual_failures_20260424:step_06_vat_feb_2017` | tags: manual_9lieoh, vat_failure_13, vat | question: прикинь какой ндс нам надо заплатить на февраль 2017
- `address_truth_harness_post_f_manual_failures_20260424:step_07_highest_value_customer` | tags: manual_9lieoh, revenue_ranking_failure_14 | question: кто у нас самый доходный клиент за все время
- `address_truth_harness_post_f_manual_failures_20260424:step_08_documents_chepurnov` | tags: manual_9lieoh, counterparty_grounding, counterparty_documents | question: по чепурнову покажи все доки
- `address_truth_harness_post_f_manual_failures_20260424:step_09_pivot_svk` | tags: manual_9lieoh, counterparty_retarget | question: а по свк
- `address_truth_harness_post_f_manual_failures_20260424:step_10_inventory_today` | tags: manual_9lieoh, stale_inventory_scope, inventory_root | question: а сейчас у нас есть что на складе?
- `address_truth_harness_post_f_manual_failures_20260424:step_11_chepurnov_item_flow` | tags: manual_9lieoh, item_flow_failure_26, stale_scope_guard, counterparty_shipment_fallback | question: что нам отгружал чепурнов? какой товар или услугу?
- `address_truth_harness_targeted_counterparty_tails:step_01_documents_by_counterparty` | tags: counterparty_documents | question: покажи все документы по чапурнову
- `address_truth_harness_targeted_counterparty_tails:step_02_counterparty_item_flow` | tags: counterparty_shipment_fallback | question: что нам отгружал чапурнов? какой товар или услугу?
- `address_truth_harness_targeted_counterparty_tails:step_03_inventory_reset` | tags: inventory_root | question: какие остатки на складе на сегодня?
@ -83,7 +822,6 @@
- `address_truth_harness_test2:step_14_receivables_today` | tags: settlements_receivables | question: а на сегодня
## Saved session questions
- `assistant_saved_session_20260416175150_gen-mo1s0m9z-ndf56a3:q01` | tags: meta_smalltalk | question: привет как дела
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q01` | tags: meta_smalltalk | question: приветик - че как там дела
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q02` | tags: meta_capability | question: расскажи что можешь интересного
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q03` | tags: inventory_root | question: кайф - что там на складе по остаткам?
@ -164,3 +902,152 @@
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q12` | tags: inventory_root | question: остатки по складу на эту же дату
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q13` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q14` | tags: none | question: хвосты покажи по счету 60 на август 2022
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q01` | tags: meta_smalltalk | question: привет, как дела?
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q02` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q03` | tags: counterparty_documents | question: покажи все документы по чепурнову
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q04` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q05` | tags: inventory_root | question: какие остатки на складе на март 2021
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q06` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q07` | tags: meta_capability | question: что ты умеешь?
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q08` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q09` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q10` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q11` | tags: settlements_receivables | question: кто нам должен на март 2020
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q12` | tags: inventory_root | question: остатки по складу на эту же дату
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q13` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
- `assistant_saved_session_20260417094132_gen-ag04170941-87680e:q14` | tags: none | question: хвосты покажи по счету 60 на август 2022
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q01` | tags: meta_smalltalk | question: привет, как дела?
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q02` | tags: meta_scope | question: по какой компании мы сейчас работаем?
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q03` | tags: inventory_root | question: какие остатки на складе на март 2021
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q04` | tags: none | question: давай по Альтернативе Плюс
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q05` | tags: inventory_root | question: тогда покажи остатки на март 2021
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q06` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q07` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q08` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q09` | tags: none | question: а по Альтернативе Плюс сколько лет активности в базе 1С?
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q10` | tags: meta_capability | question: что ты умеешь?
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q11` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q12` | tags: settlements_receivables | question: кто нам должен на март 2020
- `assistant_saved_session_20260417132600_gen-ag04171326-15a132:q13` | tags: inventory_root | question: остатки по складу на эту же дату
- `assistant_saved_session_20260417150806_gen-ag04171508-760111:q01` | tags: inventory_root | question: какие остатки на складе на март 2021
- `assistant_saved_session_20260417150806_gen-ag04171508-760111:q02` | tags: none | question: давай по Альтернативе Плюс
- `assistant_saved_session_20260417150806_gen-ag04171508-760111:q03` | tags: inventory_root | question: тогда покажи остатки на март 2021
- `assistant_saved_session_20260417150806_gen-ag04171508-760111:q04` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
- `assistant_saved_session_20260417150806_gen-ag04171508-760111:q05` | tags: none | question: а по этой позиции когда была закупка?
- `assistant_saved_session_20260417150806_gen-ag04171508-760111:q06` | tags: none | question: покажи документы по этой позиции
- `assistant_saved_session_20260417150806_gen-ag04171508-760111:q07` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
- `assistant_saved_session_20260417150806_gen-ag04171508-760111:q08` | tags: meta_memory | question: а что мы уже выяснили по этой позиции?
- `assistant_saved_session_20260419164457_gen-mo5zy5vo-z9klj34:q01` | tags: meta_smalltalk | question: привет как дела
- `assistant_saved_session_20260419164457_gen-mo5zy5vo-z9klj34:q02` | tags: none | question: кто намс должен денег на сегодня
- `assistant_saved_session_20260419164457_gen-mo5zy5vo-z9klj34:q03` | tags: none | question: что ты можешь
- `assistant_saved_session_20260419164457_gen-mo5zy5vo-z9klj34:q04` | tags: settlements_receivables | question: кто нам должен денег на сегодня
- `assistant_saved_session_20260419164457_gen-mo5zy5vo-z9klj34:q05` | tags: none | question: а мы кому
- `assistant_saved_session_20260419164457_gen-mo5zy5vo-z9klj34:q06` | tags: inventory_root | question: какиек остатки на складе на сегодня
- `assistant_saved_session_20260419164457_gen-mo5zy5vo-z9klj34:q07` | tags: none | question: альтернатива
- `assistant_saved_session_20260419164457_gen-mo5zy5vo-z9klj34:q08` | tags: none | question: покажи документы по чепурнову
- `assistant_saved_session_20260419164457_gen-mo5zy5vo-z9klj34:q09` | tags: none | question: какой оборот был свк
- `assistant_saved_session_20260422125154_gen-moa1y0lw-m30gdsz:q01` | tags: none | question: найди в 1С контрагента СВК
- `assistant_saved_session_20260422125154_gen-moa1y0lw-m30gdsz:q02` | tags: none | question: сколько получили по нему за 2020 год
- `assistant_saved_session_20260422125154_gen-moa1y0lw-m30gdsz:q03` | tags: none | question: а теперь сколько заплатили?
- `assistant_saved_session_20260422125154_gen-moa1y0lw-m30gdsz:q04` | tags: none | question: а за 2021?
- `assistant_saved_session_20260422125154_gen-moa1y0lw-m30gdsz:q05` | tags: none | question: а какое нетто?
- `assistant_saved_session_20260422125154_gen-moa1y0lw-m30gdsz:q06` | tags: none | question: а по документам?
- `assistant_saved_session_20260422125154_gen-moa1y0lw-m30gdsz:q07` | tags: none | question: а по движениям?
- `assistant_saved_session_20260423133622_gen-ag04231336-3d4cc9:q01` | tags: none | question: Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?
- `assistant_saved_session_20260423133622_gen-ag04231336-3d4cc9:q02` | tags: none | question: По ООО Альтернатива Плюс.
- `assistant_saved_session_20260423133622_gen-ag04231336-3d4cc9:q03` | tags: none | question: Понял, тогда за все время.
- `assistant_saved_session_20260423133622_gen-ag04231336-3d4cc9:q04` | tags: none | question: Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?
- `assistant_saved_session_20260423133622_gen-ag04231336-3d4cc9:q05` | tags: none | question: А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?
- `assistant_saved_session_20260423133622_gen-ag04231336-3d4cc9:q06` | tags: none | question: И кто больше всего принес денег этой организации в 2020 году?
- `assistant_saved_session_20260423133622_gen-ag04231336-3d4cc9:q07` | tags: none | question: А в 2021 году?
- `assistant_saved_session_20260423133622_gen-ag04231336-4fa660:q01` | tags: vat | question: Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?
- `assistant_saved_session_20260423133622_gen-ag04231336-4fa660:q02` | tags: vat | question: Хорошо, тогда покажи движения по ООО Альтернатива Плюс.
- `assistant_saved_session_20260423133622_gen-ag04231336-4fa660:q03` | tags: vat | question: За 2020 год.
- `assistant_saved_session_20260423133622_gen-ag04231336-4fa660:q04` | tags: vat | question: А теперь по документам?
- `assistant_saved_session_20260423133622_gen-ag04231336-4fa660:q05` | tags: vat | question: А теперь за 2021 год?
- `assistant_saved_session_20260423133622_gen-ag04231336-4fa660:q06` | tags: vat | question: А теперь за все время?
- `assistant_saved_session_20260423133622_gen-ag04231336-db78b3:q01` | tags: none | question: Хочу проверить одного контрагента. Найди в 1С Группу СВК.
- `assistant_saved_session_20260423133622_gen-ag04231336-db78b3:q02` | tags: none | question: Посмотри, сколько денег мы получили от него за 2020 год.
- `assistant_saved_session_20260423133622_gen-ag04231336-db78b3:q03` | tags: none | question: А теперь сколько мы ему заплатили?
- `assistant_saved_session_20260423133622_gen-ag04231336-db78b3:q04` | tags: none | question: А какое получилось нетто?
- `assistant_saved_session_20260423133622_gen-ag04231336-db78b3:q05` | tags: none | question: А по документам?
- `assistant_saved_session_20260423133622_gen-ag04231336-db78b3:q06` | tags: none | question: А по движениям?
- `assistant_saved_session_20260423133622_gen-ag04231336-db78b3:q07` | tags: none | question: А теперь за 2021 год?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q01` | tags: none | question: Покажи документы по Жуковке 51.
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q02` | tags: none | question: Хорошо, а теперь платежи по нему тоже покажи.
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q03` | tags: none | question: А по нему договоры?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q04` | tags: none | question: А по нему документы?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q05` | tags: none | question: А по нему платежи?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q06` | tags: none | question: А за 2021?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q07` | tags: none | question: С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q08` | tags: none | question: По ООО Альтернатива Плюс.
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q09` | tags: none | question: Понял, тогда за все время.
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q10` | tags: none | question: Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q11` | tags: none | question: А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q12` | tags: none | question: И кто больше всего принес денег этой организации в 2020 году?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q13` | tags: none | question: А в 2021 году?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q14` | tags: none | question: Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q15` | tags: none | question: Сколько получили по нему за 2020 год?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q16` | tags: none | question: А теперь сколько заплатили?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q17` | tags: none | question: А какое нетто?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q18` | tags: none | question: А по документам?
- `assistant_saved_session_20260423184425_gen-ag04231844-8e552a:q19` | tags: none | question: А по движениям?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q01` | tags: vat | question: Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q02` | tags: none | question: Хорошо, тогда покажи движения по ООО Альтернатива Плюс.
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q03` | tags: none | question: За 2020 год.
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q04` | tags: none | question: А теперь по документам?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q05` | tags: none | question: А теперь за 2021 год?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q06` | tags: none | question: А теперь за все время?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q07` | tags: vat | question: С НДС закончили. Новая тема: покажи документы по Жуковке 51.
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q08` | tags: none | question: Хорошо, а теперь платежи по нему тоже покажи.
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q09` | tags: none | question: А по нему договоры?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q10` | tags: none | question: А по нему документы?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q11` | tags: none | question: А за 2021?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q12` | tags: none | question: С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q13` | tags: none | question: По ООО Альтернатива Плюс.
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q14` | tags: none | question: Понял, тогда за все время.
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q15` | tags: none | question: Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q16` | tags: none | question: И кто больше всего принес денег этой организации в 2020 году?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q17` | tags: none | question: А в 2021 году?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q18` | tags: none | question: Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q19` | tags: none | question: Сколько получили по нему за 2020 год?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q20` | tags: none | question: А теперь сколько заплатили?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q21` | tags: none | question: А какое нетто?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q22` | tags: none | question: А по документам?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q23` | tags: none | question: А по движениям?
- `assistant_saved_session_20260424140630_gen-ag04241406-abe4d8:q24` | tags: none | question: А теперь тот же смысл за 2021 год.
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q01` | tags: vat | question: Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q02` | tags: none | question: Хорошо, тогда покажи движения по ООО Альтернатива Плюс.
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q03` | tags: none | question: За 2020 год.
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q04` | tags: none | question: А теперь по документам?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q05` | tags: none | question: А теперь за 2021 год?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q06` | tags: none | question: А теперь за все время?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q07` | tags: vat | question: С НДС закончили. Новая тема: покажи документы по Жуковке 51.
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q08` | tags: none | question: Хорошо, а теперь платежи по нему тоже покажи.
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q09` | tags: none | question: А по нему договоры?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q10` | tags: none | question: А по нему документы?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q11` | tags: none | question: А за 2021?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q12` | tags: none | question: С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q13` | tags: none | question: По ООО Альтернатива Плюс.
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q14` | tags: none | question: Понял, тогда за все время.
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q15` | tags: none | question: Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q16` | tags: none | question: И кто больше всего принес денег этой организации в 2020 году?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q17` | tags: none | question: А в 2021 году?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q18` | tags: none | question: Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q19` | tags: none | question: Сколько получили по нему за 2020 год?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q20` | tags: none | question: А теперь сколько заплатили?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q21` | tags: none | question: А какое нетто?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q22` | tags: none | question: А по документам?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q23` | tags: none | question: А по движениям?
- `assistant_saved_session_20260424161022_gen-ag04241610-84c8bb:q24` | tags: none | question: А теперь тот же смысл за 2021 год.
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q01` | tags: inventory_root | question: кайф - что там на складе по остаткам?
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q02` | tags: none | question: АЛЬТЕРНАТИВА
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q03` | tags: none | question: март 2016
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q04` | tags: selected_object | question: По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где взяли это?
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q05` | tags: vat | question: ндс можешь прикинуть на дату покупки рабочей станции?
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q06` | tags: vat | question: прикинь какой ндс нам надо заплатить на февраль 2017
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q07` | tags: none | question: кто у нас самый доходный клиент за все время
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q08` | tags: counterparty_documents | question: по чепурнову покажи все доки
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q09` | tags: none | question: а по свк
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q10` | tags: inventory_root | question: а сейчас у нас есть что на складе?
- `assistant_saved_session_20260424171031_gen-ag04241710-bdb248:q11` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов? какой товар или услугу?

@ -1 +1 @@
Subproject commit 1d2015214735d2c3d253ba184e0a45efd9ce6c5f
Subproject commit b47c3d124ae17bc4b481d1e2e88c89a7c1418b63

View File

@ -12,6 +12,7 @@ const COMPUTE_EXACT_INTENTS = new Set([
"inventory_purchase_documents_for_item",
"inventory_supplier_stock_overlap_as_of_date",
"inventory_sale_trace_for_item",
"inventory_profitability_for_item",
"inventory_purchase_to_sale_chain",
"inventory_aging_by_purchase_date",
"customer_revenue_and_payments",
@ -67,6 +68,7 @@ function defaultCapabilityId(intent) {
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date") {
return `inventory_${intent}`;
@ -149,7 +151,14 @@ function resolveCapabilityEnabled(intent) {
if (intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain") {
if (intent === "inventory_profitability_for_item") {
return {
enabled: true,
reason: "inventory_profitability_route_enabled"
};
}
if (intent === "inventory_purchase_to_sale_chain") {
return {
enabled: true,
@ -240,6 +249,7 @@ function resolveShadowRouteIntent(intent, requestedResultMode) {
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date") {
return null;

View File

@ -101,6 +101,7 @@ function isConfirmedBalanceIntent(intent) {
intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "open_contracts_confirmed_as_of_date" ||
intent === "payables_confirmed_as_of_date" ||
@ -109,6 +110,10 @@ function isConfirmedBalanceIntent(intent) {
intent === "vat_liability_confirmed_for_tax_period");
}
function resolveAddressAsOfDateBasis(filters, semanticFrame) {
if (semanticFrame?.date_scope_kind === "implicit_current" &&
semanticFrame.date_basis_hint === "implicit_current_snapshot") {
return "implicit_current_snapshot";
}
const asOfDate = normalizeIsoDateHint(filters.as_of_date);
if (asOfDate) {
return "explicit_as_of_date";

View File

@ -11,6 +11,7 @@ const iconv_lite_1 = __importDefault(require("iconv-lite"));
const ACCOUNT_PATTERN = /(?:сч[её]т|счет|account)[^0-9]{0,12}(\d{2}(?:[.,]\d{1,2})?)/i;
const ACCOUNT_REVERSE_PATTERN = /(?:^|[\s,.;:!?()\-])(\d{2}(?:[.,]\d{1,2})?)(?=\s*(?:сч[её]т|счет|account|acct))/iu;
const LIMIT_PATTERN = /(?:\btop\b|\blimit\b|первые|топ)[\s\-—_:№#]*?(\d{1,3})/iu;
const VALUE_ANALYTICS_SAMPLE_LIMIT = 1000;
const COUNTERPARTY_PATTERN = /(?:по\s+контрагенту|контрагент(?:у|а)?|по\s+контре|контра|по\s+компан(?:ии|ию|ия)|компан(?:ия|ии|ию)|по\s+организац(?:ии|ию|ия)|организац(?:ия|ии|ию)|по\s+поставщик(?:у|а)?|поставщик(?:у|а)?|по\s+клиент(?:у|а)?|клиент(?:у|а)?|по\s+покупател(?:ю|я)|покупател(?:ю|я)|по\s+партнер(?:у|а)?|партнер(?:у|а)?|by\s+counterparty|counterparty|by\s+company|company|by\s+supplier|supplier|by\s+vendor|vendor|by\s+customer|customer|by\s+client|client|by\s+partner|partner)\s+([^\r\n,.;:]+)/iu;
const CONTRACT_PATTERN = /(?:по\s+(?:договору|контракту)|(?:договор|контракт)(?:у|а)?\s*(?:№|#|n)?|by\s+contract|contract(?:\s*(?:no|number|#|n))?)\s+([^\r\n,.;:]+)/i;
const DATE_DMY_PATTERN = /\b(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{2,4})\b/;
@ -166,8 +167,11 @@ function toIsoDate(year, month, day) {
}
return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}
function hasImplicitCurrentAsOfDateCue(text) {
return /\b(сегодня|на\s+сегодня|на\s+текущ(?:ую|ая|ий|ее|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодняшн(?:юю|ий|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+текущ(?:ий|ую)\s+момент|today|as\s+of\s+today|current\s+date|as\s+of\s+current\s+date)\b/i.test(text);
}
function extractAsOfDate(text) {
if (/\b(сегодня|на\s+сегодня|на\s+текущ(?:ую|ая|ий|ее|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодняшн(?:юю|ий|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+текущ(?:ий|ую)\s+момент|today|as\s+of\s+today|current\s+date|as\s+of\s+current\s+date)\b/i.test(text)) {
if (hasImplicitCurrentAsOfDateCue(text)) {
return new Date().toISOString().slice(0, 10);
}
const ymd = text.match(DATE_YMD_PATTERN);
@ -657,6 +661,8 @@ function isLowQualityCounterpartyAnchorValue(rawValue) {
"ноябрь",
"декабрь"
]);
const isLowQualityTimeToken = (token) => lowQualityTimeTokens.has(token) ||
/^(?:январ|феврал|март|апрел|ма(?:й|я|е)|июн|июл|август|сентябр|октябр|ноябр|декабр)/iu.test(token);
const lowQualityGenericTokens = new Set([
"деньги",
"денег",
@ -680,13 +686,13 @@ function isLowQualityCounterpartyAnchorValue(rawValue) {
"целом"
]);
const meaningfulNonTemporalTokens = tokens.filter((token) => isLikelyCounterpartyToken(token) &&
!lowQualityTimeTokens.has(token) &&
!isLowQualityTimeToken(token) &&
!/^(?:19|20)\d{2}$/.test(token));
if (meaningfulNonTemporalTokens.length === 0 && hasTemporalCue) {
return true;
}
const meaningfulNonGenericTokens = tokens.filter((token) => isLikelyCounterpartyToken(token) &&
!lowQualityTimeTokens.has(token) &&
!isLowQualityTimeToken(token) &&
!lowQualityGenericTokens.has(token) &&
!/^(?:19|20)\d{2}$/.test(token));
if (meaningfulNonGenericTokens.length === 0 && (hasTemporalCue || paymentCue)) {
@ -1133,6 +1139,9 @@ function isTemporalWarehousePhrase(candidate) {
.toLowerCase()
.replace(/ё/g, "е")
.trim();
if (/^(?:в|на)?\s*(?:сейчас|сегодня|текущ(?:ий|ую|ем|его)\s+момент|данн(?:ый|ую|ом|ого)\s+момент)$/iu.test(normalized)) {
return true;
}
if (/^(?:на\s+)?(?:эту|ту|текущ(?:ую|ая|ий|ее|ей)|сегодняшн(?:юю|ая|ий|ее|ей))(?:\s+же)?\s+дат(?:у|а|е|ой)$/iu.test(normalized)) {
return true;
}
@ -1177,6 +1186,12 @@ function isLowQualityWarehouseAnchorValue(rawValue) {
"лежали",
"на",
"по",
"компания",
"компании",
"компанию",
"организация",
"организации",
"организацию",
"складе",
"складу",
"складом",
@ -1195,7 +1210,10 @@ function isLowQualityWarehouseAnchorValue(rawValue) {
if (tokens.length === 0) {
return true;
}
const meaningfulTokens = tokens.filter((token) => !lowQualityTokens.has(token) && token.length > 1);
const isLowQualityWarehouseToken = (token) => lowQualityTokens.has(token) ||
/^(?:19|20)\d{2}$/.test(token) ||
/^(?:январ|феврал|март|апрел|ма(?:й|я|е)|июн|июл|август|сентябр|октябр|ноябр|декабр)/iu.test(token);
const meaningfulTokens = tokens.filter((token) => !isLowQualityWarehouseToken(token) && token.length > 1);
if (meaningfulTokens.length === 0) {
return true;
}
@ -1256,11 +1274,13 @@ function extractInventoryWarehouseAnchor(text) {
return undefined;
}
function extractInventorySupplierAnchor(text) {
const match = String(text ?? "").match(/(?:от\s+поставщика|у\s+поставщика|поставщика|поставщику)\s+([^\r\n?]+?)(?=$|[?])/iu);
const match = String(text ?? "").match(/(?:от\s+поставщика|у\s+поставщика|поставщик(?:а|у|ом)?|supplier|vendor)\s+([^\r\n?]+?)(?=$|[?])/iu);
if (!match?.[1]) {
return undefined;
}
const candidate = cleanupAnchorValue(cleanupAnchorValue(String(match[1])).replace(/\s+(?:сейчас|на\s+склад(?:е|у|ом)?|на\s+дату|по\s+состоянию\s+на\s+дату|котор(?:ый|ые|ых)|куплен(?:ы|а|о)?|были|был|лежат|лежит|еще|ещё|организац\w*|компани\w*).*$/iu, ""));
const candidate = cleanupAnchorValue(cleanupAnchorValue(String(match[1]))
.replace(/\s+(?:сейчас|на\s+склад(?:е|у|ом)?|на\s+дату|по\s+состоянию\s+на\s+дату|котор(?:ый|ые|ых)|куплен(?:ы|а|о)?|были|был|лежат|лежит|еще|ещё|организац\w*|компани\w*).*$/iu, "")
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|item|product|sku)[\s\S]*$/iu, ""));
if (!candidate ||
isLowQualityCounterpartyAnchorValue(candidate) ||
/^(?:были|был|куплен|куплены|которые|который|которых|сейчас|лежат|лежит)\b/iu.test(candidate)) {
@ -1369,7 +1389,7 @@ function resolveSemanticDateScopeKind(filters, warnings) {
return "none";
}
function resolveSemanticDateBasisHint(filters, warnings) {
if (warnings.includes("as_of_date_defaulted_today")) {
if (warnings.includes("as_of_date_defaulted_today") || warnings.includes("as_of_date_from_implicit_current_phrase")) {
return "implicit_current_snapshot";
}
const hasAsOfDate = typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0;
@ -1446,6 +1466,11 @@ function buildSemanticFrame(text, filters, warnings) {
selected_object_scope_detected: selectedObjectScopeDetected
};
}
function shouldExpandSampleForValueAnalytics(intent) {
return (intent === "customer_revenue_and_payments" ||
intent === "supplier_payouts_profile" ||
intent === "contract_usage_and_value");
}
function extractAddressFilters(userMessage, intent) {
const rawText = String(userMessage ?? "").trim();
const text = normalizeMojibakeString(rawText);
@ -1470,6 +1495,7 @@ function extractAddressFilters(userMessage, intent) {
}
}
const warnings = [];
const implicitCurrentAsOfDateCue = hasImplicitCurrentAsOfDateCue(text);
const explicitAsOfDate = extractAsOfDate(text);
const explicitAsOfDateWithCue = extractAsOfDateWithCue(text);
const accountMatch = text.match(ACCOUNT_REVERSE_PATTERN) ?? text.match(ACCOUNT_PATTERN);
@ -1490,6 +1516,11 @@ function extractAddressFilters(userMessage, intent) {
filters.limit = Math.min(200, Math.trunc(parsed));
}
}
if (shouldExpandSampleForValueAnalytics(intent)) {
const currentLimit = typeof filters.limit === "number" && Number.isFinite(filters.limit) ? Math.max(1, Math.trunc(filters.limit)) : 0;
filters.limit = Math.max(currentLimit, VALUE_ANALYTICS_SAMPLE_LIMIT);
warnings.push("value_analytics_sample_limit_expanded");
}
if (isInventoryItemAnchoredIntent(intent)) {
const itemAnchor = extractInventoryItemAnchor(text);
if (itemAnchor) {
@ -1510,6 +1541,13 @@ function extractAddressFilters(userMessage, intent) {
filters.counterparty = supplierAnchor;
}
}
if (intent === "inventory_purchase_to_sale_chain") {
const supplierAnchor = asksForInventorySupplierIdentity(text) ? undefined : extractInventorySupplierAnchor(text);
if (supplierAnchor) {
filters.counterparty = supplierAnchor;
warnings.push("supplier_anchor_derived_for_inventory_documentary_chain");
}
}
const allowGenericCounterpartyAnchor = !isInventoryTraceIntent(intent);
const counterpartyMatch = allowGenericCounterpartyAnchor ? text.match(COUNTERPARTY_PATTERN) : null;
if (counterpartyMatch && !filters.counterparty) {
@ -1655,6 +1693,9 @@ function extractAddressFilters(userMessage, intent) {
}
if (usesAsOfPrimaryWindow(intent) && explicitAsOfDate) {
filters.as_of_date = explicitAsOfDate;
if (implicitCurrentAsOfDateCue && !warnings.includes("as_of_date_from_implicit_current_phrase")) {
warnings.push("as_of_date_from_implicit_current_phrase");
}
const periodWasDerivedHeuristically = warnings.includes("period_derived_from_month_phrase") ||
warnings.includes("period_derived_from_year_range_phrase") ||
warnings.includes("period_derived_from_year_phrase");
@ -1670,6 +1711,9 @@ function extractAddressFilters(userMessage, intent) {
const asOfDate = extractAsOfDate(text);
if (asOfDate) {
filters.as_of_date = asOfDate;
if (implicitCurrentAsOfDateCue && !warnings.includes("as_of_date_from_implicit_current_phrase")) {
warnings.push("as_of_date_from_implicit_current_phrase");
}
}
}
// For counterparty document/bank lists we keep period open by default (all-time over available data)

View File

@ -1416,6 +1416,12 @@ function hasInventoryPurchaseDocumentsSignalV2(text) {
return hasItemCue && hasPurchaseDocCue;
}
function hasInventorySaleTraceSignalV2(text) {
const value = String(text ?? "");
const hasPlainItemCue = /(?:\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|\u043f\u043e\u0437\u0438\u0446|\u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0438|sku|item|product)/iu.test(value);
const hasPlainTraceCue = /(?:\u043a\u043e\u043c\u0443\s+(?:\u0432\s+\u0438\u0442\u043e\u0433\u0435\s+)?(?:\u043c\u044b\s+)?(?:\u043f\u0440\u043e\u0434\u0430\u043b\u0438|\u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438|\u0432\u043f\u0430\u0440\u0438\u043b\u0438)|\u043a\u043e\u043c\u0443\s+(?:\u0431\u044b\u043b[\u0430\u0438\u043e]?|\u0431\u044b\u043b\u0438)?\s*(?:\u043f\u0440\u043e\u0434\u0430\u043d|\u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d)|\u043a\u0442\u043e\s+\u043a\u0443\u043f\u0438\u043b|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|buyer|sale\s+trace|trace\s+of\s+sale)/iu.test(value);
if (hasPlainItemCue && (hasPlainTraceCue || (0, inventoryLifecycleCueHelpers_1.hasInventorySaleCue)(value))) {
return true;
}
const hasItemCue = /(?:товар|номенклатур|sku|item|product|позици(?:я|ю|и)|продукци(?:я|ю|и))/iu.test(text);
const hasTraceCue = /(?:РєРѕРјСѓ\s+(?:РІ\s+итоге\s+)?(?:РјС\s+)?продали|РєРѕРјСѓ\s+был\s+продан|РєСѓРґР°\s+(?:РІ\s+итоге\s+)?(?:РјС\s+)?продали(?:\s+(?:это|его|товар|позицию))?|РєСѓРґР°\s+(?:была\s+)?реализована\s+(?:позиция|номенклатура|продукция)|кто\s+РєСѓРїРёР»|buyer|sale\s+trace|trace\s+of\s+sale|через\s+какие\s+документы\s+РїСЂРѕС€[её]Р»\s+путь\s+товара|закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*warehouse\s*->\s*sale|purchase\s*->\s*stock\s*->\s*sale)/iu.test(text);
return hasItemCue && hasTraceCue;
@ -1435,6 +1441,12 @@ function hasInventoryAgingSignal(text) {
return hasAgingCue || (hasResidueCue && /(?:давно\s+куплен|давно\s+приобретен|задолго\s+до)/iu.test(text));
}
function hasInventoryPurchaseToSaleChainSignal(text) {
const value = String(text ?? "");
const hasPlainItemCue = /(?:товар|номенклатур|позици|sku|item|product)/iu.test(value);
const hasPlainChainCue = /(?:закупк[а-яё]*\s*->\s*склад\s*->\s*продаж|закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|через\s+какие\s+документы\s+прош[её]л\s+путь|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|цепочк[а-яё]*\s+движен|документально\s+подтвержденн[а-яё]*\s+цепочк|supplier\s*->\s*item\s*->\s*(?:buyer|customer)|supplier\s+to\s+buyer|supplier\s+to\s+item\s+to\s+buyer|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale)/iu.test(value) || value.includes("->");
if (hasPlainItemCue && hasPlainChainCue) {
return true;
}
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
const hasChainCue = /(?:закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|закупка\s*->\s*склад\s*->\s*продажа|цепочк[аи]\s+движен|документально\s+подтвержденн\w+\s+цепочк|supplier\s*->\s*item\s*->\s*(?:buyer|customer)|supplier\s+to\s+buyer|supplier\s+to\s+item\s+to\s+buyer)/iu.test(text) || text.includes("->");
return hasItemCue && hasChainCue;
@ -1603,6 +1615,10 @@ function resolveUnicodeAddressIntentBridge(text) {
]).has(byAnchorToken);
const hasMoneyCue = /(?:деньг|денег|выручк|доход|оборот|заработ|прин[её]с|чек|ликвидн|revenue|turnover|money)/iu.test(normalized);
const hasRankingCue = /(?:топ|ранк|сам(?:ый|ая|ое|ые)|больше\s+всего|наибольш|крупн|жирн|max|top|rank)/iu.test(normalized);
const hasInventoryPurchaseToSaleDocumentChainCue = /(?:закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|->\s*(?:склад|warehouse|stock)\s*->\s*(?:продаж|sale))/iu.test(normalized) && /(?:товар|позици|номенклатур|sku|item|product)/iu.test(normalized);
if (hasInventoryPurchaseToSaleDocumentChainCue) {
return unicodeBridgeResolution("inventory_purchase_to_sale_chain", "high", "unicode_inventory_purchase_to_sale_chain_bridge_signal_detected");
}
const hasOpenItemsAccountCue = /(?:хвост|долг|незакрыт|вис)/iu.test(normalized) &&
/(?:сч(?:е|ё)т(?:а|у|ом|е|ов)?\s*(?:№|#)?\s*(?:60|62|76)(?:[.,]\d{1,2})?|\b(?:60|62|76)(?:[.,]\d{1,2})?\b\s*сч(?:е|ё)т)/iu.test(normalized);
if (hasOpenItemsAccountCue) {
@ -1875,7 +1891,19 @@ function resolveAddressIntent(userMessage) {
const currentTurnBridgeText = turnNoiseNormalizedBridgeText !== bridgeText ? `${bridgeText} ${turnNoiseNormalizedBridgeText}` : bridgeText;
const unicodeAddressIntent = resolveUnicodeAddressIntentBridge(currentTurnBridgeText);
if (unicodeAddressIntent) {
return unicodeAddressIntent;
const reasons = [...unicodeAddressIntent.reasons];
if (currentTurnBridgeText !== bridgeText && !reasons.includes("current_turn_noise_normalized")) {
reasons.push("current_turn_noise_normalized");
}
if (unicodeAddressIntent.intent === "customer_revenue_and_payments" &&
[text, repairedText, turnNoiseNormalizedBridgeText, currentTurnBridgeText].some((sample) => hasSpecificCounterpartyRevenueBridgeSignal(sample)) &&
!reasons.includes("specific_counterparty_revenue_bridge_signal_detected")) {
reasons.push("specific_counterparty_revenue_bridge_signal_detected");
}
return {
...unicodeAddressIntent,
reasons
};
}
const hasLooseVatPayableBridge = /(?:\u043d\u0434\u0441|vat)/iu.test(text) &&
/(?:\u043a\u0430\u043a\u043e\u0439\s+\u043d\u0434\u0441\s+(?:(?:\u043d\u0430\u043c|(?:\u043c\u044b\s+)?\u0434\u043e\u043b\u0436\u043d\u044b)\s+)?(?:\u043d\u0430\u0434\u043e|\u043d\u0443\u0436\u043d\u043e|\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e)|(?:\u043d\u0430\u043c|\u043c\u044b\s+)?\u043d\u0430\u0434\u043e\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|(?:\u043d\u0430\u043c|\u043c\u044b\s+)?\u043d\u0443\u0436\u043d\u043e\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|\u043c\u044b\s+\u0434\u043e\u043b\u0436\u043d\u044b\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|\u043d\u0434\u0441\s+\u043a\s+\u0443\u043f\u043b\u0430\u0442\u0435)/iu.test(text) &&

View File

@ -89,9 +89,13 @@ function hasInventoryProvenanceSignalV2(text) {
return hasItemCue && hasSupplierCue && hasPurchaseCue;
}
function hasInventoryPurchaseDateSignal(text) {
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text) || hasSelectedObjectInventoryCue(text);
const hasPurchaseDateCue = /(?:РєРѕРіРґР°\s+(?:примерно\s+)?(?:РјС\s+)?купили|РєРѕРіРґР°\s+был\s+куплен|РєРѕРіРґР°\s+куплен|дата\s+закупк|purchase\s+date)/iu.test(text) ||
/(?:когда\s+был(?:а|и|о)?\s+закупк\w*|когда\s+закупк\w*)/iu.test(text);
const value = String(text ?? "");
const hasItemCue = /(?:\u0442\u043e\u0432\u0430\u0440|\u043f\u043e\u0437\u0438\u0446|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|sku|item|product)/iu.test(value) ||
/(?:товар|номенклатур|sku|item|product)/iu.test(value) ||
hasSelectedObjectInventoryCue(value);
const hasPurchaseDateCue = /(?:\u043a\u043e\u0433\u0434\u0430\s+(?:\u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e\s+)?(?:\u043c\u044b\s+)?\u043a\u0443\u043f\u0438\u043b\u0438|\u043a\u043e\u0433\u0434\u0430\s+\u0431\u044b\u043b(?:\u0430|\u0438|\u043e)?\s+\u043a\u0443\u043f\u043b\u0435\u043d|\u043a\u043e\u0433\u0434\u0430\s+\u043a\u0443\u043f\u043b\u0435\u043d|\u0434\u0430\u0442\u0430\s+\u0437\u0430\u043a\u0443\u043f\u043a|purchase\s+date)/iu.test(value) ||
/(?:РєРѕРіРґР°\s+(?:примерно\s+)?(?:РјС\s+)?купили|РєРѕРіРґР°\s+был\s+куплен|РєРѕРіРґР°\s+куплен|дата\s+закупк|purchase\s+date)/iu.test(value) ||
/(?:когда\s+был(?:а|и|о)?\s+закупк\w*|когда\s+закупк\w*)/iu.test(value);
return hasItemCue && hasPurchaseDateCue;
}
function hasInventoryPurchaseDocumentsSignalV2(text) {
@ -108,7 +112,7 @@ function hasInventoryPurchaseDocumentsSignalV2(text) {
function hasInventorySaleTraceSignalV2(text) {
const value = String(text ?? "");
const hasPlainItemCue = /(?:товар|номенклатур|позици|продукци|sku|item|product)/iu.test(value);
const hasPlainTraceCue = /(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?(?:продали|реализовали|впарили)|кому\s+(?:был[аио]?|были)?\s*реализован|кто\s+купил|покупател|buyer|sale\s+trace|trace\s+of\s+sale)/iu.test(value);
const hasPlainTraceCue = /(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?(?:продали|реализовали|впарили)|кому\s+(?:был[аио]?|были)?\s*(?:продан|реализован)|кто\s+купил|покупател|buyer|sale\s+trace|trace\s+of\s+sale)/iu.test(value);
if (hasPlainItemCue && hasPlainTraceCue) {
return true;
}

View File

@ -211,6 +211,20 @@ function deriveTaxQuarterWindowForDate(value) {
period_to: `${year}-${String(quarterEndMonth).padStart(2, "0")}-${String(quarterEndDay).padStart(2, "0")}`
};
}
function deriveMonthWindowForDate(value) {
const isoDate = normalizeIsoDateForQuery(value);
if (!isoDate) {
return null;
}
const match = isoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
return {
period_from: `${match[1]}-${match[2]}-01`,
period_to: isoDate
};
}
function toDateTimeExprForQuery(isoDate) {
const match = String(isoDate ?? "").match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
@ -1173,6 +1187,8 @@ function toNormalizedRows(rows) {
const item = resolveInventoryItemFromRawRow(row, accountDt, accountKt);
const warehouse = firstNonEmptyString(row.Склад, row.Warehouse, row.warehouse, row.СкладПредставление);
const organization = firstNonEmptyString(row.Организация, row.Organization, row.organization, row.organization_name, row.ОрганизацияПредставление);
const counterparty = firstNonEmptyString(row.Контрагент, row.Counterparty, row.counterparty);
const contract = firstNonEmptyString(row.Договор, row.Contract, row.contract);
const analytics = collectAnalyticsStrings(row);
return {
period,
@ -1184,7 +1200,9 @@ function toNormalizedRows(rows) {
quantity,
item,
warehouse,
organization
organization,
counterparty,
contract
};
})
.filter((item) => Boolean(item.period || item.registrator));
@ -1235,6 +1253,10 @@ function formatMoneyRubForReply(value) {
}).format(value)} `;
}
function extractContractNameFromNormalizedRow(row) {
const explicitContract = firstNonEmptyString(row.contract);
if (explicitContract) {
return explicitContract;
}
for (const token of row.analytics) {
const normalized = String(token ?? "").trim();
if (!normalized) {
@ -1539,6 +1561,10 @@ function applyPreExecutionOrganizationScopeGrounding(input) {
]);
const resolvedOrganizationFromMessage = (0, assistantOrganizationMatcher_1.resolveOrganizationSelectionFromMessage)(input.userMessage, candidateOrganizations);
const referentialOrganizationScopeDetected = hasReferentialOrganizationScopeSignal(input.userMessage);
const counterpartyAnchorProtectsOrganizationScope = input.semanticFrame?.anchor_kind === "counterparty" &&
typeof input.filters.counterparty === "string" &&
input.filters.counterparty.trim().length > 0 &&
!referentialOrganizationScopeDetected;
if (!input.filters.organization &&
input.semanticFrame?.scope_kind === "implicit_self_scope" &&
activeOrganization) {
@ -1552,6 +1578,7 @@ function applyPreExecutionOrganizationScopeGrounding(input) {
}
if (resolvedOrganizationFromMessage &&
(!input.filters.organization || input.semanticFrame?.anchor_kind === "organization") &&
!counterpartyAnchorProtectsOrganizationScope &&
!sameNormalizedOrganizationScope(input.filters.organization ?? null, resolvedOrganizationFromMessage)) {
input.filters.organization = resolvedOrganizationFromMessage;
if (!input.warnings.includes("organization_grounded_from_scope_candidates")) {
@ -1921,6 +1948,9 @@ function hasExplicitPeriodWindow(filters) {
return ((typeof filters.period_from === "string" && filters.period_from.trim().length > 0) ||
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0));
}
function asksForUnresolvedInventorySupplierLink(userMessage) {
return /(?:\u0431\u0435\u0437\s+\u043f\u043e\u043d\u044f\u0442\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0431\u0435\u0437\s+(?:\u044f\u0432\u043d[^\s]*\s+)?\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043d\u0435\s+\u0438\u043c\u0435\u044e\u0442\s+\u044f\u0432\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043d\u0435\u0442\s+\u044f\u0432\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|unresolved\s+supplier\s+link)/iu.test(String(userMessage ?? ""));
}
function canAutoBroadenPeriodWindow(intent, filters) {
const hasRecoverableAsOfOnlyWindow = !hasExplicitPeriodWindow(filters) &&
typeof filters.as_of_date === "string" &&
@ -1940,6 +1970,7 @@ function canAutoBroadenPeriodWindow(intent, filters) {
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date");
}
@ -1948,19 +1979,25 @@ function shouldBoostAutoBroadenedLimit(intent) {
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date");
}
function shouldClearAsOfDateForHistoryRecovery(intent) {
return (intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain");
}
function shouldDetachLifecycleExecutionFromSnapshotContext(intent, reasons) {
if (intent !== "inventory_sale_trace_for_item" &&
if (intent !== "inventory_supplier_stock_overlap_as_of_date" &&
intent !== "inventory_sale_trace_for_item" &&
intent !== "inventory_profitability_for_item" &&
intent !== "inventory_purchase_to_sale_chain") {
return false;
}
return (reasons.includes("as_of_date_from_followup_context") ||
return (reasons.includes("period_window_semantic_from_inventory_snapshot_context") ||
reasons.includes("period_window_semantic_from_inventory_as_of_month") ||
reasons.includes("as_of_date_from_followup_context") ||
reasons.includes("period_from_followup_context") ||
reasons.includes("as_of_date_from_open_items_followup_context"));
}
@ -2820,6 +2857,84 @@ class AddressQueryService {
});
const knownOrganizations = (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)(options.knownOrganizations ?? []);
const activeOrganization = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(options.activeOrganization ?? null);
const previousOrganizationFromContext = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(followupContext?.previous_filters?.organization ?? null);
const chainCounterpartyAnchor = toNonEmptyFilterValue(filters.extracted_filters.counterparty);
const chainOrganizationAnchor = toNonEmptyFilterValue(filters.extracted_filters.organization);
if (intent.intent === "inventory_purchase_to_sale_chain" &&
chainCounterpartyAnchor &&
chainOrganizationAnchor &&
sameOrganizationEntityReference(chainOrganizationAnchor, chainCounterpartyAnchor)) {
delete filters.extracted_filters.organization;
const restoredOrganization = activeOrganization ?? previousOrganizationFromContext;
if (restoredOrganization && !sameOrganizationEntityReference(restoredOrganization, chainCounterpartyAnchor)) {
filters.extracted_filters.organization = restoredOrganization;
if (!filters.warnings.includes("organization_restored_from_inventory_chain_context")) {
filters.warnings.push("organization_restored_from_inventory_chain_context");
}
if (!baseReasons.includes("organization_restored_from_inventory_chain_context")) {
baseReasons.push("organization_restored_from_inventory_chain_context");
}
}
else {
if (!filters.warnings.includes("organization_cleared_from_inventory_chain_counterparty_anchor")) {
filters.warnings.push("organization_cleared_from_inventory_chain_counterparty_anchor");
}
if (!baseReasons.includes("organization_cleared_from_inventory_chain_counterparty_anchor")) {
baseReasons.push("organization_cleared_from_inventory_chain_counterparty_anchor");
}
}
}
if (intent.intent === "inventory_supplier_stock_overlap_as_of_date" &&
(followupContext?.root_filters || followupContext?.previous_filters) &&
!toNonEmptyFilterValue(filters.extracted_filters.period_from) &&
!toNonEmptyFilterValue(filters.extracted_filters.period_to) &&
!/(?:за\s+вс[её]\s+время|за\s+любой\s+период|all[\s-]?time|all\s+periods?)/iu.test(userMessage)) {
const snapshotContextFilters = followupContext?.root_filters &&
(toNonEmptyFilterValue(followupContext.root_filters.period_from) ||
toNonEmptyFilterValue(followupContext.root_filters.period_to))
? followupContext.root_filters
: followupContext?.previous_filters;
const previousPeriodFrom = toNonEmptyFilterValue(snapshotContextFilters?.period_from);
const previousPeriodTo = toNonEmptyFilterValue(snapshotContextFilters?.period_to);
if (previousPeriodFrom || previousPeriodTo) {
filters.extracted_filters = {
...filters.extracted_filters,
...(previousPeriodFrom ? { period_from: previousPeriodFrom } : {}),
...(previousPeriodTo ? { period_to: previousPeriodTo } : {})
};
if (!toNonEmptyFilterValue(filters.extracted_filters.as_of_date)) {
const inheritedAsOfDate = toNonEmptyFilterValue(snapshotContextFilters?.as_of_date) ?? previousPeriodTo ?? previousPeriodFrom;
if (inheritedAsOfDate) {
filters.extracted_filters.as_of_date = inheritedAsOfDate;
}
}
if (!filters.warnings.includes("period_window_semantic_from_inventory_snapshot_context")) {
filters.warnings.push("period_window_semantic_from_inventory_snapshot_context");
}
if (!baseReasons.includes("period_window_semantic_from_inventory_snapshot_context")) {
baseReasons.push("period_window_semantic_from_inventory_snapshot_context");
}
}
}
if (intent.intent === "inventory_supplier_stock_overlap_as_of_date" &&
!toNonEmptyFilterValue(filters.extracted_filters.period_from) &&
!toNonEmptyFilterValue(filters.extracted_filters.period_to) &&
asksForUnresolvedInventorySupplierLink(userMessage) &&
!/(?:за\s+вс[её]\s+время|за\s+любой\s+период|all[\s-]?time|all\s+periods?)/iu.test(userMessage)) {
const monthWindow = deriveMonthWindowForDate(filters.extracted_filters.as_of_date);
if (monthWindow) {
filters.extracted_filters = {
...filters.extracted_filters,
...monthWindow
};
if (!filters.warnings.includes("period_window_semantic_from_inventory_as_of_month")) {
filters.warnings.push("period_window_semantic_from_inventory_as_of_month");
}
if (!baseReasons.includes("period_window_semantic_from_inventory_as_of_month")) {
baseReasons.push("period_window_semantic_from_inventory_as_of_month");
}
}
}
if (isOrganizationScopedValueFlowIntent(intent.intent) &&
hasExplicitSingleOrganizationValueFlowScopeRequest(userMessage) &&
!resolvedOrganizationFromMessage) {
@ -2969,13 +3084,14 @@ class AddressQueryService {
const detachedExecutionFilters = { ...executionFilters };
let periodDetached = false;
let asOfDetached = false;
const keepAsOfDateForInventorySnapshotOverlap = intent.intent === "inventory_supplier_stock_overlap_as_of_date";
if (toNonEmptyFilterValue(detachedExecutionFilters.period_from) ||
toNonEmptyFilterValue(detachedExecutionFilters.period_to)) {
delete detachedExecutionFilters.period_from;
delete detachedExecutionFilters.period_to;
periodDetached = true;
}
if (toNonEmptyFilterValue(detachedExecutionFilters.as_of_date)) {
if (!keepAsOfDateForInventorySnapshotOverlap && toNonEmptyFilterValue(detachedExecutionFilters.as_of_date)) {
delete detachedExecutionFilters.as_of_date;
asOfDetached = true;
}
@ -3452,6 +3568,18 @@ class AddressQueryService {
: anchor.anchor_type === "contract" && anchor.anchor_value_resolved
? { ...executionFilters, contract: anchor.anchor_value_resolved }
: executionFilters;
if (intent.intent === "inventory_purchase_to_sale_chain" &&
toNonEmptyFilterValue(filtersForMatching.item) &&
toNonEmptyFilterValue(filtersForMatching.counterparty)) {
filtersForMatching = { ...filtersForMatching };
delete filtersForMatching.counterparty;
if (!filters.warnings.includes("inventory_chain_counterparty_anchor_kept_for_verification")) {
filters.warnings.push("inventory_chain_counterparty_anchor_kept_for_verification");
}
if (!baseReasons.includes("inventory_chain_counterparty_anchor_kept_for_verification")) {
baseReasons.push("inventory_chain_counterparty_anchor_kept_for_verification");
}
}
const accountScopeAudit = buildAccountScopeAudit({
intent: intent.intent,
filters: filtersForMatching,

View File

@ -833,6 +833,17 @@ const BASE_RECIPES = [
account_scope_mode: "strict",
query_template: "inventory_sale_trace_profile"
},
{
recipe_id: "address_inventory_trading_margin_proxy_for_organization_v1",
intent: "inventory_trading_margin_proxy_for_organization",
purpose: "Trace organization-period purchase and sale document rows and derive bounded trading revenue, purchase-cost proxy, spread, and margin proxy",
required_filters: [],
optional_filters: ["period_from", "period_to", "organization", "warehouse", "limit", "sort"],
default_limit: 600,
account_scope: ["41.01"],
account_scope_mode: "strict",
query_template: "inventory_trading_margin_proxy_profile"
},
{
recipe_id: "address_inventory_purchase_to_sale_chain_v1",
intent: "inventory_purchase_to_sale_chain",
@ -844,6 +855,17 @@ const BASE_RECIPES = [
account_scope_mode: "strict",
query_template: "inventory_purchase_to_sale_chain_profile"
},
{
recipe_id: "address_inventory_profitability_for_item_v1",
intent: "inventory_profitability_for_item",
purpose: "Trace purchase and sale document rows for one inventory item and derive bounded revenue, purchase-cost proxy, spread, and margin",
required_filters: ["item"],
optional_filters: ["as_of_date", "period_from", "period_to", "organization", "warehouse", "limit", "sort"],
default_limit: 600,
account_scope: ["41.01"],
account_scope_mode: "strict",
query_template: "inventory_profitability_profile"
},
{
recipe_id: "address_inventory_aging_by_purchase_date_v1",
intent: "inventory_aging_by_purchase_date",
@ -1235,6 +1257,26 @@ function buildInventoryPurchaseDocumentQuery(filters, resolvedLimit) {
.replace("__WHERE_CLAUSE__", buildWhereClause(filters, "Товары.Ссылка.Дата", ['Товары.Ссылка.Проведен = ИСТИНА', itemCondition].filter((item) => Boolean(item))))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
}
function stripTrailingOrderBy(query) {
return String(query ?? "").replace(/\r?\nУПОРЯДОЧИТЬ ПО[\s\S]*$/u, "").trimEnd();
}
function removeTopLimit(query) {
return String(query ?? "").replace(/ВЫБРАТЬ ПЕРВЫЕ\s+\d+/u, "ВЫБРАТЬ");
}
function buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit) {
const purchaseQuery = removeTopLimit(stripTrailingOrderBy(buildInventoryPurchaseDocumentQuery(filters, resolvedLimit)));
const saleQuery = removeTopLimit(stripTrailingOrderBy(buildInventorySaleDocumentQuery(filters, resolvedLimit)));
return [
purchaseQuery,
"",
"ОБЪЕДИНИТЬ ВСЕ",
"",
saleQuery,
"",
"УПОРЯДОЧИТЬ ПО",
` Период ${resolveOrderDirection(filters.sort)}`
].join("\n");
}
function buildCounterpartyPurchaseDocumentQuery(filters, resolvedLimit) {
const goodsCounterpartyCondition = buildCounterpartyReferenceCondition(filters, ["Товары.Ссылка.Контрагент"]);
const servicesCounterpartyCondition = buildCounterpartyReferenceCondition(filters, ["Услуги.Ссылка.Контрагент"]);
@ -1284,6 +1326,8 @@ function maxLimitForIntent(intent) {
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_trading_margin_proxy_for_organization" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date" ||
intent === "open_contracts_confirmed_as_of_date" ||
@ -1463,50 +1507,18 @@ function buildAddressRecipePlan(recipe, filters) {
: recipe.query_template === "inventory_supplier_stock_overlap_profile"
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
: recipe.query_template === "inventory_sale_trace_profile"
? buildInventoryMovementQuery(filters, resolvedLimit, "kt")
: recipe.query_template === "inventory_purchase_to_sale_chain_profile"
? buildInventoryMovementQuery(filters, resolvedLimit, "either")
: recipe.query_template === "inventory_aging_by_purchase_date_profile"
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
: recipe.query_template === "contracts_by_counterparty_profile"
? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit))
: recipe.query_template === "open_contracts_confirmed_as_of_balance_profile"
? (() => {
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
? toDateTimeExpr(filters.as_of_date, true)
: null) ??
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
? toDateTimeExpr(filters.period_to, true)
: null) ??
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "62", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: recipe.query_template === "payables_confirmed_as_of_balance_profile"
? (() => {
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
? toDateTimeExpr(filters.as_of_date, true)
: null) ??
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
? toDateTimeExpr(filters.period_to, true)
: null) ??
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
? buildInventorySaleDocumentQuery(filters, resolvedLimit)
: recipe.query_template === "inventory_profitability_profile"
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
: recipe.query_template === "inventory_trading_margin_proxy_profile"
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
: recipe.query_template === "inventory_purchase_to_sale_chain_profile"
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
: recipe.query_template === "inventory_aging_by_purchase_date_profile"
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
: recipe.query_template === "contracts_by_counterparty_profile"
? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit))
: recipe.query_template === "open_contracts_confirmed_as_of_balance_profile"
? (() => {
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
? toDateTimeExpr(filters.as_of_date, true)
@ -1518,23 +1530,59 @@ function buildAddressRecipePlan(recipe, filters) {
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "62", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: MOVEMENTS_QUERY_TEMPLATE
.replace("__LIMIT__", String(resolvedLimit))
.replace("__WHERE_CLAUSE__", (() => {
const extraConditions = [];
const accountCondition = buildMovementAccountCondition(filters);
if (accountCondition) {
extraConditions.push(accountCondition);
}
return buildWhereClause(filters, "Движения.Период", extraConditions);
})())
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
: recipe.query_template === "payables_confirmed_as_of_balance_profile"
? (() => {
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
? toDateTimeExpr(filters.as_of_date, true)
: null) ??
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
? toDateTimeExpr(filters.period_to, true)
: null) ??
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
? (() => {
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
? toDateTimeExpr(filters.as_of_date, true)
: null) ??
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
? toDateTimeExpr(filters.period_to, true)
: null) ??
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: MOVEMENTS_QUERY_TEMPLATE
.replace("__LIMIT__", String(resolvedLimit))
.replace("__WHERE_CLAUSE__", (() => {
const extraConditions = [];
const accountCondition = buildMovementAccountCondition(filters);
if (accountCondition) {
extraConditions.push(accountCondition);
}
return buildWhereClause(filters, "Движения.Период", extraConditions);
})())
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
return {
recipe,
query,

View File

@ -848,6 +848,16 @@ function extractInventoryCounterpartyCandidates(row, excludedTokens = []) {
}
candidates.push(normalized);
}
const explicitCounterparty = normalizeCounterpartyDisplayLabel(row.counterparty);
const explicitComparable = normalizeEntityToken(explicitCounterparty);
if (explicitCounterparty &&
explicitComparable &&
explicitComparable !== itemToken &&
explicitComparable !== warehouseToken &&
explicitComparable !== organizationToken &&
!excludedComparableTokens.includes(explicitComparable)) {
candidates.unshift(explicitCounterparty);
}
return uniqueStrings(candidates);
}
function summarizeInventoryTraceRows(rows, excludedCounterpartyTokens = []) {
@ -883,6 +893,7 @@ function summarizeInventoryTraceRows(rows, excludedCounterpartyTokens = []) {
function formatInventoryTraceRows(rows, limit = 10, excludedCounterpartyTokens = []) {
return rows.slice(0, limit).map((row, index) => {
const parties = extractInventoryCounterpartyCandidates(row, excludedCounterpartyTokens);
const item = extractInventoryItemName(row);
const warehouse = extractInventoryWarehouseName(row);
const organization = extractInventoryOrganizationName(row);
const amount = typeof row.amount === "number" && Number.isFinite(row.amount) ? formatMoneyRub(row.amount) : "сумма не указана";
@ -891,6 +902,9 @@ function formatInventoryTraceRows(rows, limit = 10, excludedCounterpartyTokens =
`дата: ${inventoryTraceDateLabel(row.period)}`,
`сумма: ${amount}`
];
if (item) {
parts.push(`товар: ${item}`);
}
if (warehouse) {
parts.push(`склад: ${warehouse}`);
}

View File

@ -593,15 +593,24 @@ function composeCounterpartyAnalyticsReply(intent, rows, options = {}, deps) {
return (0, replyContracts_1.buildFactualListReply)(lines);
}
const visible = rankedByTotal.slice(0, limit);
const heading = isSupplier
? `Топ-${visible.length} поставщиков по сумме выплат:`
: `Топ-${visible.length} заказчиков по сумме поступлений:`;
const singleCandidateOnly = rankedByTotal.length === 1;
const heading = singleCandidateOnly
? isSupplier
? "Найденный поставщик по сумме выплат:"
: "Найденный заказчик по сумме поступлений:"
: isSupplier
? `Топ-${visible.length} поставщиков по сумме выплат:`
: `Топ-${visible.length} заказчиков по сумме поступлений:`;
const leadingCounterparty = visible[0] ?? null;
lines.unshift(heading);
if (leadingCounterparty) {
const directAnswerLine = isSupplier
? `Крупнейший поставщик по подтвержденным выплатам за доступное время: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям).`
: `Самый доходный клиент за доступное время по подтвержденным поступлениям: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это денежный поток, а не чистая прибыль.`;
const directAnswerLine = singleCandidateOnly
? isSupplier
? `В выбранном срезе найден один поставщик: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это не полноценный сравнительный рейтинг.`
: `В выбранном срезе найден один клиент: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это не полноценный сравнительный рейтинг; сумма является денежным потоком, а не чистой прибылью.`
: isSupplier
? `Крупнейший поставщик по подтвержденным выплатам за доступное время: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям).`
: `Самый доходный клиент за доступное время по подтвержденным поступлениям: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это денежный поток, а не чистая прибыль.`;
lines.unshift(directAnswerLine);
}
lines.push(...visible.map((item, index) => {

View File

@ -292,6 +292,7 @@ function isInventoryLifecycleHistoryIntent(intent) {
return (intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain");
}
function shouldSuppressInventoryCounterpartyAlias(intent, counterparty, organization) {
@ -1008,6 +1009,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date" ||
intent === "payables_confirmed_as_of_date" ||
@ -1061,6 +1063,19 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
reasons.push("period_from_followup_context");
}
}
if (intent === "inventory_profitability_for_item" &&
previousHasPeriod &&
hasSelectedObjectInventorySignal(userMessage) &&
!hasExplicitPeriodInMessage &&
!hasExplicitCurrentDateInMessage) {
if (previousPeriodFrom && merged.period_from !== previousPeriodFrom) {
merged.period_from = previousPeriodFrom;
}
if (previousPeriodTo && merged.period_to !== previousPeriodTo) {
merged.period_to = previousPeriodTo;
}
reasons.push("period_from_followup_context");
}
if (!currentHasPeriod &&
previousHasPeriod &&
hasFollowupSignal &&

View File

@ -3,6 +3,83 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.composeInventoryReply = composeInventoryReply;
const replyContracts_1 = require("./replyContracts");
const inventoryReplyPresentation_1 = require("./inventoryReplyPresentation");
function cleanupInventoryRequestedParty(value) {
const cleaned = String(value ?? "")
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|покупател|buyer|customer|item|product|sku)[\s\S]*$/iu, "")
.replace(/\s+(?:на\s+дату|по\s+состоянию|за\s+период)\b[\s\S]*$/iu, "")
.replace(/[«»"]/gu, "")
.replace(/[.,;:\s]+$/u, "")
.trim();
return cleaned.length > 0 ? cleaned : null;
}
function extractRequestedInventoryParty(userMessage, role) {
const text = String(userMessage ?? "");
const patterns = role === "supplier"
? [
/(?:от\s+поставщика|у\s+поставщика|поставщик(?:а|у|ом)?|supplier|vendor)\s+([^\r\n?]+?)(?=$|[?]|(?:\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|item|product|sku|покупател|buyer|customer)))/iu
]
: [
/(?:покупател(?:ь|я|ю|ем)?|buyer|customer|client)\s+([^\r\n?]+?)(?=$|[?]|(?:\s*(?:->|=>|→))|(?:\s+на\s+дату))/iu
];
for (const pattern of patterns) {
const match = text.match(pattern);
const candidate = match?.[1] ? cleanupInventoryRequestedParty(match[1]) : null;
if (candidate) {
return candidate;
}
}
return null;
}
function inventoryPartyComparableTokens(value) {
const stopWords = new Set(["ооо", "ао", "пао", "зао", "ип", "llc", "ltd", "inc", "corp"]);
return String(value ?? "")
.toLowerCase()
.replace(/ё/gu, "е")
.replace(/[^a-zа-я0-9]+/giu, " ")
.split(/\s+/u)
.map((token) => token.trim())
.filter((token) => token.length >= 3 && !stopWords.has(token));
}
function inventoryRequestedPartyMatches(requested, actualParties) {
if (!requested) {
return true;
}
const requestedTokens = inventoryPartyComparableTokens(requested);
if (requestedTokens.length === 0) {
return false;
}
return actualParties.some((actual) => {
const actualTokens = inventoryPartyComparableTokens(actual);
return requestedTokens.every((token) => actualTokens.includes(token));
});
}
function inventoryPartyListOrUnknown(parties) {
return parties.length > 0 ? parties.slice(0, 4).join("; ") : "не выделен отдельным полем";
}
function sumInventoryRowAmount(rows) {
return rows.reduce((sum, row) => sum + (typeof row.amount === "number" && Number.isFinite(row.amount) ? row.amount : 0), 0);
}
function sumInventoryRowQuantity(rows) {
return rows.reduce((sum, row) => sum + (typeof row.quantity === "number" && Number.isFinite(row.quantity) ? row.quantity : 0), 0);
}
function formatInventoryPercent(value, formatNumberWithDots) {
return value === null || !Number.isFinite(value) ? "не подтверждена" : `${formatNumberWithDots(value, 2)}%`;
}
function inventoryProfitabilityPeriodLabel(options, deps) {
const from = typeof options.periodFrom === "string" && options.periodFrom.trim().length > 0 ? options.periodFrom : null;
const to = typeof options.periodTo === "string" && options.periodTo.trim().length > 0 ? options.periodTo : null;
if (from && to) {
return `${deps.formatDateRu(from)} - ${deps.formatDateRu(to)}`;
}
if (from) {
return `с ${deps.formatDateRu(from)}`;
}
if (to) {
return `до ${deps.formatDateRu(to)}`;
}
const asOfDate = typeof options.asOfDate === "string" && options.asOfDate.trim().length > 0 ? options.asOfDate : null;
return asOfDate ? `до ${deps.formatDateRu(asOfDate)}` : "по доступной выборке";
}
function composeInventoryReply(intent, rows, options, deps) {
if (intent === "inventory_on_hand_as_of_date") {
const asOfDate = deps.resolvePayablesAsOfDate(options);
@ -163,6 +240,29 @@ function composeInventoryReply(intent, rows, options, deps) {
const purchaseRows = rows.filter((row) => deps.isInventoryPurchaseMovement(row));
const summary = deps.summarizeInventoryTraceRows(purchaseRows);
const unresolvedRows = purchaseRows.filter((row) => deps.extractInventoryCounterpartyCandidates(row).length === 0);
const unresolvedSupplierQuestion = /(?:\u0431\u0435\u0437\s+\u043f\u043e\u043d\u044f\u0442\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0431\u0435\u0437\s+(?:\u044f\u0432\u043d[^\s]*\s+)?\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043d\u0435\s+\u0438\u043c\u0435\u044e\u0442\s+\u044f\u0432\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043d\u0435\u0442\s+\u044f\u0432\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|unresolved\s+supplier\s+link)/iu.test(String(options.userMessage ?? ""));
if (unresolvedSupplierQuestion) {
const directAnswerLine = unresolvedRows.length > 0
? `В текущем складском срезе найдено операций без явно выделенного поставщика: ${deps.formatNumberWithDots(unresolvedRows.length)}.`
: "В текущем складском срезе товары без явно выделенной привязки к поставщику в доступных данных не найдены.";
const lines = [directAnswerLine];
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Что проверили:", [
`Дата среза: ${deps.formatDateRu(asOfDate)}.`,
`Закупочных операций в выборке: ${deps.formatNumberWithDots(purchaseRows.length)}.`,
`Операций без явно выделенного поставщика: ${deps.formatNumberWithDots(unresolvedRows.length)}.`,
`Поставщиков, выделенных в остальных операциях: ${deps.formatNumberWithDots(summary.counterparties.length)}.`
]);
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [
"Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии."
]);
if (unresolvedRows.length > 0) {
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Позиции без явно выделенного поставщика:", deps.formatInventoryTraceRows(unresolvedRows, 12));
}
else if (summary.counterparties.length > 0) {
lines.push(`- В доступном закупочном следе встречаются поставщики: ${summary.counterparties.slice(0, 6).join("; ")}.`);
}
return (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(unresolvedRows.length > 0 ? "medium" : "strong", true));
}
const warehouseLabel = summary.warehouses[0] ?? "не указанного склада";
const directAnswerLine = summary.counterparties.length === 1
? `По складскому остатку ${warehouseLabel} выявлен поставщик: ${summary.counterparties[0]}.`
@ -277,18 +377,96 @@ function composeInventoryReply(intent, rows, options, deps) {
? (0, replyContracts_1.buildFactualListReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(summary.counterparties.length > 0 ? "strong" : "medium", true))
: (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)("medium", false));
}
if (intent === "inventory_profitability_for_item") {
const purchaseRows = rows.filter((row) => deps.isInventoryPurchaseMovement(row));
const saleRows = rows.filter((row) => deps.isInventorySaleMovement(row));
const requestedItemHint = String(options.itemHint ?? "").trim();
const excludedCounterpartyTokens = requestedItemHint ? [requestedItemHint] : [];
const purchaseSummary = deps.summarizeInventoryTraceRows(purchaseRows, excludedCounterpartyTokens);
const saleSummary = deps.summarizeInventoryTraceRows(saleRows, excludedCounterpartyTokens);
const itemLabel = requestedItemHint || purchaseSummary.item || saleSummary.item || "товар не определен";
const revenue = sumInventoryRowAmount(saleRows);
const purchaseCostProxy = sumInventoryRowAmount(purchaseRows);
const spread = revenue - purchaseCostProxy;
const marginPct = revenue > 0 ? (spread / revenue) * 100 : null;
const markupPct = purchaseCostProxy > 0 ? (spread / purchaseCostProxy) * 100 : null;
const saleQuantity = sumInventoryRowQuantity(saleRows);
const purchaseQuantity = sumInventoryRowQuantity(purchaseRows);
const periodLabel = inventoryProfitabilityPeriodLabel(options, deps);
const hasSales = saleRows.length > 0;
const hasPurchases = purchaseRows.length > 0;
const directAnswerLine = hasSales && hasPurchases
? `По товару ${itemLabel} за период ${periodLabel} подтверждена выручка продаж ${deps.formatMoneyRub(revenue)} и закупочный след ${deps.formatMoneyRub(purchaseCostProxy)}; расчетный валовый спред по доступным документам: ${deps.formatMoneyRub(spread)}. Маржинальность к выручке: ${formatInventoryPercent(marginPct, deps.formatNumberWithDots)}.`
: hasSales
? `По товару ${itemLabel} за период ${periodLabel} подтверждена выручка продаж ${deps.formatMoneyRub(revenue)}, но закупочный след в доступных строках не найден; прибыль и маржа не подтверждены.`
: hasPurchases
? `По товару ${itemLabel} за период ${periodLabel} найден закупочный след ${deps.formatMoneyRub(purchaseCostProxy)}, но продажи в доступных строках не найдены; выручка, прибыль и маржа не подтверждены.`
: `По товару ${itemLabel} за период ${periodLabel} не найдено ни продаж, ни закупочного следа в доступных строках 41.01.`;
const lines = [directAnswerLine];
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Расчет:", [
`Строк продаж со счета 41.01: ${deps.formatNumberWithDots(saleRows.length)}.`,
`Строк закупки на счет 41.01: ${deps.formatNumberWithDots(purchaseRows.length)}.`,
`Выручка по документам продажи: ${deps.formatMoneyRub(revenue)}.`,
`Закупочная сумма по доступным документам: ${deps.formatMoneyRub(purchaseCostProxy)}.`,
`Расчетный валовый спред: ${deps.formatMoneyRub(spread)}.`,
`Маржинальность к выручке: ${formatInventoryPercent(marginPct, deps.formatNumberWithDots)}.`,
`Наценка к закупочному следу: ${formatInventoryPercent(markupPct, deps.formatNumberWithDots)}.`
]);
if (saleQuantity > 0 || purchaseQuantity > 0) {
lines.push(`- Количество в продажах: ${deps.formatNumberWithDots(saleQuantity, 3)}; количество в закупках: ${deps.formatNumberWithDots(purchaseQuantity, 3)}.`);
}
if (saleSummary.counterparties.length > 0) {
lines.push(`- Покупатели в продаже: ${saleSummary.counterparties.slice(0, 4).join("; ")}.`);
}
if (purchaseSummary.counterparties.length > 0) {
lines.push(`- Поставщики в закупочном следе: ${purchaseSummary.counterparties.slice(0, 4).join("; ")}.`);
}
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [
"Это не чистая прибыль компании и не бухгалтерский финансовый результат.",
"Закупочная сумма является proxy по найденным документам поступления; без партионного/управленческого учета нельзя доказать точную себестоимость конкретной продажи.",
"Если продажи и закупки попали в разные периоды или разные организации/склады, вывод нужно читать как документальный срез по доступной выборке, а не как полный P&L."
]);
if (saleRows.length > 0) {
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Продажи:", [
`- Первая дата продажи: ${deps.inventoryTraceDateLabel(saleSummary.firstPeriod)}.`,
`- Последняя дата продажи: ${deps.inventoryTraceDateLabel(saleSummary.lastPeriod)}.`,
...deps.formatInventoryTraceRows(saleRows, 8, [itemLabel])
]);
}
if (purchaseRows.length > 0) {
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Закупки:", [
`- Первая дата закупки: ${deps.inventoryTraceDateLabel(purchaseSummary.firstPeriod)}.`,
`- Последняя дата закупки: ${deps.inventoryTraceDateLabel(purchaseSummary.lastPeriod)}.`,
...deps.formatInventoryTraceRows(purchaseRows, 8, [itemLabel])
]);
}
return (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(hasSales && hasPurchases ? "strong" : hasSales || hasPurchases ? "medium" : "weak", hasSales || hasPurchases));
}
if (intent === "inventory_purchase_to_sale_chain") {
const purchaseRows = rows.filter((row) => deps.isInventoryPurchaseMovement(row));
const saleRows = rows.filter((row) => deps.isInventorySaleMovement(row));
const purchaseSummary = deps.summarizeInventoryTraceRows(purchaseRows);
const saleSummary = deps.summarizeInventoryTraceRows(saleRows);
const itemLabel = purchaseSummary.item ?? saleSummary.item ?? "товар не определен";
const directAnswerLine = purchaseSummary.counterparties.length === 1 && saleSummary.counterparties.length === 1
? `По товару ${itemLabel} цепочка поставки и продажи связана с поставщиком ${purchaseSummary.counterparties[0]} и покупателем ${saleSummary.counterparties[0]}.`
: `По товару ${itemLabel} цепочка поставки и продажи подтверждена частично или разнообразно: детали идут следом.`;
const requestedSupplier = extractRequestedInventoryParty(options.userMessage, "supplier");
const requestedBuyer = extractRequestedInventoryParty(options.userMessage, "buyer");
const supplierMatches = inventoryRequestedPartyMatches(requestedSupplier, purchaseSummary.counterparties);
const buyerMatches = inventoryRequestedPartyMatches(requestedBuyer, saleSummary.counterparties);
const mismatchParts = [];
if (requestedSupplier && purchaseRows.length > 0 && !supplierMatches) {
mismatchParts.push(`запрошенный поставщик ${requestedSupplier} не совпал с найденным поставщиком: ${inventoryPartyListOrUnknown(purchaseSummary.counterparties)}`);
}
if (requestedBuyer && saleRows.length > 0 && !buyerMatches) {
mismatchParts.push(`запрошенный покупатель ${requestedBuyer} не совпал с найденным покупателем: ${inventoryPartyListOrUnknown(saleSummary.counterparties)}`);
}
const directAnswerLine = mismatchParts.length > 0
? `Запрошенная цепочка по товару ${itemLabel} полностью не подтверждена: ${mismatchParts.join("; ")}.`
: purchaseSummary.counterparties.length === 1 && saleSummary.counterparties.length === 1
? `По товару ${itemLabel} цепочка поставки и продажи связана с поставщиком ${purchaseSummary.counterparties[0]} и покупателем ${saleSummary.counterparties[0]}.`
: `По товару ${itemLabel} цепочка поставки и продажи подтверждена частично или разнообразно: детали идут следом.`;
const lines = [directAnswerLine, "", "Подтверждение:"];
lines.push(`- Закупочных движений на 41.01: ${deps.formatNumberWithDots(purchaseRows.length)}.`);
lines.push(`- Движений выбытия со счета 41.01: ${deps.formatNumberWithDots(saleRows.length)}.`);
lines.push(`- Строк закупки на 41.01: ${deps.formatNumberWithDots(purchaseRows.length)}.`);
lines.push(`- Строк продажи со счета 41.01: ${deps.formatNumberWithDots(saleRows.length)}.`);
if (purchaseRows.length > 0 && saleRows.length > 0) {
lines.push("- В доступных данных найдены обе стороны цепочки: поступление и последующее выбытие.");
}

View File

@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.ASSISTANT_MCP_CATALOG_PLAN_REVIEW_SCHEMA_VERSION = exports.ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION = void 0;
exports.searchAssistantMcpCatalogPrimitivesByDecompositionCandidates = searchAssistantMcpCatalogPrimitivesByDecompositionCandidates;
exports.searchAssistantMcpCatalogPrimitivesByFactAxis = searchAssistantMcpCatalogPrimitivesByFactAxis;
exports.searchAssistantMcpCatalogChainTemplatesByFactAxis = searchAssistantMcpCatalogChainTemplatesByFactAxis;
exports.searchAssistantMcpCatalogPrimitivesByMetadataSurface = searchAssistantMcpCatalogPrimitivesByMetadataSurface;
exports.buildAssistantMcpCatalogIndex = buildAssistantMcpCatalogIndex;
exports.getAssistantMcpCatalogPrimitive = getAssistantMcpCatalogPrimitive;
exports.getAssistantMcpCatalogChainTemplate = getAssistantMcpCatalogChainTemplate;
exports.reviewAssistantMcpDiscoveryPlanAgainstCatalog = reviewAssistantMcpDiscoveryPlanAgainstCatalog;
const assistantMcpDiscoveryPolicy_1 = require("./assistantMcpDiscoveryPolicy");
exports.ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION = "assistant_mcp_catalog_index_v1";
@ -43,8 +45,30 @@ const PRIMITIVE_CONTRACTS = [
primitive_id: "resolve_entity_reference",
purpose: "Resolve a user-visible entity name to a concrete 1C reference candidate.",
decomposition_hints: ["resolve_entity_reference"],
supported_fact_families: ["entity_grounding", "value_flow", "document_evidence", "movement_evidence", "activity_lifecycle"],
supported_action_families: ["search_business_entity", "turnover", "payout", "net_value_flow", "list_documents", "list_movements", "activity_duration"],
supported_fact_families: [
"entity_grounding",
"value_flow",
"document_evidence",
"movement_evidence",
"activity_lifecycle",
"business_overview",
"inventory_purchase_provenance",
"inventory_sale_trace",
"inventory_supplier_overlap"
],
supported_action_families: [
"search_business_entity",
"turnover",
"payout",
"net_value_flow",
"list_documents",
"list_movements",
"activity_duration",
"broad_evaluation",
"purchase_provenance",
"sale_trace",
"supplier_overlap"
],
planning_tags: ["subject_resolution"],
required_axes_any_of: [["business_entity"], ["counterparty"], ["organization"], ["contract"], ["item"]],
optional_axes: ["period", "inn", "document"],
@ -62,18 +86,35 @@ const PRIMITIVE_CONTRACTS = [
"collect_outgoing_movements",
"fetch_scoped_movements"
],
supported_fact_families: ["value_flow", "movement_evidence"],
supported_action_families: ["turnover", "payout", "net_value_flow", "list_movements"],
supported_fact_families: [
"value_flow",
"movement_evidence",
"business_overview",
"inventory_stock_snapshot",
"inventory_supplier_overlap"
],
supported_action_families: [
"turnover",
"payout",
"net_value_flow",
"list_movements",
"broad_evaluation",
"stock_snapshot",
"supplier_overlap"
],
planning_tags: ["movement", "comparison", "ranking", "aggregation", "monthly_aggregation"],
required_axes_any_of: [
["period", "account"],
["period", "counterparty"],
["period", "organization"],
["as_of_date", "organization"],
["as_of_date", "warehouse"],
["as_of_date", "item"],
["all_time_scope", "counterparty"],
["all_time_scope", "organization"]
],
optional_axes: ["contract", "document", "amount", "item", "warehouse"],
output_fact_kinds: ["movement_rows", "turnover", "balance_delta"],
optional_axes: ["contract", "document", "amount", "item", "warehouse", "quantity", "supplier"],
output_fact_kinds: ["movement_rows", "turnover", "balance_delta", "stock_rows", "stock_quantity"],
evidence_floor: "rows_matched",
safe_for_model_planning: true,
runtime_must_execute: true
@ -82,19 +123,35 @@ const PRIMITIVE_CONTRACTS = [
primitive_id: "query_documents",
purpose: "Fetch documents related to a scoped entity, period, contract, or movement explanation.",
decomposition_hints: ["fetch_scoped_documents", "fetch_supporting_documents"],
supported_fact_families: ["document_evidence", "activity_lifecycle"],
supported_action_families: ["list_documents", "activity_duration"],
supported_fact_families: [
"document_evidence",
"activity_lifecycle",
"business_overview",
"inventory_purchase_provenance",
"inventory_sale_trace",
"inventory_supplier_overlap"
],
supported_action_families: [
"list_documents",
"activity_duration",
"broad_evaluation",
"purchase_provenance",
"sale_trace",
"supplier_overlap"
],
planning_tags: ["document"],
required_axes_any_of: [
["document"],
["item"],
["supplier"],
["counterparty"],
["contract"],
["period", "organization"],
["all_time_scope", "counterparty"],
["all_time_scope", "organization"]
],
optional_axes: ["account", "amount", "item", "warehouse"],
output_fact_kinds: ["document_rows", "document_dates", "document_amounts"],
optional_axes: ["account", "amount", "item", "warehouse", "as_of_date", "supplier", "buyer"],
output_fact_kinds: ["document_rows", "document_dates", "document_amounts", "purchase_documents", "sale_documents"],
evidence_floor: "rows_matched",
safe_for_model_planning: true,
runtime_must_execute: true
@ -103,18 +160,19 @@ const PRIMITIVE_CONTRACTS = [
primitive_id: "aggregate_by_axis",
purpose: "Aggregate already-scoped 1C evidence by a business axis such as counterparty, contract, or period.",
decomposition_hints: ["aggregate_checked_amounts", "aggregate_ranked_axis_values", "aggregate_by_month"],
supported_fact_families: ["value_flow"],
supported_action_families: ["turnover", "payout", "net_value_flow"],
supported_fact_families: ["value_flow", "business_overview", "inventory_stock_snapshot", "inventory_supplier_overlap"],
supported_action_families: ["turnover", "payout", "net_value_flow", "broad_evaluation", "stock_snapshot", "supplier_overlap"],
planning_tags: ["aggregation", "ranking", "monthly_aggregation"],
required_axes_any_of: [
["aggregate_axis", "period"],
["aggregate_axis", "as_of_date"],
["aggregate_axis", "counterparty"],
["aggregate_axis", "account"],
["aggregate_axis", "counterparty", "all_time_scope"],
["aggregate_axis", "organization", "all_time_scope"]
],
optional_axes: ["organization", "contract", "document", "amount"],
output_fact_kinds: ["aggregate_totals", "ranked_axis_values"],
optional_axes: ["organization", "contract", "document", "amount", "quantity", "item", "warehouse", "supplier"],
output_fact_kinds: ["aggregate_totals", "ranked_axis_values", "stock_totals", "item_totals"],
evidence_floor: "rows_matched",
safe_for_model_planning: true,
runtime_must_execute: true
@ -123,12 +181,12 @@ const PRIMITIVE_CONTRACTS = [
primitive_id: "drilldown_related_objects",
purpose: "Drill from a known entity or document into related contracts, documents, movements, or payments.",
decomposition_hints: ["drilldown_related_objects"],
supported_fact_families: [],
supported_action_families: [],
planning_tags: ["drilldown"],
required_axes_any_of: [["business_entity"], ["document"], ["contract"], ["counterparty"]],
optional_axes: ["period", "account", "amount"],
output_fact_kinds: ["related_objects", "relationship_edges"],
supported_fact_families: ["inventory_purchase_provenance", "inventory_sale_trace", "inventory_supplier_overlap"],
supported_action_families: ["purchase_provenance", "sale_trace", "supplier_overlap", "purchase_to_sale_chain"],
planning_tags: ["drilldown", "inventory"],
required_axes_any_of: [["business_entity"], ["document"], ["contract"], ["counterparty"], ["item"], ["supplier"]],
optional_axes: ["period", "account", "amount", "warehouse", "as_of_date", "buyer"],
output_fact_kinds: ["related_objects", "relationship_edges", "supplier_item_edges", "buyer_item_edges"],
evidence_floor: "rows_received",
safe_for_model_planning: true,
runtime_must_execute: true
@ -143,7 +201,12 @@ const PRIMITIVE_CONTRACTS = [
"value_flow",
"document_evidence",
"movement_evidence",
"activity_lifecycle"
"activity_lifecycle",
"business_overview",
"inventory_stock_snapshot",
"inventory_purchase_provenance",
"inventory_sale_trace",
"inventory_supplier_overlap"
],
supported_action_families: [
"inspect_catalog",
@ -157,11 +220,17 @@ const PRIMITIVE_CONTRACTS = [
"net_value_flow",
"list_documents",
"list_movements",
"activity_duration"
"activity_duration",
"broad_evaluation",
"stock_snapshot",
"purchase_provenance",
"sale_trace",
"supplier_overlap",
"purchase_to_sale_chain"
],
planning_tags: ["coverage"],
required_axes_any_of: [["coverage_target"], ["domain_family"], ["primitive_id"]],
optional_axes: ["period", "organization", "counterparty", "document", "account"],
optional_axes: ["period", "organization", "counterparty", "document", "account", "item", "warehouse", "as_of_date"],
output_fact_kinds: ["coverage_status", "known_gaps"],
evidence_floor: "source_summary",
safe_for_model_planning: true,
@ -171,11 +240,26 @@ const PRIMITIVE_CONTRACTS = [
primitive_id: "explain_evidence_basis",
purpose: "Produce a machine-readable explanation of which checked MCP evidence supports, limits, or fails the answer.",
decomposition_hints: ["explain_evidence_basis"],
supported_fact_families: ["activity_lifecycle"],
supported_action_families: ["activity_duration"],
planning_tags: ["explanation"],
supported_fact_families: [
"activity_lifecycle",
"business_overview",
"inventory_stock_snapshot",
"inventory_purchase_provenance",
"inventory_sale_trace",
"inventory_supplier_overlap"
],
supported_action_families: [
"activity_duration",
"broad_evaluation",
"stock_snapshot",
"purchase_provenance",
"sale_trace",
"supplier_overlap",
"purchase_to_sale_chain"
],
planning_tags: ["explanation", "inventory", "business_overview", "bounded_inference"],
required_axes_any_of: [["evidence_basis"], ["primitive_id"], ["source_rows_summary"]],
optional_axes: ["coverage_target", "domain_family"],
optional_axes: ["coverage_target", "domain_family", "item", "warehouse", "as_of_date", "supplier", "buyer"],
output_fact_kinds: ["confirmed_facts", "inferred_facts", "unknown_facts"],
evidence_floor: "source_summary",
safe_for_model_planning: true,
@ -183,6 +267,202 @@ const PRIMITIVE_CONTRACTS = [
}
];
const PRIMITIVE_CONTRACT_MAP = new Map(PRIMITIVE_CONTRACTS.map((contract) => [contract.primitive_id, contract]));
const CHAIN_TEMPLATES = [
{
chain_id: "metadata_inspection",
semantic_data_need: "1C metadata evidence",
chain_summary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.",
fallback_primitives: ["inspect_1c_metadata"],
base_required_axes: ["metadata_scope"],
supported_fact_families: ["schema_surface"],
supported_action_families: ["inspect_catalog", "inspect_documents", "inspect_registers", "inspect_fields", "inspect_surface"],
planning_tags: ["metadata", "surface_inspection"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "catalog_drilldown",
semantic_data_need: "catalog drilldown metadata evidence",
chain_summary: "Drill deeper into the confirmed catalog-oriented metadata surface, inspect related metadata objects, and keep the next safe lane grounded in checked schema evidence.",
fallback_primitives: ["inspect_1c_metadata"],
base_required_axes: ["metadata_scope"],
supported_fact_families: ["schema_surface"],
supported_action_families: ["inspect_catalog", "inspect_surface"],
planning_tags: ["metadata", "surface_inspection", "drilldown"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "entity_resolution",
semantic_data_need: "entity discovery evidence",
chain_summary: "Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.",
fallback_primitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"],
base_required_axes: ["business_entity", "coverage_target"],
supported_fact_families: ["entity_grounding"],
supported_action_families: ["search_business_entity"],
planning_tags: ["subject_resolution", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "document_evidence",
semantic_data_need: "document evidence",
chain_summary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.",
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
base_required_axes: ["coverage_target"],
supported_fact_families: ["document_evidence", "activity_lifecycle"],
supported_action_families: ["list_documents", "activity_duration"],
planning_tags: ["document", "subject_resolution", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "movement_evidence",
semantic_data_need: "movement evidence",
chain_summary: "Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.",
fallback_primitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
base_required_axes: ["coverage_target"],
supported_fact_families: ["movement_evidence", "value_flow"],
supported_action_families: ["list_movements", "turnover", "payout", "net_value_flow"],
planning_tags: ["movement", "subject_resolution", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "inventory_stock_snapshot",
semantic_data_need: "inventory stock snapshot evidence",
chain_summary: "Fetch checked inventory movement or balance rows for the requested as-of date, aggregate item quantities, then probe coverage and explain stock-snapshot limits before answering.",
fallback_primitives: ["query_movements", "aggregate_by_axis", "probe_coverage", "explain_evidence_basis"],
base_required_axes: ["as_of_date", "organization", "warehouse", "aggregate_axis", "quantity", "coverage_target", "evidence_basis"],
supported_fact_families: ["inventory_stock_snapshot"],
supported_action_families: ["stock_snapshot"],
planning_tags: ["inventory", "movement", "aggregation", "coverage", "explanation"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "inventory_supplier_overlap",
semantic_data_need: "inventory supplier overlap evidence",
chain_summary: "Ground the supplier or item anchor, fetch checked stock and supporting purchase evidence for the requested slice, aggregate item overlap, then state supplier-link limits explicitly.",
fallback_primitives: [
"resolve_entity_reference",
"query_movements",
"query_documents",
"aggregate_by_axis",
"probe_coverage",
"explain_evidence_basis"
],
base_required_axes: ["supplier", "item", "warehouse", "as_of_date", "aggregate_axis", "coverage_target", "evidence_basis"],
supported_fact_families: ["inventory_supplier_overlap"],
supported_action_families: ["supplier_overlap"],
planning_tags: ["inventory", "subject_resolution", "movement", "document", "aggregation", "coverage", "explanation"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "inventory_purchase_provenance",
semantic_data_need: "inventory purchase provenance evidence",
chain_summary: "Ground the selected item, fetch checked purchase documents, drill into related supplier evidence, then probe coverage before stating purchase provenance.",
fallback_primitives: [
"resolve_entity_reference",
"query_documents",
"drilldown_related_objects",
"probe_coverage",
"explain_evidence_basis"
],
base_required_axes: ["item", "document", "supplier", "coverage_target", "evidence_basis"],
supported_fact_families: ["inventory_purchase_provenance"],
supported_action_families: ["purchase_provenance"],
planning_tags: ["inventory", "document", "drilldown", "subject_resolution", "coverage", "explanation"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "inventory_sale_trace",
semantic_data_need: "inventory sale trace evidence",
chain_summary: "Ground the selected item, fetch checked sale documents, drill into related buyer evidence, then probe coverage before stating the sale or purchase-to-sale trace.",
fallback_primitives: [
"resolve_entity_reference",
"query_documents",
"drilldown_related_objects",
"probe_coverage",
"explain_evidence_basis"
],
base_required_axes: ["item", "document", "buyer", "coverage_target", "evidence_basis"],
supported_fact_families: ["inventory_sale_trace"],
supported_action_families: ["sale_trace", "purchase_to_sale_chain"],
planning_tags: ["inventory", "document", "drilldown", "subject_resolution", "coverage", "explanation"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "value_flow",
semantic_data_need: "counterparty value-flow evidence",
chain_summary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.",
fallback_primitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"],
base_required_axes: ["aggregate_axis", "amount", "coverage_target"],
supported_fact_families: ["value_flow"],
supported_action_families: ["turnover", "payout", "net_value_flow"],
planning_tags: ["movement", "aggregation", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "value_flow_comparison",
semantic_data_need: "bidirectional value-flow comparison evidence",
chain_summary: "Query incoming and outgoing movements for the checked period and organization, compare the checked sides, and probe coverage before answering a bounded comparison.",
fallback_primitives: ["query_movements", "probe_coverage"],
base_required_axes: ["amount", "coverage_target"],
supported_fact_families: ["value_flow"],
supported_action_families: ["net_value_flow"],
planning_tags: ["movement", "comparison", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "value_flow_ranking",
semantic_data_need: "ranked value-flow evidence",
chain_summary: "Query scoped movements for the checked period and organization, aggregate checked amounts by counterparty, then probe coverage before answering a bounded ranking.",
fallback_primitives: ["query_movements", "aggregate_by_axis", "probe_coverage"],
base_required_axes: ["aggregate_axis", "amount", "coverage_target"],
supported_fact_families: ["value_flow"],
supported_action_families: ["turnover", "payout"],
planning_tags: ["movement", "ranking", "aggregation", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "lifecycle",
semantic_data_need: "counterparty lifecycle evidence with bounded activity-window inference",
chain_summary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window without presenting it as legal registration age.",
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
base_required_axes: ["document_date", "coverage_target", "evidence_basis", "activity_window", "legal_fact_boundary"],
supported_fact_families: ["activity_lifecycle"],
supported_action_families: ["activity_duration"],
planning_tags: ["document", "explanation", "coverage", "bounded_inference", "activity_window", "legal_fact_boundary"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "business_overview",
semantic_data_need: "business overview evidence with bounded analyst interpretation",
chain_summary: "Collect organization-scoped movement and document evidence, aggregate checked business signals, probe coverage, and explain profit/margin limits before giving a bounded analyst overview.",
fallback_primitives: [
"query_movements",
"aggregate_by_axis",
"query_documents",
"probe_coverage",
"explain_evidence_basis"
],
base_required_axes: ["organization", "aggregate_axis", "amount", "coverage_target", "evidence_basis", "profit_margin_boundary"],
supported_fact_families: ["business_overview"],
supported_action_families: ["broad_evaluation"],
planning_tags: ["business_overview", "movement", "document", "aggregation", "coverage", "explanation", "bounded_inference"],
safe_for_model_planning: true,
requires_evidence_gate: true
}
];
const CHAIN_TEMPLATE_MAP = new Map(CHAIN_TEMPLATES.map((template) => [template.chain_id, template]));
function toStringSet(values) {
return new Set(values.map((item) => item.trim()).filter((item) => item.length > 0));
}
@ -243,6 +523,32 @@ function tagSetFromFactAxisInput(input) {
if (input.business_fact_family === "value_flow") {
tags.add("movement");
}
if (input.business_fact_family === "business_overview") {
tags.add("business_overview");
tags.add("movement");
tags.add("document");
tags.add("aggregation");
tags.add("coverage");
tags.add("explanation");
tags.add("bounded_inference");
}
if (input.business_fact_family?.startsWith("inventory_")) {
tags.add("inventory");
}
if (input.business_fact_family === "inventory_stock_snapshot") {
tags.add("movement");
tags.add("aggregation");
}
if (input.business_fact_family === "inventory_supplier_overlap") {
tags.add("movement");
tags.add("document");
tags.add("aggregation");
}
if (input.business_fact_family === "inventory_purchase_provenance" ||
input.business_fact_family === "inventory_sale_trace") {
tags.add("document");
tags.add("drilldown");
}
if (input.comparison_need) {
tags.add("comparison");
}
@ -371,9 +677,7 @@ function searchAssistantMcpCatalogPrimitivesByDecompositionCandidates(input) {
continue;
}
for (const contract of PRIMITIVE_CONTRACTS) {
if (contract.primitive_id === "aggregate_by_axis" &&
normalizedCandidate === "aggregate_by_month" &&
!allowAggregateByAxis) {
if (contract.primitive_id === "aggregate_by_axis" && !allowAggregateByAxis) {
continue;
}
if (!contract.decomposition_hints.includes(normalizedCandidate)) {
@ -416,6 +720,9 @@ function searchAssistantMcpCatalogPrimitivesByFactAxis(input) {
if (contract.primitive_id === "explain_evidence_basis" && !desiredTags.has("explanation")) {
continue;
}
if (contract.primitive_id === "drilldown_related_objects" && !desiredTags.has("drilldown")) {
continue;
}
if (!factMatch && !actionMatch && tagMatches.length <= 0) {
continue;
}
@ -447,6 +754,53 @@ function searchAssistantMcpCatalogPrimitivesByFactAxis(input) {
.sort((left, right) => right.score - left.score)
.map((item) => item.primitive);
}
function searchAssistantMcpCatalogChainTemplatesByFactAxis(input) {
const requiredAxisSet = toStringSet(input.required_axes ?? []);
const desiredTags = tagSetFromFactAxisInput({
business_fact_family: input.business_fact_family,
action_family: input.action_family,
required_axes: input.required_axes,
comparison_need: input.comparison_need,
ranking_need: input.ranking_need,
aggregation_need: input.aggregation_need
});
const scored = [];
for (const template of CHAIN_TEMPLATES) {
const factMatch = matchesPlanningToken(input.business_fact_family, template.supported_fact_families);
const actionMatch = matchesPlanningToken(input.action_family, template.supported_action_families);
const tagMatches = template.planning_tags.filter((tag) => desiredTags.has(normalizePlanningToken(tag)));
const axisOverlap = template.base_required_axes.filter((axis) => requiredAxisSet.has(axis)).length;
let score = 0;
if (factMatch) {
score += 8;
}
if (actionMatch) {
score += 5;
}
score += tagMatches.length * 2;
score += axisOverlap;
if (input.comparison_need && template.planning_tags.some((tag) => normalizePlanningToken(tag) === "comparison")) {
score += 6;
}
if (input.ranking_need && template.planning_tags.some((tag) => normalizePlanningToken(tag) === "ranking")) {
score += 6;
}
if (input.aggregation_need === "by_month" &&
template.planning_tags.some((tag) => normalizePlanningToken(tag) === "monthly_aggregation")) {
score += 4;
}
if (score <= 0) {
continue;
}
scored.push({
chainId: template.chain_id,
score
});
}
return scored
.sort((left, right) => right.score - left.score)
.map((item) => item.chainId);
}
function searchAssistantMcpCatalogPrimitivesByMetadataSurface(input) {
const allowAggregateByAxis = input.allow_aggregate_by_axis !== false;
const requiredAxisSet = toStringSet(input.required_axes ?? []);
@ -516,10 +870,18 @@ function buildAssistantMcpCatalogIndex() {
else {
pushReason(reasonCodes, "catalog_covers_all_discovery_primitives");
}
const unknownChainPrimitives = CHAIN_TEMPLATES.flatMap((template) => template.fallback_primitives.filter((primitive) => !PRIMITIVE_CONTRACT_MAP.has(primitive)));
if (unknownChainPrimitives.length > 0) {
pushReason(reasonCodes, "catalog_chain_template_references_unknown_primitive");
}
else {
pushReason(reasonCodes, "catalog_chain_templates_reference_reviewed_primitives");
}
return {
schema_version: exports.ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION,
policy_owner: "assistantMcpCatalogIndex",
primitives: PRIMITIVE_CONTRACTS,
chain_templates: CHAIN_TEMPLATES,
reason_codes: reasonCodes
};
}
@ -530,6 +892,13 @@ function getAssistantMcpCatalogPrimitive(primitive) {
}
return contract;
}
function getAssistantMcpCatalogChainTemplate(chainId) {
const template = CHAIN_TEMPLATE_MAP.get(chainId);
if (!template) {
throw new Error(`Missing MCP catalog chain template: ${chainId}`);
}
return template;
}
function reviewAssistantMcpDiscoveryPlanAgainstCatalog(plan) {
const reasonCodes = [];
const axisSet = toStringSet(plan.required_axes);

View File

@ -35,7 +35,12 @@ function formatNamedChoiceList(values) {
}
function isInternalMechanicsLine(value) {
const text = value.toLowerCase();
return (text.includes("primitive") ||
return (text.includes("mcp fetch failed") ||
text.includes("this operation was aborted") ||
text.includes("entity-resolution") ||
text.includes("could not continue") ||
text.includes("checked catalog search step") ||
text.includes("primitive") ||
text.includes("query_documents") ||
text.includes("query_movements") ||
text.includes("resolve_entity_reference") ||
@ -46,8 +51,19 @@ function isInternalMechanicsLine(value) {
text.includes("runtime_") ||
text.includes("planner_") ||
text.includes("catalog_") ||
text.includes("scope is not implemented yet") ||
text.includes("needs more scope before execution") ||
text.includes("mcp_execution_performed"));
text.includes("mcp_execution_performed")
|| text.includes("confirmed 1c metadata surface")
|| text.includes("metadata surface family scores")
|| text.includes("available metadata object sets")
|| text.includes("selected metadata"));
}
function isMcpTransportFailureLine(value) {
const text = value.toLowerCase();
return (text.includes("mcp fetch failed") ||
text.includes("this operation was aborted") ||
text.includes("operation was aborted"));
}
function userFacingUnknowns(values) {
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
@ -61,7 +77,20 @@ function rankedValueFlowUnknownLines(pilot) {
return [`Полный рейтинг контрагентов вне ${period} этим поиском не подтвержден.`];
}
function userFacingLimitations(values) {
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
const result = [];
for (const value of uniqueStrings(values)) {
if (isMcpTransportFailureLine(value)) {
const line = "Доступ к 1С во время проверки оборвался; подтвержденные строки не получены.";
if (!result.includes(line)) {
result.push(line);
}
continue;
}
if (!isInternalMechanicsLine(value)) {
result.push(value);
}
}
return result;
}
function modeFor(pilot) {
if (pilot.pilot_status === "blocked") {
@ -88,6 +117,9 @@ function isValueFlowPilot(pilot) {
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" ||
pilot.pilot_scope === "counterparty_bidirectional_value_flow_query_movements_v1");
}
function isBusinessOverviewPilot(pilot) {
return pilot.pilot_scope === "business_overview_route_template_v1";
}
function isDocumentPilot(pilot) {
return pilot.pilot_scope === "counterparty_document_evidence_query_documents_v1";
}
@ -97,6 +129,9 @@ function isMovementPilot(pilot) {
function isMetadataPilot(pilot) {
return pilot.pilot_scope === "metadata_inspection_v1";
}
function isInventoryTemplatePilot(pilot) {
return pilot.pilot_scope === "inventory_route_template_v1";
}
function isCatalogDrilldownPilot(pilot) {
return (isMetadataPilot(pilot) &&
(pilot.reason_codes.includes("planner_selected_catalog_drilldown_from_confirmed_metadata_surface_ref") ||
@ -286,12 +321,129 @@ function metadataRouteFamilyLabelRu(routeFamily) {
}
return null;
}
function businessOverviewInventoryUnknownLabel(overview) {
if (overview.inventory_staleness_risk_proxy) {
return "резервы/списания/ликвидационная стоимость склада";
}
if (overview.inventory_turnover_proxy) {
return "FIFO-оборачиваемость/подтвержденная складская ликвидность";
}
if (overview.inventory_position) {
return "полноценная складская ликвидность";
}
return "склад";
}
function businessOverviewNextStepLine(overview) {
const missing = new Set(overview.missing_signal_families);
const checks = [];
if (missing.has("profit_or_clean_margin")) {
checks.push("чистую прибыль через себестоимость продаж, расходы и закрытие периода");
}
else if (missing.has("profit_or_margin")) {
checks.push("прибыль/маржу по проверенному финрезультату");
}
if (missing.has("tax_position")) {
checks.push("НДС/налоговую позицию за явный период");
}
if (missing.has("debt_due_date_aging_quality")) {
checks.push("договорные сроки оплаты и due-date aging");
}
else if (missing.has("debt_open_settlement_quality")) {
checks.push("качество открытых расчетов");
}
else if (missing.has("debt_position")) {
checks.push("долговой срез на дату");
}
if (missing.has("inventory_reserve_liquidation_quality")) {
checks.push("резервы, списания, неликвидность и ликвидационную стоимость склада");
}
else if (missing.has("inventory_liquidity_quality")) {
checks.push("FIFO-оборачиваемость и подтвержденную складскую ликвидность");
}
else if (missing.has("inventory_turnover_quality")) {
checks.push("скорость продаж и оборачиваемость склада");
}
else if (missing.has("inventory_position")) {
checks.push("складской остаток на дату");
}
const target = checks.length > 0
? checks.join("; ")
: "оставшиеся непроверенные области по выбранному контуру";
return `Следующий шаг для полного бизнес-аудита: отдельно проверить ${target}, не смешивая эти будущие проверки с уже подтвержденным обзором.`;
}
function headlineFor(mode, pilot) {
const askedMonthlyBreakdown = pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" ||
pilot.derived_value_flow?.aggregation_axis === "month";
if (isEntityResolutionPilot(pilot) && mode === "confirmed_with_bounded_inference") {
return "По каталогу 1С найден вероятный контрагент; это заземление сущности для следующего шага, а не еще бизнес-ответ по данным.";
}
if (isInventoryTemplatePilot(pilot) && mode === "confirmed_with_bounded_inference") {
return "По exact inventory runtime в 1С найдены подтвержденные строки; ответ ограничен проверенным складским/товарным срезом.";
}
if (isInventoryTemplatePilot(pilot) && mode === "checked_sources_only") {
if (pilot.mcp_execution_performed) {
return "Exact inventory runtime был проверен, но подтвержденный складской/товарный факт в найденных строках не получен.";
}
return "Инвентарный route-template уже выбран, но live-исполнение этого generic MCP контура еще не подключено; складской/товарный факт не подтвержден.";
}
if (isBusinessOverviewPilot(pilot) && pilot.derived_business_overview && mode === "confirmed_with_bounded_inference") {
const overview = pilot.derived_business_overview;
const families = [];
if (overview.incoming_customer_revenue.rows_with_amount > 0 ||
overview.outgoing_supplier_payout.rows_with_amount > 0) {
families.push("денежный поток");
}
if (overview.yearly_breakdown?.length) {
families.push("годовая operating-flow динамика");
}
if (overview.activity_period) {
families.push("активность");
}
if (overview.tax_position) {
families.push("НДС-позиция");
}
if (overview.trading_margin_proxy) {
families.push("торговый margin proxy");
}
if (overview.debt_position) {
families.push("долговой срез на дату");
}
if (overview.debt_open_settlement_quality) {
families.push("качество открытых расчетов");
if (overview.debt_open_settlement_quality.age_signal) {
families.push("возрастной сигнал открытых расчетов");
}
}
if (overview.debt_staleness_risk_proxy) {
families.push("staleness risk proxy открытых расчетов");
}
if (overview.inventory_position) {
families.push("складской срез на дату");
}
if (overview.inventory_turnover_proxy) {
families.push("оборотный proxy склада");
}
if (overview.inventory_staleness_risk_proxy) {
families.push("staleness risk proxy склада");
}
const unknownFamilies = [overview.trading_margin_proxy ? "чистая прибыль/точная маржа" : "прибыль/маржа"];
if (!overview.tax_position) {
unknownFamilies.push("НДС");
}
if (!overview.debt_position) {
unknownFamilies.push("долговой срез");
}
unknownFamilies.push(overview.debt_staleness_risk_proxy
? "договорные сроки оплаты/due-date просрочка"
: overview.debt_open_settlement_quality
? "due-date просрочка"
: "качество открытых расчетов");
unknownFamilies.push(businessOverviewInventoryUnknownLabel(overview));
return `По данным 1С собран ограниченный бизнес-обзор: ${families.join(", ")} подтверждены найденными строками; ${unknownFamilies.join(", ")} остаются отдельными непроверенными областями.`;
}
if (isBusinessOverviewPilot(pilot) && mode === "checked_sources_only") {
return "Бизнес-обзор был запущен, но подтвержденные денежные или activity-сигналы в найденных строках не получены.";
}
if (isEntityResolutionPilot(pilot) && mode === "needs_clarification") {
return "По каталогу 1С нашлось несколько похожих контрагентов, и без уточнения нельзя честно выбрать правильную сущность.";
}
@ -307,16 +459,16 @@ function headlineFor(mode, pilot) {
return `По движениям${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
}
if (isCatalogDrilldownPilot(pilot) && mode === "confirmed_with_bounded_inference") {
return "По метаданным 1С удалось углубиться в контур справочников и связанных объектов; это уже не общий обзор схемы, а следующий безопасный catalog drilldown.";
return "По схеме 1С удалось углубиться в контур справочников и связанных объектов; это следующий безопасный шаг по проверенной схеме, а не бизнес-обороты.";
}
if (pilot.derived_metadata_surface && mode === "confirmed_with_bounded_inference") {
if (pilot.derived_metadata_surface.ambiguity_detected) {
return "По метаданным 1С найдены конкурирующие schema-поверхности; перед следующим шагом нужно удержать неоднозначность явно.";
return "По схеме 1С найдены несколько конкурирующих контуров; перед следующим шагом нужно явно выбрать нужный тип данных.";
}
if (pilot.derived_metadata_surface.downstream_route_family) {
return "По метаданным 1С найдена схема и заземлена вероятная поверхность для следующего безопасного шага.";
return "По схеме 1С найдены подходящие объекты; можно безопасно выбрать следующий контур проверки.";
}
return "По метаданным 1С найдена доступная схема для дальнейшего безопасного поиска.";
return "По схеме 1С найдены доступные объекты для дальнейшего безопасного поиска.";
}
if (askedMonthlyBreakdown && pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто и помесячная раскладка могут называться только как расчет по найденным строкам и проверенному периоду.";
@ -355,7 +507,7 @@ function headlineFor(mode, pilot) {
return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С.";
}
if (mode === "needs_clarification" && isMetadataLaneChoiceClarification(pilot)) {
return "По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя.";
return "По проверенной схеме 1С видно несколько возможных контуров, и без явного выбора дальше идти нельзя.";
}
if (mode === "needs_clarification" && isMovementLaneClarification(pilot)) {
const need = clarificationNeedRu(pilot);
@ -422,18 +574,29 @@ function nextStepFor(mode, pilot) {
if (mode === "needs_clarification") {
return "Уточните контрагента, период или организацию, и я смогу выполнить проверку по 1С.";
}
if (mode === "checked_sources_only" && isInventoryTemplatePilot(pilot)) {
if (pilot.mcp_execution_performed) {
return "Можно уточнить дату, организацию, склад, поставщика или позицию и повторить exact inventory проверку.";
}
return "Следующий шаг - связать inventory route-template с exact inventory runtime и затем проверить live-прогоном.";
}
if (mode === "confirmed_with_bounded_inference" && isBusinessOverviewPilot(pilot)) {
return pilot.derived_business_overview
? businessOverviewNextStepLine(pilot.derived_business_overview)
: "Если нужен уже управленческий вывод, следующим шагом стоит отдельно проверить прибыль/маржу, долги, НДС и складскую ликвидность, а затем собрать полный бизнес-аудит.";
}
if (mode === "confirmed_with_bounded_inference" && pilot.derived_metadata_surface) {
const surface = pilot.derived_metadata_surface;
if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) {
return `Следующим шагом лучше сузить surface до одного семейства: ${surface.ambiguity_entity_sets.join(", ")}.`;
return `Следующим шагом лучше выбрать один контур схемы: ${surface.ambiguity_entity_sets.join(", ")}.`;
}
const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family);
if (surface.selected_entity_set && routeLabel) {
return `Следующим шагом могу пойти в ${routeLabel} по surface «${surface.selected_entity_set}» и уже искать подтвержденные данные, а не только схему.`;
return `Следующим шагом могу пойти в ${routeLabel} по типу «${surface.selected_entity_set}» и уже искать подтвержденные данные, а не только схему.`;
}
}
if (mode === "checked_sources_only" && pilot.query_limitations.length > 0) {
return "Можно повторить проверку после восстановления MCP-доступа или сузить вопрос до конкретного контрагента/периода.";
return "Можно повторить проверку после восстановления доступа к 1С или сузить вопрос до конкретного контрагента/периода.";
}
if (mode === "blocked") {
return "Нужно сначала снять policy/blocking причину, иначе данные 1С использовать нельзя.";
@ -453,6 +616,20 @@ function buildMustNotClaim(pilot) {
claims.push("Do not claim full all-time turnover unless the checked period and coverage prove it.");
claims.push("Do not present a derived sum as a legal/accounting final total outside the checked 1C rows.");
}
if (isBusinessOverviewPilot(pilot)) {
claims.push("Do not present business overview cash-flow spread as profit or margin.");
claims.push("Do not present business overview yearly operating-flow breakdown as profit, financial result, or a complete annual P&L.");
claims.push("Do not present business overview trading-margin proxy as clean profit, accounting financial result, or exact cost-of-sales margin.");
claims.push("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked.");
claims.push("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis.");
claims.push("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt.");
claims.push("Do not present business overview debt staleness risk proxy as confirmed overdue debt, contractual delinquency, credit risk, or due-date aging.");
claims.push("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health.");
claims.push("Do not present business overview inventory turnover proxy as full inventory liquidity, FIFO turnover, obsolescence analysis, or liquidation value.");
claims.push("Do not present business overview inventory staleness risk proxy as confirmed obsolete stock, reserve, write-off, or liquidation value.");
claims.push("Do not expose business_overview_route_template_v1 or MCP primitive names in the user answer.");
}
if (pilot.derived_ranked_value_flow) {
claims.push("Do not present a bounded ranking as a complete all-time ranking outside the checked period and organization.");
claims.push("Do not imply the top-ranked counterparty is globally final when probe-limit or scope boundaries still exist.");
@ -475,6 +652,13 @@ function buildMustNotClaim(pilot) {
claims.push("Do not claim legal identity uniqueness when several catalog candidates are still plausible.");
claims.push("Do not imply that the resolved entity has already been used in a downstream data probe.");
}
if (isInventoryTemplatePilot(pilot)) {
if (!pilot.mcp_execution_performed) {
claims.push("Do not present inventory route-template planning as executed stock, supplier, purchase, or sale evidence.");
}
claims.push("Do not expose inventory_route_template_v1 or MCP primitive names in the user answer.");
claims.push("Do not claim full inventory coverage outside the checked rows, date, organization, item, or supplier scope.");
}
if (pilot.evidence.confirmed_facts.length === 0) {
claims.push("Do not claim a confirmed business fact when confirmed_facts is empty.");
}
@ -530,19 +714,18 @@ function derivedMetadataConfirmedLine(pilot) {
}
const scope = surface.metadata_scope ? ` по области "${surface.metadata_scope}"` : "";
const entitySets = surface.available_entity_sets.length > 0
? ` Типы объектов: ${surface.available_entity_sets.join(", ")}.`
? ` Тип объектов: ${surface.available_entity_sets.join(", ")}.`
: "";
const objects = surface.matched_objects.length > 0
? ` Найденные объекты: ${surface.matched_objects.slice(0, 8).join(", ")}.`
: "";
const selectedEntitySet = surface.selected_entity_set ? ` Выбранное family: ${surface.selected_entity_set}.` : "";
const selectedObjects = surface.selected_surface_objects.length > 0
? ` Выбранные surface-объекты: ${surface.selected_surface_objects.slice(0, 6).join(", ")}.`
? ` Для следующего шага подходят: ${surface.selected_surface_objects.slice(0, 6).join(", ")}.`
: "";
const fields = surface.available_fields.length > 0
? ` Доступные поля/секции: ${surface.available_fields.slice(0, 12).join(", ")}.`
: "";
return `Подтвержденная metadata-поверхность 1С${scope}: ${surface.matched_rows} строк metadata-ответа.${entitySets}${objects}${selectedEntitySet}${selectedObjects}${fields}`.replace(/\s+/g, " ").trim();
return `В схеме 1С${scope} найдены подтвержденные объекты: ${surface.matched_rows}.${entitySets}${objects}${selectedObjects}${fields}`.replace(/\s+/g, " ").trim();
}
function derivedMetadataInferenceLine(pilot) {
const surface = pilot.derived_metadata_surface;
@ -550,13 +733,13 @@ function derivedMetadataInferenceLine(pilot) {
return null;
}
if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) {
return `По подтвержденной metadata-поверхности видно несколько конкурирующих family: ${surface.ambiguity_entity_sets.join(", ")}. Следующий data-lane пока нельзя выбрать без явного сужения.`;
return `По проверенной схеме видно несколько возможных контуров: ${surface.ambiguity_entity_sets.join(", ")}. Следующий шаг пока нельзя выбрать без явного сужения.`;
}
const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family);
if (!surface.selected_entity_set || !routeLabel) {
return null;
}
return `По подтвержденной metadata-поверхности следующий проверяемый шаг можно ограниченно оценить как ${routeLabel} через family «${surface.selected_entity_set}». Это еще не выполненный data-fetch, а только grounded выбор следующего контура.`;
return `Следующий проверяемый шаг можно вести в ${routeLabel} через тип «${surface.selected_entity_set}». Это пока выбор контура по схеме 1С, а не уже полученные бизнес-строки.`;
}
function derivedEntityResolutionConfirmedLine(pilot) {
const resolution = pilot.derived_entity_resolution;
@ -601,6 +784,15 @@ function derivedRankedValueFlowConfirmedLine(pilot) {
const leader = ranking.ranked_values[0];
const organization = ranking.organization_scope ? ` по организации ${ranking.organization_scope}` : "";
const period = ranking.period_scope ? ` за период ${ranking.period_scope}` : " в проверенном окне";
if (ranking.ranked_values.length === 1) {
const singleLead = ranking.value_flow_direction === "outgoing_supplier_payout"
? "В проверенных исходящих платежах найден один контрагент"
: "В проверенных входящих поступлениях найден один контрагент";
const limitCaveat = ranking.coverage_limited_by_probe_limit
? " Лимит строк проверки достигнут; сравнение с другими контрагентами может быть неполным."
: " Других контрагентов в этом проверенном срезе не найдено, поэтому это не полноценный сравнительный рейтинг.";
return `${singleLead} ${leader.axis_value}${organization}${period}: ${leader.total_amount_human_ru} по ${leader.rows_with_amount} строкам с суммой.${limitCaveat}`;
}
const directionLead = ranking.ranking_need === "bottom_asc"
? ranking.value_flow_direction === "outgoing_supplier_payout"
? "Меньше всего заплатили контрагенту"
@ -698,6 +890,317 @@ function derivedBidirectionalValueFlowMonthlyLines(pilot) {
}
return flow.monthly_breakdown.map((bucket) => `Помесячно: ${monthLabelRu(bucket.month_bucket)} — получили ${bucket.incoming_total_amount_human_ru}, заплатили ${bucket.outgoing_total_amount_human_ru}, ${netLabelRu(bucket.net_direction)} ${bucket.net_amount_human_ru}`);
}
function businessOverviewNetDirectionRu(direction) {
if (direction === "net_incoming") {
return "операционный денежный поток в проверенном срезе больше входящий, чем исходящий";
}
if (direction === "net_outgoing") {
return "операционный денежный поток в проверенном срезе больше исходящий, чем входящий";
}
return "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
}
function amountHumanRu(value) {
const rounded = Math.round(Math.abs(value) * 100) / 100;
return `${new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 2 }).format(rounded)} руб.`;
}
function yearCountHumanRu(count) {
const abs = Math.abs(count) % 100;
const last = abs % 10;
const noun = abs >= 11 && abs <= 14
? "лет"
: last === 1
? "год"
: last >= 2 && last <= 4
? "года"
: "лет";
return `${count} ${noun}`;
}
function percentOfTotal(part, total) {
if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) {
return null;
}
return Math.round((part / total) * 10_000) / 100;
}
function percentText(part, total) {
const pct = percentOfTotal(part, total);
return pct === null ? null : `${pct}%`;
}
function inventoryStalenessRiskBandRu(riskBand) {
if (riskBand === "high") {
return "высокая зона внимания";
}
if (riskBand === "elevated") {
return "повышенная зона внимания";
}
if (riskBand === "watch") {
return "зона наблюдения";
}
return "низкий видимый риск";
}
function debtStalenessRiskBandRu(riskBand) {
if (riskBand === "high") {
return "высокая зона внимания";
}
if (riskBand === "elevated") {
return "повышенная зона внимания";
}
if (riskBand === "watch") {
return "зона наблюдения";
}
return "низкий видимый риск";
}
function derivedBusinessOverviewConfirmedLines(pilot) {
const overview = pilot.derived_business_overview;
if (!overview) {
return [];
}
const organization = overview.organization_scope ? ` по организации ${overview.organization_scope}` : "";
const period = overview.period_scope ? ` за ${overview.period_scope}` : " за все доступное проверенное окно";
const lines = [];
if (overview.incoming_customer_revenue.rows_with_amount > 0) {
lines.push(`Входящие поступления${organization}${period}: ${overview.incoming_customer_revenue.total_amount_human_ru} по ${overview.incoming_customer_revenue.rows_with_amount} строкам с суммой.`);
}
if (overview.outgoing_supplier_payout.rows_with_amount > 0) {
lines.push(`Исходящие платежи/списания${organization}${period}: ${overview.outgoing_supplier_payout.total_amount_human_ru} по ${overview.outgoing_supplier_payout.rows_with_amount} строкам с суммой.`);
}
const leader = overview.top_customers[0];
if (leader) {
lines.push(`Самый крупный подтвержденный клиент в проверенном срезе: ${leader.axis_value}${leader.total_amount_human_ru}.`);
}
const supplierLeader = overview.top_suppliers?.[0];
if (supplierLeader) {
lines.push(`Самый крупный подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${supplierLeader.axis_value}${supplierLeader.total_amount_human_ru}.`);
}
if (overview.yearly_breakdown?.length) {
lines.push(`Годовая раскладка операционного денежного потока построена по подтвержденным строкам 1С за ${yearCountHumanRu(overview.yearly_breakdown.length)}.`);
}
if (overview.activity_period) {
lines.push(`Окно подтвержденной активности в 1С: ${overview.activity_period.first_activity_date}${overview.activity_period.latest_activity_date}; ориентировочно ${overview.activity_period.duration_human_ru}.`);
}
if (overview.tax_position) {
const taxDirection = overview.tax_position.net_vat_direction === "vat_to_pay"
? "к уплате"
: overview.tax_position.net_vat_direction === "vat_to_recover_or_offset"
? "к вычету/зачету"
: "сбалансирован";
lines.push(`НДС-позиция за ${overview.tax_position.period_scope}: книга продаж ${overview.tax_position.sales_vat_amount_human_ru}, книга покупок/вычеты ${overview.tax_position.purchase_vat_amount_human_ru}, нетто ${taxDirection} ${overview.tax_position.net_vat_amount_human_ru}.`);
}
if (overview.trading_margin_proxy) {
const proxy = overview.trading_margin_proxy;
const marginText = proxy.margin_to_revenue_pct === null ? "не рассчитана" : `${proxy.margin_to_revenue_pct}%`;
lines.push(`Торговый margin proxy за ${proxy.period_scope}: выручка продаж ${proxy.sales_revenue_human_ru}, закупочный документный след ${proxy.purchase_cost_proxy_human_ru}, валовый спред proxy ${proxy.gross_spread_proxy_human_ru}, маржинальность к выручке ${marginText}. Это не чистая прибыль и не бухгалтерский финрезультат.`);
}
if (overview.debt_position) {
const debtDirection = overview.debt_position.net_debt_position_direction === "net_receivable"
? "в пользу дебиторки"
: overview.debt_position.net_debt_position_direction === "net_payable"
? "в сторону кредиторки"
: "сбалансировано";
lines.push(`Долговой срез на ${overview.debt_position.as_of_date}: дебиторка ${overview.debt_position.receivables.total_amount_human_ru}, кредиторка ${overview.debt_position.payables.total_amount_human_ru}, нетто ${debtDirection} ${overview.debt_position.net_debt_position_amount_human_ru}.`);
}
if (overview.debt_open_settlement_quality) {
const quality = overview.debt_open_settlement_quality;
const topContract = quality.top_contracts[0];
const topContractText = topContract
? ` Крупнейший открытый договор: ${topContract.contract}${topContract.counterparty ? ` / ${topContract.counterparty}` : ""}${topContract.total_amount_human_ru}${topContract.share_of_gross_open_amount_pct === null ? "" : ` (${topContract.share_of_gross_open_amount_pct}%)`}.`
: "";
lines.push(`Качество открытых расчетов на ${quality.as_of_date}: брутто открытых договорных остатков ${quality.gross_open_amount_human_ru}, договоров ${quality.unique_contracts}, контрагентов ${quality.unique_counterparties}.${topContractText}`);
if (quality.age_signal?.oldest_start_date) {
const ageText = quality.age_signal.max_age_days === null
? ""
: `, максимальный возраст сигнала ${quality.age_signal.max_age_days} дн.`;
lines.push(`Возрастной сигнал открытых расчетов: самая ранняя найденная дата договора ${quality.age_signal.oldest_start_date}${ageText}. Это не просрочка и не due-date анализ.`);
}
}
if (overview.debt_staleness_risk_proxy) {
const proxy = overview.debt_staleness_risk_proxy;
const counterpartyText = proxy.top_contract_counterparty ? ` / ${proxy.top_contract_counterparty}` : "";
lines.push(`Staleness risk proxy открытых расчетов на ${proxy.as_of_date}: самый старый договорный сигнал ${proxy.oldest_contract_start_date}, возраст ${proxy.max_contract_age_days} дн.; старейший крупный договор ${proxy.top_contract}${counterpartyText} держит ${proxy.top_contract_amount_human_ru} (${proxy.top_contract_share_pct}% брутто открытых остатков); оценка ${debtStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная просрочка, не кредитный риск и не due-date aging.`);
}
if (overview.inventory_position) {
const leader = overview.inventory_position.top_items[0];
const leaderText = leader
? ` Крупнейшая подтвержденная позиция: ${leader.item}${leader.total_amount_human_ru}.`
: "";
lines.push(`Складской срез на ${overview.inventory_position.as_of_date}: остаток ${overview.inventory_position.total_amount_human_ru} по ${overview.inventory_position.rows_with_amount} строкам с суммой и ${overview.inventory_position.rows_with_quantity} строкам с количеством.${leaderText}`);
if (overview.inventory_position.aging_signal?.oldest_purchase_date) {
const ageText = overview.inventory_position.aging_signal.max_age_days === null
? ""
: `, максимальный возраст сигнала ${overview.inventory_position.aging_signal.max_age_days} дн.`;
lines.push(`Возрастной сигнал склада: самая ранняя найденная дата закупки ${overview.inventory_position.aging_signal.oldest_purchase_date}${ageText}.`);
}
}
if (overview.inventory_turnover_proxy) {
const proxy = overview.inventory_turnover_proxy;
const ratioText = proxy.sales_to_stock_amount_ratio === null
? "не рассчитано"
: `${proxy.sales_to_stock_amount_ratio}x`;
const stockShareText = proxy.stock_to_sales_revenue_pct === null
? "не рассчитана"
: `${proxy.stock_to_sales_revenue_pct}%`;
lines.push(`Оборотный proxy склада за ${proxy.period_scope}: продажи ${proxy.sales_revenue_human_ru}, остаток на ${proxy.as_of_date} ${proxy.inventory_amount_human_ru}, sales-to-stock ratio ${ratioText}, остаток к продажам ${stockShareText}. Это не полноценная складская ликвидность, не FIFO-оборачиваемость и не анализ устаревания.`);
}
if (overview.inventory_staleness_risk_proxy) {
const proxy = overview.inventory_staleness_risk_proxy;
lines.push(`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`);
}
return lines;
}
function businessOverviewCashSynthesisLine(overview) {
const incoming = overview.incoming_customer_revenue;
const outgoing = overview.outgoing_supplier_payout;
if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) {
return null;
}
const checkedOperationalScale = Math.abs(incoming.total_amount) + Math.abs(outgoing.total_amount);
return [
`Аналитический вывод по оборотам: проверенный операционный размах ${amountHumanRu(checkedOperationalScale)}; входящий поток ${incoming.total_amount_human_ru}, исходящий ${outgoing.total_amount_human_ru}.`,
`${businessOverviewNetDirectionRu(overview.net_direction)}; расчетное нетто ${overview.net_amount_human_ru}.`
].join(" ");
}
function businessOverviewCustomerConcentrationLine(overview) {
const leader = overview.top_customers[0];
if (!leader || overview.incoming_customer_revenue.total_amount <= 0) {
return null;
}
const share = percentText(leader.total_amount, overview.incoming_customer_revenue.total_amount);
return share
? `Концентрация входящего потока: крупнейший подтвержденный клиент ${leader.axis_value} дает около ${share} проверенных входящих поступлений (${leader.total_amount_human_ru}). Это сигнал зависимости от клиента, а не полный customer-risk аудит.`
: `Крупнейший подтвержденный клиент в проверенном срезе: ${leader.axis_value}${leader.total_amount_human_ru}.`;
}
function businessOverviewSupplierConcentrationLine(overview) {
const leader = overview.top_suppliers?.[0];
if (!leader || overview.outgoing_supplier_payout.total_amount <= 0) {
return null;
}
const share = percentText(leader.total_amount, overview.outgoing_supplier_payout.total_amount);
return share
? `Концентрация исходящего потока: крупнейший подтвержденный поставщик/получатель исходящих платежей ${leader.axis_value} держит около ${share} проверенных исходящих платежей (${leader.total_amount_human_ru}). Это сигнал procurement concentration по найденным строкам, а не полный vendor-risk аудит или структура всех расходов.`
: `Крупнейший подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${leader.axis_value}${leader.total_amount_human_ru}.`;
}
function businessOverviewYearlyOperatingLine(overview) {
const years = overview.yearly_breakdown ?? [];
if (years.length === 0) {
return null;
}
const strongestIncomingYear = [...years]
.filter((bucket) => bucket.incoming_total_amount > 0)
.sort((left, right) => right.incoming_total_amount - left.incoming_total_amount || left.year_bucket.localeCompare(right.year_bucket))[0];
const strongestNetYear = [...years]
.filter((bucket) => bucket.net_amount !== 0)
.sort((left, right) => right.net_amount - left.net_amount || left.year_bucket.localeCompare(right.year_bucket))[0];
if (!strongestIncomingYear && !strongestNetYear) {
return null;
}
const parts = [];
if (strongestIncomingYear) {
parts.push(`самый сильный год по подтвержденным входящим поступлениям ${strongestIncomingYear.year_bucket}: ${strongestIncomingYear.incoming_total_amount_human_ru}`);
}
if (strongestNetYear) {
const netText = strongestNetYear.net_direction === "net_outgoing"
? `нетто исходящее ${strongestNetYear.net_amount_human_ru}`
: `нетто в плюс ${strongestNetYear.net_amount_human_ru}`;
parts.push(`лучший год по расчетному операционному нетто ${strongestNetYear.year_bucket}: ${netText}`);
}
return `Годовая динамика по проверенным строкам: ${parts.join("; ")}. Это operating-flow proxy, не бухгалтерская прибыль и не финрезультат.`;
}
function businessOverviewRiskSynthesisLine(overview) {
const signals = [];
if (overview.tax_position) {
const taxDirection = overview.tax_position.net_vat_direction === "vat_to_pay"
? `НДС к уплате ${overview.tax_position.net_vat_amount_human_ru}`
: overview.tax_position.net_vat_direction === "vat_to_recover_or_offset"
? `НДС к вычету/зачету ${overview.tax_position.net_vat_amount_human_ru}`
: "НДС-позиция сбалансирована";
signals.push(taxDirection);
}
if (overview.trading_margin_proxy) {
const marginText = overview.trading_margin_proxy.margin_to_revenue_pct === null
? "маржинальность не рассчитана"
: `маржинальность proxy ${overview.trading_margin_proxy.margin_to_revenue_pct}%`;
signals.push(`торговый спред proxy ${overview.trading_margin_proxy.gross_spread_proxy_human_ru}, ${marginText}`);
}
if (overview.debt_position) {
const debtDirection = overview.debt_position.net_debt_position_direction === "net_receivable"
? `дебиторка больше кредиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
: overview.debt_position.net_debt_position_direction === "net_payable"
? `кредиторка больше дебиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
: "дебиторка и кредиторка сбалансированы";
signals.push(debtDirection);
}
if (overview.debt_open_settlement_quality?.concentration_top_contract_pct !== null && overview.debt_open_settlement_quality?.top_contracts[0]) {
const topContract = overview.debt_open_settlement_quality.top_contracts[0];
signals.push(`крупнейший открытый договор держит ${overview.debt_open_settlement_quality.concentration_top_contract_pct}% открытых остатков (${topContract.total_amount_human_ru})`);
}
if (overview.debt_open_settlement_quality?.age_signal?.max_age_days !== null && overview.debt_open_settlement_quality?.age_signal?.max_age_days !== undefined) {
signals.push(`самый старый договорный возрастной сигнал ${overview.debt_open_settlement_quality.age_signal.max_age_days} дн.`);
}
if (overview.debt_staleness_risk_proxy) {
signals.push(`staleness risk proxy открытых расчетов: ${debtStalenessRiskBandRu(overview.debt_staleness_risk_proxy.risk_band)}, возраст ${overview.debt_staleness_risk_proxy.max_contract_age_days} дн., концентрация старейшего крупного договора ${overview.debt_staleness_risk_proxy.top_contract_share_pct}%`);
}
if (overview.inventory_position) {
signals.push(`складской остаток на дату ${overview.inventory_position.total_amount_human_ru}`);
if (overview.inventory_position.aging_signal?.max_age_days !== null && overview.inventory_position.aging_signal?.max_age_days !== undefined) {
signals.push(`самый старый складской purchase-date сигнал ${overview.inventory_position.aging_signal.max_age_days} дн.`);
}
}
if (overview.inventory_turnover_proxy) {
const ratioText = overview.inventory_turnover_proxy.sales_to_stock_amount_ratio === null
? "sales-to-stock не рассчитан"
: `sales-to-stock ${overview.inventory_turnover_proxy.sales_to_stock_amount_ratio}x`;
signals.push(`оборотный proxy склада: ${ratioText}`);
}
if (overview.inventory_staleness_risk_proxy) {
signals.push(`staleness risk proxy склада: ${inventoryStalenessRiskBandRu(overview.inventory_staleness_risk_proxy.risk_band)}, возраст ${overview.inventory_staleness_risk_proxy.max_purchase_age_days} дн.`);
}
return signals.length > 0
? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.`
: null;
}
function businessOverviewExecutiveVerdictLine(overview) {
const hasCash = overview.incoming_customer_revenue.rows_with_amount > 0 || overview.outgoing_supplier_payout.rows_with_amount > 0;
const hasExtraSignals = Boolean(overview.tax_position ||
overview.trading_margin_proxy ||
overview.debt_position ||
overview.debt_open_settlement_quality ||
overview.debt_staleness_risk_proxy ||
overview.inventory_position ||
overview.inventory_turnover_proxy ||
overview.inventory_staleness_risk_proxy);
if (!hasCash && !hasExtraSignals) {
return null;
}
const cashTone = overview.net_direction === "net_incoming"
? "операционно входящий поток сильнее исходящего"
: overview.net_direction === "net_outgoing"
? "операционно исходящий поток сильнее входящего, это зона внимания к расходам/закупкам"
: "операционный поток выглядит сбалансированным";
const evidenceTone = hasExtraSignals
? "часть налоговых, долговых или складских контуров уже отдельно проверена"
: "налоги, долги и склад еще не дают проверенного управленческого контекста";
return `Сводный LLM-аудит по подтвержденному: ${cashTone}; ${evidenceTone}. Это полезный управленческий срез по найденным строкам 1С, но не финальный вывод о прибыльности, марже или здоровье компании.`;
}
function derivedBusinessOverviewInferenceLines(pilot) {
const overview = pilot.derived_business_overview;
if (!overview) {
return [];
}
return [
businessOverviewCashSynthesisLine(overview),
businessOverviewCustomerConcentrationLine(overview),
businessOverviewSupplierConcentrationLine(overview),
businessOverviewYearlyOperatingLine(overview),
businessOverviewRiskSynthesisLine(overview),
businessOverviewExecutiveVerdictLine(overview),
"Это аналитическая интерпретация подтвержденных строк, а не прибыль и не маржа: для финального управленческого вывода нужны отдельные расходы, себестоимость, закрывающие документы, долги, налоги и складская оборачиваемость."
].filter((line) => Boolean(line));
}
function businessOverviewUnknownLines(pilot) {
if (!pilot.derived_business_overview) {
return userFacingUnknowns(pilot.evidence.unknown_facts);
}
return userFacingUnknowns(pilot.evidence.unknown_facts);
}
function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
const mode = modeFor(pilot);
const reasonCodes = [...pilot.reason_codes, ...pilot.evidence.reason_codes];
@ -705,16 +1208,22 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
if (pilot.evidence.unknown_facts.length > 0) {
pushReason(reasonCodes, "answer_contains_unknown_fact_boundary");
}
if (pilot.evidence.inferred_facts.length > 0) {
pushReason(reasonCodes, "answer_contains_bounded_inference");
}
const businessOverviewInferenceLines = derivedBusinessOverviewInferenceLines(pilot);
const derivedInferenceLine = derivedActivityInferenceLine(pilot) ??
derivedMetadataInferenceLine(pilot) ??
derivedRankedValueFlowInferenceLine(pilot) ??
derivedEntityResolutionInferenceLine(pilot);
const inferenceLines = derivedInferenceLine
? [derivedInferenceLine]
: pilot.evidence.inferred_facts;
const inferenceLines = businessOverviewInferenceLines.length > 0
? businessOverviewInferenceLines
: derivedInferenceLine
? [derivedInferenceLine]
: pilot.evidence.inferred_facts;
if (inferenceLines.length > 0) {
pushReason(reasonCodes, "answer_contains_bounded_inference");
}
if (businessOverviewInferenceLines.length > 0) {
pushReason(reasonCodes, "answer_contains_business_overview_analyst_synthesis");
}
const derivedMetadataLine = derivedMetadataConfirmedLine(pilot);
const derivedEntityResolutionLine = derivedEntityResolutionConfirmedLine(pilot);
const derivedValueLine = derivedBidirectionalValueFlowConfirmedLine(pilot) ??
@ -723,18 +1232,64 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
const monthlyConfirmedLines = derivedBidirectionalValueFlowMonthlyLines(pilot).length > 0
? derivedBidirectionalValueFlowMonthlyLines(pilot)
: derivedValueFlowMonthlyLines(pilot);
const businessOverviewLines = derivedBusinessOverviewConfirmedLines(pilot);
if (monthlyConfirmedLines.length > 0) {
pushReason(reasonCodes, "answer_contains_monthly_breakdown");
}
const confirmedLines = pilot.derived_ranked_value_flow && derivedValueLine
? [derivedValueLine]
: derivedValueLine
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
: derivedEntityResolutionLine
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
: derivedMetadataLine
? [...pilot.evidence.confirmed_facts, derivedMetadataLine]
: pilot.evidence.confirmed_facts;
if (businessOverviewLines.length > 0) {
pushReason(reasonCodes, "answer_contains_business_overview");
}
if (pilot.derived_business_overview?.tax_position) {
pushReason(reasonCodes, "answer_contains_business_overview_tax_position");
}
if (pilot.derived_business_overview?.trading_margin_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_trading_margin_proxy");
}
if (pilot.derived_business_overview?.top_suppliers?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_supplier_concentration");
}
if (pilot.derived_business_overview?.yearly_breakdown?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_yearly_operating_breakdown");
}
if (pilot.derived_business_overview?.debt_position) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_position");
}
if (pilot.derived_business_overview?.debt_open_settlement_quality) {
pushReason(reasonCodes, "answer_contains_business_overview_open_settlement_quality");
if (pilot.derived_business_overview.debt_open_settlement_quality.age_signal) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_age_signal");
}
}
if (pilot.derived_business_overview?.debt_staleness_risk_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_staleness_risk_proxy");
}
if (pilot.derived_business_overview?.inventory_position) {
pushReason(reasonCodes, "answer_contains_business_overview_inventory_position");
}
if (pilot.derived_business_overview?.inventory_turnover_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_inventory_turnover_proxy");
}
if (pilot.derived_business_overview?.inventory_staleness_risk_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_inventory_staleness_risk_proxy");
}
const confirmedLines = businessOverviewLines.length > 0
? businessOverviewLines
: pilot.derived_ranked_value_flow && derivedValueLine
? [derivedValueLine]
: derivedValueLine
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
: derivedEntityResolutionLine
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
: derivedMetadataLine
? [derivedMetadataLine]
: pilot.evidence.confirmed_facts;
const unknownLines = pilot.derived_business_overview
? businessOverviewUnknownLines(pilot)
: pilot.derived_metadata_surface
? pilot.derived_metadata_surface.available_fields.length > 0
? userFacingUnknowns(pilot.evidence.unknown_facts)
: ["Детальный список полей этих объектов этим шагом не получен."]
: rankedValueFlowUnknownLines(pilot);
return {
schema_version: exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryAnswerAdapter",
@ -742,7 +1297,7 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
headline: headlineFor(mode, pilot),
confirmed_lines: uniqueStrings(confirmedLines),
inference_lines: uniqueStrings(inferenceLines),
unknown_lines: rankedValueFlowUnknownLines(pilot),
unknown_lines: unknownLines,
limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]),
next_step_line: nextStepFor(mode, pilot),
internal_mechanics_allowed: false,

View File

@ -44,6 +44,40 @@ function businessFactFamilyFor(input) {
if (combined.includes("entity discovery") || combined.includes("entity_resolution")) {
return "entity_grounding";
}
if (combined.includes("broad_business_evaluation") ||
combined.includes("broad_evaluation") ||
combined.includes("business overview") ||
combined.includes("business_overview") ||
combined.includes("company analysis") ||
combined.includes("business audit")) {
return "business_overview";
}
if (combined.includes("inventory") ||
combined.includes("stock") ||
combined.includes("warehouse") ||
combined.includes("item provenance") ||
combined.includes("purchase provenance")) {
if (combined.includes("sale_trace") ||
combined.includes("sale trace") ||
combined.includes("buyer") ||
combined.includes("purchase_to_sale") ||
combined.includes("purchase-to-sale")) {
return "inventory_sale_trace";
}
if (combined.includes("supplier_overlap") ||
combined.includes("supplier overlap") ||
combined.includes("supplier stock") ||
combined.includes("stock by supplier")) {
return "inventory_supplier_overlap";
}
if (combined.includes("purchase_provenance") ||
combined.includes("purchase provenance") ||
combined.includes("purchase document") ||
combined.includes("supplier provenance")) {
return "inventory_purchase_provenance";
}
return "inventory_stock_snapshot";
}
if (combined.includes("lifecycle") || combined.includes("activity")) {
return "activity_lifecycle";
}
@ -87,6 +121,15 @@ function timeScopeNeedFor(input) {
if (input.family === "activity_lifecycle") {
return "open_activity_window";
}
if (input.family === "business_overview") {
return input.explicitDateScope ? "explicit_period" : "all_time_scope";
}
if (input.family === "inventory_stock_snapshot" || input.family === "inventory_supplier_overlap") {
return input.explicitDateScope ? "explicit_period" : "as_of_date_required";
}
if (input.family === "inventory_purchase_provenance" || input.family === "inventory_sale_trace") {
return input.explicitDateScope ? "explicit_period" : null;
}
return null;
}
function comparisonNeedFor(action) {
@ -161,6 +204,9 @@ function proofExpectationFor(input) {
if (input.family === "activity_lifecycle") {
return "bounded_inference";
}
if (input.family === "business_overview") {
return "bounded_inference";
}
return "coverage_checked_fact";
}
function decompositionCandidatesFor(input) {
@ -230,6 +276,39 @@ function decompositionCandidatesFor(input) {
pushUnique(result, "fetch_supporting_documents");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
return result;
}
if (input.family === "business_overview") {
pushUnique(result, "collect_scoped_movements");
pushUnique(result, "aggregate_checked_amounts");
pushUnique(result, "aggregate_ranked_axis_values");
pushUnique(result, "fetch_supporting_documents");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
return result;
}
if (input.family === "inventory_stock_snapshot") {
pushUnique(result, "fetch_scoped_movements");
pushUnique(result, "aggregate_checked_amounts");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
return result;
}
if (input.family === "inventory_supplier_overlap") {
pushUnique(result, "resolve_entity_reference");
pushUnique(result, "fetch_scoped_movements");
pushUnique(result, "fetch_scoped_documents");
pushUnique(result, "aggregate_checked_amounts");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
return result;
}
if (input.family === "inventory_purchase_provenance" || input.family === "inventory_sale_trace") {
pushUnique(result, "resolve_entity_reference");
pushUnique(result, "fetch_scoped_documents");
pushUnique(result, "drilldown_related_objects");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
}
return result;
}
@ -244,9 +323,23 @@ function forbiddenOverclaimFlagsFor(family) {
if (family === "activity_lifecycle") {
pushUnique(result, "no_legal_age_claim_without_evidence");
}
if (family === "business_overview") {
pushUnique(result, "no_unchecked_fact_totals");
pushUnique(result, "no_unchecked_business_health_claim");
pushUnique(result, "no_profit_or_margin_claim_without_evidence");
}
if (family === "value_flow" || family === "movement_evidence" || family === "document_evidence") {
pushUnique(result, "no_unchecked_fact_totals");
}
if (family === "inventory_stock_snapshot") {
pushUnique(result, "no_unchecked_stock_snapshot");
}
if (family === "inventory_purchase_provenance" || family === "inventory_supplier_overlap") {
pushUnique(result, "no_unproven_supplier_attribution");
}
if (family === "inventory_sale_trace") {
pushUnique(result, "no_unproven_buyer_or_sale_trace");
}
return result;
}
function buildAssistantMcpDiscoveryDataNeedGraph(input) {
@ -290,6 +383,8 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
family: businessFactFamily,
subjectResolutionOptional
});
const inventoryStockSnapshotWithoutSubject = subjectCandidates.length === 0 &&
businessFactFamily === "inventory_stock_snapshot";
const clarificationGaps = [];
if (unsupported === "metadata_lane_choice_clarification" || action === "resolve_next_lane") {
pushUnique(clarificationGaps, "lane_family_choice");
@ -305,10 +400,17 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
!explicitOrganizationScope) {
pushUnique(clarificationGaps, "organization");
}
else if (subjectCandidates.length === 0 &&
businessFactFamily === "business_overview" &&
!explicitOrganizationScope) {
pushUnique(clarificationGaps, "organization");
}
else if (subjectCandidates.length === 0 &&
businessFactFamily !== "schema_surface" &&
businessFactFamily !== "business_overview" &&
!openScopeWithoutSubject &&
!metadataScopedOpenLaneWithoutSubject) {
!metadataScopedOpenLaneWithoutSubject &&
!inventoryStockSnapshotWithoutSubject) {
pushUnique(clarificationGaps, "subject");
}
const timeScopeNeed = timeScopeNeedFor({
@ -319,6 +421,9 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
if (timeScopeNeed === "period_required" && !explicitDateScope) {
pushUnique(clarificationGaps, "period");
}
if (timeScopeNeed === "as_of_date_required" && !explicitDateScope) {
pushUnique(clarificationGaps, "as_of_date");
}
const decompositionCandidates = decompositionCandidatesFor({
family: businessFactFamily,
action,
@ -354,6 +459,9 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
if (allTimeScopeHint) {
pushReason(reasonCodes, "data_need_graph_all_time_scope_hint");
}
if (businessFactFamily === "business_overview" && !explicitDateScope) {
pushReason(reasonCodes, "data_need_graph_business_overview_defaults_to_all_time_scope");
}
if (clarificationGaps.includes("organization")) {
pushReason(reasonCodes, "data_need_graph_open_scope_total_needs_organization");
}

View File

@ -15,6 +15,19 @@ function toNonEmptyString(value) {
const text = String(value).trim();
return text.length > 0 ? text : null;
}
function toStringArray(value) {
if (!Array.isArray(value)) {
return [];
}
const result = [];
for (const item of value) {
const text = toNonEmptyString(item);
if (text && !result.includes(text)) {
result.push(text);
}
}
return result;
}
function isMcpDiscoveryEntryPointContract(value) {
const record = toRecordObject(value);
return (record?.schema_version === "assistant_mcp_discovery_runtime_entry_point_v1" &&
@ -32,6 +45,8 @@ function resolveEntryPoint(input) {
function buildAssistantMcpDiscoveryDebugAttachmentFields(input) {
const entryPoint = resolveEntryPoint(input);
const bridge = toRecordObject(entryPoint?.bridge);
const planner = toRecordObject(bridge?.planner);
const chainAlignment = toRecordObject(planner?.catalog_chain_template_alignment);
const answerDraft = toRecordObject(bridge?.answer_draft);
return {
assistant_mcp_discovery_entry_point_v1: entryPoint,
@ -39,6 +54,11 @@ function buildAssistantMcpDiscoveryDebugAttachmentFields(input) {
mcp_discovery_attempted: Boolean(entryPoint?.discovery_attempted),
mcp_discovery_hot_runtime_wired: false,
mcp_discovery_bridge_status: toNonEmptyString(bridge?.bridge_status),
mcp_discovery_selected_chain_id: toNonEmptyString(planner?.selected_chain_id),
mcp_discovery_catalog_chain_template_matches: toStringArray(planner?.catalog_chain_template_matches),
mcp_discovery_catalog_chain_alignment_status: toNonEmptyString(chainAlignment?.alignment_status),
mcp_discovery_catalog_chain_top_match: toNonEmptyString(chainAlignment?.top_chain_template_match),
mcp_discovery_catalog_chain_selected_matches_top: chainAlignment?.selected_chain_matches_top === true,
mcp_discovery_answer_mode: toNonEmptyString(answerDraft?.answer_mode),
mcp_discovery_business_fact_answer_allowed: bridge?.business_fact_answer_allowed === true,
mcp_discovery_user_facing_response_allowed: bridge?.user_facing_response_allowed === true,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -48,7 +48,12 @@ function uniqueStrings(values) {
}
function hasInternalMechanics(value) {
const text = value.toLowerCase();
return (text.includes("query_documents") ||
return (text.includes("mcp fetch failed") ||
text.includes("this operation was aborted") ||
text.includes("entity-resolution") ||
text.includes("could not continue") ||
text.includes("checked catalog search step") ||
text.includes("query_documents") ||
text.includes("query_movements") ||
text.includes("primitive") ||
text.includes("pilot_") ||
@ -245,6 +250,24 @@ function localizeLine(value) {
if (/^Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes after a broad probe hit the row limit$/i.test(value)) {
return "Покрытие запрошенного периода по двустороннему денежному потоку восстановлено помесячными проверками 1С после того, как общая выборка уперлась в лимит строк хотя бы по одной стороне.";
}
if (/^Requested period coverage was recovered through monthly 1C value-flow probes$/i.test(value)) {
return "Покрытие запрошенного периода восстановлено помесячными проверками 1С.";
}
if (/^Requested period coverage for counterparty ranking was recovered through monthly 1C probes$/i.test(value)) {
return "Покрытие запрошенного периода для рейтинга контрагентов восстановлено помесячными проверками 1С.";
}
if (/^Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes$/i.test(value)) {
return "Покрытие запрошенного периода по двустороннему денежному потоку восстановлено помесячными проверками 1С.";
}
if (/^Complete requested-period coverage is not proven by the available checked rows$/i.test(value)) {
return "Полное покрытие запрошенного периода не подтверждено доступными проверенными строками.";
}
if (/^Complete requested-period ranking coverage is not proven by the available checked rows$/i.test(value)) {
return "Полное покрытие рейтинга за запрошенный период не подтверждено доступными проверенными строками.";
}
if (/^Complete requested-period coverage for bidirectional value-flow is not proven by the available checked rows$/i.test(value)) {
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено доступными проверенными строками.";
}
return value;
}
function section(title, lines) {

View File

@ -38,7 +38,12 @@ function pushReason(target, value) {
}
function hasInternalMechanics(value) {
const text = value.toLowerCase();
return (text.includes("query_documents") ||
return (text.includes("mcp fetch failed") ||
text.includes("this operation was aborted") ||
text.includes("entity-resolution") ||
text.includes("could not continue") ||
text.includes("checked catalog search step") ||
text.includes("query_documents") ||
text.includes("query_movements") ||
text.includes("primitive") ||
text.includes("pilot_") ||
@ -192,6 +197,69 @@ function readStateTransitionReasonCodes(input) {
.map((item) => toNonEmptyString(item))
.filter((item) => Boolean(item));
}
function readStringArray(value) {
return Array.isArray(value)
? value.map((item) => toNonEmptyString(item)).filter((item) => Boolean(item))
: [];
}
function hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family);
if (askedDomain !== "counterparty_value") {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
if (askedAction === "payout") {
return detectedIntent !== "supplier_payouts_profile";
}
if (askedAction === "net_value_flow") {
return true;
}
return false;
}
function hasExactMatchedFactualAddressReply(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
if (hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint)) {
return false;
}
if (hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
return false;
}
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
const truthMode = toNonEmptyString(input.addressRuntimeMeta?.truth_mode);
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
const bindingStatus = toNonEmptyString(input.addressRuntimeMeta?.capability_binding_status);
const bindingViolations = readStringArray(input.addressRuntimeMeta?.capability_binding_violations);
return Boolean(mcpCallStatus === "matched_non_empty" &&
truthMode === "confirmed" &&
selectedRecipe?.startsWith("address_") &&
(bindingStatus === "bound" || bindingStatus === "bound_with_limits") &&
bindingViolations.length === 0);
}
function hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
return Boolean(isOpenScopeValueFlowRanking(entryPoint) ||
needsOpenScopeValueFlowOrganizationClarification(entryPoint) ||
(detectedIntent === "customer_revenue_and_payments" && isOpenScopeValueFlowWithoutSubject(entryPoint)));
}
function hasRuntimeAdjustedExactReply(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
@ -332,7 +400,10 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
const matchedFactualAddressContinuationTarget = hasMatchedFactualAddressContinuationTarget(input, entryPoint);
const matchedFactualSuggestedIntentPivotTarget = hasMatchedFactualSuggestedIntentPivotTarget(input, entryPoint);
const fullConfirmedFactualAddressReply = hasFullConfirmedFactualAddressReply(input, entryPoint);
const exactMatchedFactualAddressReply = hasExactMatchedFactualAddressReply(input, entryPoint);
const runtimeAdjustedExactReply = hasRuntimeAdjustedExactReply(input, entryPoint);
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
if (!entryPoint) {
pushReason(reasonCodes, "mcp_discovery_response_policy_no_entry_point");
}
@ -354,6 +425,12 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
if (semanticConflictWithDiscoveryTurnMeaning) {
pushReason(reasonCodes, "mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
}
if (valueFlowActionConflictWithDiscoveryTurnMeaning) {
pushReason(reasonCodes, "mcp_discovery_response_policy_value_flow_action_conflict_allows_candidate_override");
}
if (openScopeValueFlowDiscoveryPriority) {
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
}
if (matchedFactualAddressContinuationTarget) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_factual_address_continuation_target");
}
@ -363,6 +440,9 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
if (fullConfirmedFactualAddressReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_full_confirmed_factual_address_reply");
}
if (exactMatchedFactualAddressReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
}
if (runtimeAdjustedExactReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_runtime_adjusted_exact_reply_over_stale_discovery_turn_meaning");
}
@ -387,6 +467,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
!matchedFactualAddressContinuationTarget &&
!matchedFactualSuggestedIntentPivotTarget &&
!fullConfirmedFactualAddressReply &&
!exactMatchedFactualAddressReply &&
!runtimeAdjustedExactReply &&
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&

View File

@ -90,6 +90,8 @@ function buildLoopState(planner, pilot, bridgeStatus) {
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
loop_status: loopStatusFor(bridgeStatus),
selected_chain_id: planner.selected_chain_id,
catalog_chain_template_matches: [...planner.catalog_chain_template_matches],
catalog_chain_template_alignment: planner.catalog_chain_template_alignment,
pilot_scope: pilot.pilot_scope,
asked_domain_family: planner.discovery_plan.turn_meaning_ref?.asked_domain_family ?? null,
asked_action_family: planner.discovery_plan.turn_meaning_ref?.asked_action_family ?? null,

View File

@ -511,9 +511,15 @@ function metadataAmbiguityCollapsesToMovementLane(values) {
function hasLifecycleSignal(text) {
return /(?:сколько\s+лет|как\s+давно|давно\s+ли|возраст|перв(?:ая|ый)\s+актив|когда\s+начал|когда\s+появ|lifecycle|activity\s+duration|business\s+age|how\s+long)/iu.test(text);
}
function hasBusinessOverviewSignal(text) {
return /(?:\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u0430\u0443\u0434\u0438\u0442|\u043f\u043e\u043b\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u0441\u0432\u043e\u0434\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u043a\u0430\u043a\s+\u0442\u044b\s+\u043e\u0446\u0435\u043d(?:\u0438\u0448\u044c|\u0438)\s+\u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044e|\u0438\u044f)\s+\u0432\s+\u0446\u0435\u043b\u043e\u043c|company\s+(?:analysis|overview)|business\s+(?:overview|audit)|llm[-\s]?audit|бизнес[-\s]?обзор|бизнес[-\s]?аудит)/iu.test(text);
}
function hasValueFlowSignal(text) {
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(text);
}
function hasValueFlowAggregateQuestionSignal(text) {
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u0443\u043c\u043c|\u0438\u0442\u043e\u0433|\u0440\u0430\u0441\u0441\u0447\u0438\u0442|\u043d\u0435\u0442\u0442\u043e|\u0441\u0430\u043b\u044c\u0434\u043e|how\s+much|total|sum|net)/iu.test(text);
}
function hasPayoutSignal(text) {
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(text);
}
@ -540,13 +546,15 @@ function extractOrganizationScopeFromRawText(value) {
if (!match?.[1]) {
return null;
}
return toNonEmptyString(match[1]);
return toNonEmptyString(match[1]
.replace(/\s+(?:\u043f\u043e\s+\u0434\u0430\u043d\u043d\u044b\u043c\s+1\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0437\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})(?:\s+\u0433(?:\u043e\u0434|\.)?)?|\u043d\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})|\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0434\u0430\u0439\s+\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u043d\u043e\s+\u043d\u0435).*$/iu, "")
.trim());
}
function hasMonthlyAggregationSignal(text) {
return /(?:\u043f\u043e\s+\u043c\u0435\u0441\u044f\u0446\u0430\u043c|\u043f\u043e\u043c\u0435\u0441\u044f\u0447\u043d\u043e|\u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e|month\s+by\s+month|by\s+month|monthly)/iu.test(text);
}
function hasAllTimeScopeHint(text) {
return /(?:\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0437\u0430\s+\u043b\u044e\u0431\u043e\u0439\s+\u043f\u0435\u0440\u0438\u043e\u0434|for\s+all\s+time|all\s+time|entire\s+period|full\s+history|any\s+period)/iu.test(text);
return /(?:\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0437\u0430\s+\u043b\u044e\u0431\u043e\u0439\s+\u043f\u0435\u0440\u0438\u043e\u0434|for\s+all\s+time|all\s+time|entire\s+period|full\s+history|any\s+period)/iu.test(text);
}
function hasMetadataSignal(text) {
if (/(?:\u043c\u0435\u0442\u0430\u0434\u0430\u043d|schema|catalog|metadata\s+surface|\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440[\u0430\u044b]\s+1\u0441|\u0441\u0445\u0435\u043c[\u0430\u044b]\s+1\u0441)/iu.test(text)) {
@ -739,6 +747,15 @@ function metadataScopeHintFromRawText(text) {
function hasExplicitDateScopeLiteral(text) {
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b)/iu.test(text);
}
function stripNegatedTaxDateScopeClauses(text) {
const dateScopeLiteral = String.raw `\b(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})\b`;
const taxToken = String.raw `(?:\u043d\u0434\u0441|vat)`;
const scopeToken = String.raw `(?:\u0437\u0430|\u043d\u0430|\u043f\u043e|for|in)`;
const negatedVerb = String.raw `(?:\u0442\u0430\u0449\u0438(?:\u0442\u044c)?|\u0431\u0435\u0440\u0438|\u0431\u0440\u0430\u0442\u044c|\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439(?:\u0442\u0435)?|\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c|\u0441\u0447\u0438\u0442\u0430\u0439(?:\u0442\u0435)?|\u0441\u0447\u0438\u0442\u0430\u0442\u044c|\u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0439(?:\u0442\u0435)?|\u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0442\u044c|\u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438|\u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u044c|pull|reuse|use|carry)`;
const direct = new RegExp(String.raw `(?:^|[\s,;:])(?:\u043d\u0435\s+${negatedVerb}\s+${taxToken}\s+${scopeToken}\s+${dateScopeLiteral}|\u0431\u0435\u0437\s+${taxToken}\s+${scopeToken}\s+${dateScopeLiteral}|do\s+not\s+(?:${negatedVerb}\s+)?${taxToken}\s+${scopeToken}\s+${dateScopeLiteral})`, "giu");
const reversed = new RegExp(String.raw `${taxToken}\s+${scopeToken}\s+${dateScopeLiteral}\s+(?:\u043d\u0435\s+${negatedVerb}|do\s+not\s+(?:${negatedVerb})?)`, "giu");
return text.replace(direct, " ").replace(reversed, " ");
}
function collectDateScopeFromRawText(text) {
const isoDate = text.match(/\b(\d{4}-\d{2}-\d{2})\b/u);
if (isoDate?.[1]) {
@ -765,6 +782,9 @@ function isImplicitCurrentDateScope(value) {
}
function semanticNeedFor(input) {
const combined = compactLower(`${input.domain ?? ""} ${input.action ?? ""} ${input.unsupported ?? ""}`);
if (/(?:broad_business_evaluation|broad_evaluation|business_summary|business_overview|company analysis|business audit)/iu.test(combined)) {
return "business overview evidence with bounded analyst interpretation";
}
if (input.metadataSignal || /(?:metadata|schema|catalog|inspect_(?:catalog|documents|registers|fields))/iu.test(combined)) {
return "1C metadata evidence";
}
@ -784,6 +804,18 @@ function semanticNeedFor(input) {
if (/(?:document|documents|list_documents)/iu.test(combined)) {
return "document evidence";
}
if (/(?:inventory|stock|warehouse|purchase_provenance|purchase provenance|supplier provenance|supplier_overlap|supplier overlap|sale_trace|sale trace|purchase_to_sale|purchase-to-sale)/iu.test(combined)) {
if (/(?:sale_trace|sale trace|purchase_to_sale|purchase-to-sale|buyer)/iu.test(combined)) {
return "inventory sale trace evidence";
}
if (/(?:supplier_overlap|supplier overlap|supplier stock|stock by supplier)/iu.test(combined)) {
return "inventory supplier overlap evidence";
}
if (/(?:purchase_provenance|purchase provenance|supplier provenance|purchase document)/iu.test(combined)) {
return "inventory purchase provenance evidence";
}
return "inventory stock snapshot evidence";
}
return null;
}
function shouldRunDiscovery(input) {
@ -826,9 +858,11 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const rawEntitySourceText = repairedUserText ?? rawUserText ?? repairedEffectiveText ?? rawEffectiveText ?? rawSignalSourceText;
const rawText = compactLower(rawSignalSourceText);
const rawReferentialDocumentExclusionSignal = hasReferentialDocumentExclusionFollowupSignal(repairedUserText ?? rawUserText ?? "");
const rawLifecycleSignal = hasLifecycleSignal(rawText);
const rawBidirectionalValueFlowSignal = !rawLifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
const rawValueFlowSignal = !rawLifecycleSignal &&
const rawBusinessOverviewSignal = hasBusinessOverviewSignal(rawText);
const rawLifecycleSignal = !rawBusinessOverviewSignal && hasLifecycleSignal(rawText);
const rawBidirectionalValueFlowSignal = !rawBusinessOverviewSignal && !rawLifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
const rawValueFlowSignal = !rawBusinessOverviewSignal &&
!rawLifecycleSignal &&
(hasValueFlowSignal(rawText) || hasValueRankingSignal(rawText) || rawBidirectionalValueFlowSignal);
const rawMetadataSignal = !rawLifecycleSignal &&
!rawValueFlowSignal &&
@ -836,11 +870,16 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
hasMetadataSignal(rawText);
const rawEntityResolutionSignal = !rawLifecycleSignal && !rawValueFlowSignal && !rawMetadataSignal && hasEntityResolutionSignal(rawText);
const rawPayoutSignal = rawValueFlowSignal && !rawBidirectionalValueFlowSignal && hasPayoutSignal(rawText);
const rawValueFlowAggregateQuestionSignal = rawValueFlowSignal && hasValueFlowAggregateQuestionSignal(rawText);
const monthlyAggregationSignal = hasMonthlyAggregationSignal(rawText);
const rawAllTimeScopeSignal = hasAllTimeScopeHint(rawText);
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(rawText);
const dateScopeSignalText = stripNegatedTaxDateScopeClauses(rawText);
const negatedTaxDateScopeOnlySignal = dateScopeSignalText !== rawText &&
hasExplicitDateScopeLiteral(rawText) &&
!hasExplicitDateScopeLiteral(dateScopeSignalText);
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(dateScopeSignalText);
const relativeCurrentDateHintDetected = hasRelativeCurrentDateHint(rawText);
const rawDateScope = collectDateScopeFromRawText(rawText);
const rawDateScope = collectDateScopeFromRawText(dateScopeSignalText);
const rawMetadataScopeHint = rawMetadataSignal ? metadataScopeHintFromRawText(rawText) : null;
const rawEntityCandidate = rawEntityResolutionSignal ? rawEntityResolutionCandidate(rawEntitySourceText) : null;
const entityResolutionClarificationCandidate = followupSeed.pilotScope === "entity_resolution_search_v1" &&
@ -853,6 +892,12 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
const unsupported = toNonEmptyString(assistantTurnMeaning?.unsupported_but_understood_family);
const broadBusinessEvaluationUnsupported = unsupported === "broad_business_evaluation";
const businessOverviewSignal = rawBusinessOverviewSignal ||
broadBusinessEvaluationUnsupported ||
rawDomain === "business_summary" ||
rawDomain === "business_overview" ||
rawAction === "broad_evaluation";
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
const currentTurnDocumentLaneSignal = rawAction === "list_documents";
const currentTurnMovementLaneSignal = rawAction === "list_movements";
@ -1165,10 +1210,12 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable
? followupSeed.unsupported
: null;
const lifecycleSignal = rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
const bidirectionalValueFlowSignal = !lifecycleSignal &&
const lifecycleSignal = !businessOverviewSignal && (rawLifecycleSignal || seededDomain === "counterparty_lifecycle");
const bidirectionalValueFlowSignal = !businessOverviewSignal &&
!lifecycleSignal &&
(rawBidirectionalValueFlowSignal || seededAction === "net_value_flow");
const valueFlowSignal = !lifecycleSignal &&
const valueFlowSignal = !businessOverviewSignal &&
!lifecycleSignal &&
!metadataGroundedMovementLaneApplicable &&
(rawValueFlowSignal || seededDomain === "counterparty_value");
const payoutSignal = valueFlowSignal &&
@ -1177,9 +1224,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const semanticDataNeed = metadataAmbiguityLaneClarificationApplicable
? "metadata lane clarification"
: semanticNeedFor({
domain: rawDomain ?? seededDomain,
action: rawAction ?? seededAction,
unsupported: unsupported ?? seededUnsupported,
domain: businessOverviewSignal ? "business_overview" : rawDomain ?? seededDomain,
action: businessOverviewSignal ? "broad_evaluation" : rawAction ?? seededAction,
unsupported: businessOverviewSignal ? "broad_business_evaluation" : unsupported ?? seededUnsupported,
lifecycleSignal,
valueFlowSignal,
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
@ -1298,23 +1345,37 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
}
}
const clarificationLoopStillNeedsPeriod = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period"));
const businessOverviewRawWithoutDateScope = Boolean(businessOverviewSignal &&
!rawAllTimeScopeSignal &&
!explicitDateScopeLiteralDetected &&
!rawDateScope &&
!relativeCurrentDateHintDetected);
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(predecomposeDateScope &&
!isImplicitCurrentDateScope(predecomposeDateScope) &&
!businessOverviewRawWithoutDateScope);
const currentTurnCarriesExplicitPeriod = Boolean(explicitDateScopeLiteralDetected ||
rawDateScope ||
relativeCurrentDateHintDetected ||
(predecomposeDateScope && !isImplicitCurrentDateScope(predecomposeDateScope)));
predecomposeDateScopeCountsAsCurrentTurnPeriod);
const suppressImplicitCurrentDateScope = Boolean(!currentTurnCarriesExplicitPeriod &&
(clarificationLoopStillNeedsPeriod ||
businessOverviewSignal ||
openScopeValueFlowWithoutResolvedCounterparty ||
(valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal))));
const suppressNegatedTaxOnlyDateScope = Boolean(businessOverviewSignal && negatedTaxDateScopeOnlySignal);
const normalizedPredecomposeDateScope = (rawEntitySearchOverridesStaleScope && !currentTurnCarriesExplicitPeriod) ||
suppressNegatedTaxOnlyDateScope ||
businessOverviewRawWithoutDateScope ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope))
? null
: predecomposeDateScope;
const normalizedAssistantTurnMeaningDateScope = rawEntitySearchOverridesStaleScope ||
suppressNegatedTaxOnlyDateScope ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
? null
: assistantTurnMeaningDateScope;
const normalizedFollowupDateScope = rawEntitySearchOverridesStaleScope ||
suppressNegatedTaxOnlyDateScope ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope))
? null
: followupSeed.dateScope;
@ -1331,41 +1392,45 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
normalizedFollowupDateScope);
const clarificationLoopSeedApplied = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopSelectedChainId);
const turnMeaning = {
asked_domain_family: lifecycleSignal
? "counterparty_lifecycle"
: valueFlowSignal
? "counterparty_value"
: metadataGroundedMovementLaneApplicable
? "movements"
: metadataGroundedDocumentLaneApplicable
? "documents"
: entityResolutionSignal
? "entity_resolution"
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
? "metadata"
: rawDomain ?? seededDomain,
asked_action_family: lifecycleSignal
? "activity_duration"
: valueFlowSignal
? bidirectionalValueFlowSignal
? "net_value_flow"
: payoutSignal
? "payout"
: rawAction ?? seededAction ?? "turnover"
: metadataGroundedMovementLaneApplicable
? "list_movements"
: metadataGroundedDocumentLaneApplicable
? "list_documents"
: entityResolutionSignal
? "search_business_entity"
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
? metadataActionFromRawText(rawText) ?? seededAction
: rawAction ?? seededAction,
asked_domain_family: businessOverviewSignal
? "business_overview"
: lifecycleSignal
? "counterparty_lifecycle"
: valueFlowSignal
? "counterparty_value"
: metadataGroundedMovementLaneApplicable
? "movements"
: metadataGroundedDocumentLaneApplicable
? "documents"
: entityResolutionSignal
? "entity_resolution"
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
? "metadata"
: rawDomain ?? seededDomain,
asked_action_family: businessOverviewSignal
? "broad_evaluation"
: lifecycleSignal
? "activity_duration"
: valueFlowSignal
? bidirectionalValueFlowSignal
? "net_value_flow"
: payoutSignal
? "payout"
: rawAction ?? seededAction ?? "turnover"
: metadataGroundedMovementLaneApplicable
? "list_movements"
: metadataGroundedDocumentLaneApplicable
? "list_documents"
: entityResolutionSignal
? "search_business_entity"
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
? metadataActionFromRawText(rawText) ?? seededAction
: rawAction ?? seededAction,
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
seeded_ranking_need: valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
? followupSeed.rankingNeed
: undefined,
explicit_entity_candidates: entityCandidates,
explicit_entity_candidates: businessOverviewSignal ? [] : entityCandidates,
metadata_ambiguity_entity_sets: metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
? followupSeed.metadataAmbiguityEntitySets
: undefined,
@ -1373,29 +1438,32 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
explicit_organization_scope: explicitOrganizationScope,
explicit_date_scope: explicitDateScope,
subject_resolution_optional: metadataScopedLaneWithoutSubject || undefined,
unsupported_but_understood_family: unsupported ??
(lifecycleSignal
? "counterparty_lifecycle"
: valueFlowSignal
? bidirectionalValueFlowSignal
? "counterparty_bidirectional_value_flow_or_netting"
: payoutSignal
? "counterparty_payouts_or_outflow"
: seededUnsupported ?? "counterparty_value_or_turnover"
: metadataGroundedMovementLaneApplicable
? "movement_evidence"
: metadataGroundedDocumentLaneApplicable
? "document_evidence"
: metadataAmbiguityLaneClarificationApplicable
? "metadata_lane_choice_clarification"
: entityResolutionSignal
? "entity_resolution"
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
? "1c_metadata_surface"
: followupDiscoverySeedApplicable
? seededUnsupported
: null),
unsupported_but_understood_family: businessOverviewSignal
? "broad_business_evaluation"
: unsupported ??
(lifecycleSignal
? "counterparty_lifecycle"
: valueFlowSignal
? bidirectionalValueFlowSignal
? "counterparty_bidirectional_value_flow_or_netting"
: payoutSignal
? "counterparty_payouts_or_outflow"
: seededUnsupported ?? "counterparty_value_or_turnover"
: metadataGroundedMovementLaneApplicable
? "movement_evidence"
: metadataGroundedDocumentLaneApplicable
? "document_evidence"
: metadataAmbiguityLaneClarificationApplicable
? "metadata_lane_choice_clarification"
: entityResolutionSignal
? "entity_resolution"
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
? "1c_metadata_surface"
: followupDiscoverySeedApplicable
? seededUnsupported
: null),
stale_replay_forbidden: Boolean(assistantTurnMeaning?.stale_replay_forbidden ||
businessOverviewSignal ||
unsupported ||
lifecycleSignal ||
valueFlowSignal ||
@ -1444,8 +1512,13 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
if (turnMeaning.stale_replay_forbidden) {
cleanTurnMeaning.stale_replay_forbidden = true;
}
const currentTurnValueFlowExactOverrideApplicable = Boolean(valueFlowSignal &&
explicitIntentCandidate &&
rawValueFlowAggregateQuestionSignal &&
semanticDataNeed &&
(entityCandidates.length > 0 || explicitOrganizationScope || openScopeValueFlowWithoutResolvedCounterparty));
const runDiscovery = shouldRunDiscovery({
unsupported: unsupported ?? seededUnsupported,
unsupported: businessOverviewSignal ? "broad_business_evaluation" : unsupported ?? seededUnsupported,
lifecycleSignal,
valueFlowSignal,
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
@ -1459,13 +1532,15 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
metadataGroundedMovementLaneApplicable ||
metadataGroundedDocumentLaneApplicable ||
groundedValueFlowFollowupApplicable,
forceDiscoveryOverExplicitIntent: Boolean(entityResolutionClarificationCandidate) ||
forceDiscoveryOverExplicitIntent: businessOverviewSignal ||
Boolean(entityResolutionClarificationCandidate) ||
organizationClarificationFollowupApplicable ||
periodClarificationFollowupApplicable ||
metadataAmbiguityLaneClarificationApplicable ||
metadataGroundedMovementLaneApplicable ||
metadataGroundedDocumentLaneApplicable ||
groundedValueFlowFollowupApplicable
groundedValueFlowFollowupApplicable ||
currentTurnValueFlowExactOverrideApplicable
});
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
const sourceSignal = rawEntitySearchOverridesStaleScope
@ -1531,6 +1606,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
if (rawAllTimeScopeSignal) {
pushReason(reasonCodes, "mcp_discovery_all_time_scope_signal_detected");
}
if (suppressNegatedTaxOnlyDateScope) {
pushReason(reasonCodes, "mcp_discovery_negated_tax_period_scope_suppressed");
}
if (followupDiscoverySeedApplicable) {
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
}
@ -1576,6 +1654,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
if (groundedValueFlowFollowupApplicable) {
pushReason(reasonCodes, "mcp_discovery_grounded_value_flow_followup");
}
if (currentTurnValueFlowExactOverrideApplicable) {
pushReason(reasonCodes, "mcp_discovery_current_turn_value_flow_overrides_supported_exact");
}
if (documentEvidenceGroundedMovementFollowupApplicable) {
pushReason(reasonCodes, "mcp_discovery_document_evidence_grounded_movement_followup");
}
@ -1606,6 +1687,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
if (unsupported) {
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
}
if (businessOverviewSignal) {
pushReason(reasonCodes, "mcp_discovery_broad_business_evaluation_route_candidate");
}
if (!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
normalizedPredecomposeCounterparty) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");

View File

@ -88,31 +88,55 @@ function buildDiscoveryRecapFactLine(input) {
const objectsPart = objects.length > 0 ? `, нашли объекты ${objects.slice(0, 4).join(", ")}` : "";
const entitySetsPart = entitySets.length > 0 ? `, видны типы ${entitySets.slice(0, 4).join(", ")}` : "";
const fieldsPart = fields.length > 0 ? `, доступны поля/секции ${fields.slice(0, 5).join(", ")}` : "";
return `смотрели metadata-поверхность 1С${scopePart}${periodPart}: ${rows} подтвержденных строк${objectsPart}${entitySetsPart}${fieldsPart}`.trim();
return `смотрели схему 1С${scopePart}${periodPart}: ${rows} подтвержденных строк${objectsPart}${entitySetsPart}${fieldsPart}`.trim();
}
if (!input.counterparty) {
const rankedFlow = toRecordObject(pilot?.derived_ranked_value_flow);
if (rankedFlow) {
const rankedValues = Array.isArray(rankedFlow.ranked_values) ? rankedFlow.ranked_values : [];
const leader = toRecordObject(rankedValues[0]);
const leaderName = toNonEmptyString(leader?.axis_value);
const leaderAmount = toNonEmptyString(leader?.total_amount_human_ru);
const leaderRows = toNonEmptyString(leader?.rows_with_amount);
const organization = toNonEmptyString(rankedFlow.organization_scope) ?? input.organization;
const period = toNonEmptyString(rankedFlow.period_scope) ?? input.scopedDate;
const organizationPart = organization ? ` по компании «${organization}»` : "";
const periodPartForRanking = period ? ` за период ${period}` : periodPart;
if (leaderName && leaderAmount) {
const rowsPart = leaderRows ? ` по ${leaderRows} строкам` : "";
const rankingKind = rankedValues.length > 1 ? "строили рейтинг клиентов" : "видели единственного клиента в проверенном срезе";
return `${rankingKind}${organizationPart}${periodPartForRanking}: ${leaderName}${leaderAmount}${rowsPart}`.trim();
}
}
const subjectPart = input.counterparty
? `контрагенту «${input.counterparty}»`
: input.organization
? `компании «${input.organization}»`
: null;
if (!subjectPart) {
return null;
}
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
const activityPeriod = toRecordObject(pilot?.derived_activity_period);
const duration = toNonEmptyString(activityPeriod?.duration_human_ru);
return duration
? `смотрели подтвержденную активность по контрагенту «${input.counterparty}»${periodPart} и оценили период взаимодействия примерно как ${duration}`
: `смотрели подтвержденную активность по контрагенту «${input.counterparty}»${periodPart}`;
? `смотрели подтвержденную активность по ${subjectPart}${periodPart} и оценили период взаимодействия примерно как ${duration}`
: `смотрели подтвержденную активность по ${subjectPart}${periodPart}`;
}
if (pilotScope === "counterparty_supplier_payout_query_movements_v1") {
const flow = toRecordObject(pilot?.derived_value_flow);
const amount = toNonEmptyString(flow?.total_amount_human_ru);
const flowPeriodPart = periodPartForRecap(toNonEmptyString(flow?.period_scope) ?? input.scopedDate);
return amount
? `считали исходящие платежи/списания по контрагенту «${input.counterparty}»${periodPart}: ${amount}`
: `считали исходящие платежи/списания по контрагенту «${input.counterparty}»${periodPart}`;
? `считали исходящие платежи/списания по ${subjectPart}${flowPeriodPart}: ${amount}`
: `считали исходящие платежи/списания по ${subjectPart}${flowPeriodPart}`;
}
if (pilotScope === "counterparty_value_flow_query_movements_v1") {
const flow = toRecordObject(pilot?.derived_value_flow);
const amount = toNonEmptyString(flow?.total_amount_human_ru);
const flowPeriodPart = periodPartForRecap(toNonEmptyString(flow?.period_scope) ?? input.scopedDate);
return amount
? `смотрели денежный поток по контрагенту «${input.counterparty}»${periodPart}: ${amount}`
: `смотрели денежный поток по контрагенту «${input.counterparty}»${periodPart}`;
? `смотрели денежный поток по ${subjectPart}${flowPeriodPart}: ${amount}`
: `смотрели денежный поток по ${subjectPart}${flowPeriodPart}`;
}
if (pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1") {
const flow = toRecordObject(pilot?.derived_bidirectional_value_flow);
@ -121,10 +145,11 @@ function buildDiscoveryRecapFactLine(input) {
const incomingAmount = toNonEmptyString(incoming?.total_amount_human_ru);
const outgoingAmount = toNonEmptyString(outgoing?.total_amount_human_ru);
const netAmount = toNonEmptyString(flow?.net_amount_human_ru);
const flowPeriodPart = periodPartForRecap(toNonEmptyString(flow?.period_scope) ?? input.scopedDate);
if (incomingAmount && outgoingAmount && netAmount) {
return `считали нетто по деньгам с контрагентом «${input.counterparty}»${periodPart}: получили ${incomingAmount}, заплатили ${outgoingAmount}, расчетное нетто ${netAmount}`;
return `считали нетто по деньгам по ${subjectPart}${flowPeriodPart}: получили ${incomingAmount}, заплатили ${outgoingAmount}, расчетное нетто ${netAmount}`;
}
return `считали нетто по деньгам с контрагентом «${input.counterparty}»${periodPart}`;
return `считали нетто по деньгам по ${subjectPart}${flowPeriodPart}`;
}
return null;
}
@ -177,6 +202,7 @@ function buildRecapFactLine(input) {
const discoveryFact = buildDiscoveryRecapFactLine({
debug: input.debug,
counterparty: input.counterparty,
organization: input.organization,
scopedDate
});
if (discoveryFact) {
@ -259,12 +285,142 @@ function collectRecentRecapFacts(input) {
}
seen.add(fact);
facts.push(fact);
if (facts.length >= 3) {
if (facts.length >= (input.limit ?? 3)) {
break;
}
}
return facts.reverse();
}
function parseHumanMoney(value) {
const text = String(value ?? "")
.replace(/[^\d,.\-]/g, "")
.replace(/\s+/g, "")
.replace(",", ".");
if (!text) {
return null;
}
const parsed = Number(text);
return Number.isFinite(parsed) ? parsed : null;
}
function pushBusinessLine(target, value) {
const text = String(value ?? "").trim();
if (text && !target.includes(text)) {
target.push(text);
}
}
function collectBusinessEvaluationEvidence(input) {
const sessionItems = Array.isArray(input.sessionItems) ? input.sessionItems : [];
const currentOrganizationKey = normalizeRecapIdentity(input.organization);
const confirmedLines = [];
const interpretationLines = [];
let hasRanking = false;
let hasNet = false;
let moneySignalCount = 0;
for (let index = sessionItems.length - 1; index >= 0; index -= 1) {
const item = sessionItems[index];
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
if (!(0, assistantContinuityPolicy_1.isGroundedAddressDebug)(item.debug, input.toNonEmptyString)) {
continue;
}
const debugContext = (0, assistantContinuityPolicy_1.resolveAddressDebugContextFacts)(item.debug, input.toNonEmptyString);
const debugOrganizationKey = normalizeRecapIdentity(debugContext.organization);
if (currentOrganizationKey && debugOrganizationKey && debugOrganizationKey !== currentOrganizationKey) {
continue;
}
const discoveryEntry = toRecordObject(item.debug.assistant_mcp_discovery_entry_point_v1);
const bridge = toRecordObject(discoveryEntry?.bridge);
const pilot = toRecordObject(bridge?.pilot);
const pilotScope = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryPilotScope)(item.debug, input.toNonEmptyString);
if (!pilot) {
continue;
}
const rankedFlow = toRecordObject(pilot.derived_ranked_value_flow);
if (rankedFlow) {
const rankedValues = Array.isArray(rankedFlow.ranked_values) ? rankedFlow.ranked_values : [];
const leader = toRecordObject(rankedValues[0]);
const leaderName = input.toNonEmptyString(leader?.axis_value);
const leaderAmount = input.toNonEmptyString(leader?.total_amount_human_ru);
const period = input.toNonEmptyString(rankedFlow.period_scope);
const periodPart = period ? ` за ${period}` : "";
if (leaderName && leaderAmount) {
pushBusinessLine(confirmedLines, `Топ-контрагент по подтвержденному денежному срезу${periodPart}: ${leaderName} - ${leaderAmount}.`);
hasRanking = true;
moneySignalCount += 1;
}
}
const bidirectionalFlow = toRecordObject(pilot.derived_bidirectional_value_flow);
if (bidirectionalFlow) {
const incoming = toRecordObject(bidirectionalFlow.incoming_customer_revenue);
const outgoing = toRecordObject(bidirectionalFlow.outgoing_supplier_payout);
const incomingAmount = input.toNonEmptyString(incoming?.total_amount_human_ru);
const outgoingAmount = input.toNonEmptyString(outgoing?.total_amount_human_ru);
const netAmount = input.toNonEmptyString(bidirectionalFlow.net_amount_human_ru);
const period = input.toNonEmptyString(bidirectionalFlow.period_scope);
const periodPart = period ? ` за ${period}` : "";
if (incomingAmount && outgoingAmount && netAmount) {
pushBusinessLine(confirmedLines, `Денежный поток${periodPart}: получили ${incomingAmount}, заплатили ${outgoingAmount}, расчетное нетто ${netAmount}.`);
const incomingNumber = parseHumanMoney(incomingAmount);
const outgoingNumber = parseHumanMoney(outgoingAmount);
const netNumber = parseHumanMoney(netAmount);
if (incomingNumber && outgoingNumber !== null && netNumber !== null) {
const spreadPercent = Math.abs(netNumber) / Math.max(Math.abs(incomingNumber), 1);
const spreadLabel = `${Math.round(spreadPercent * 100)}%`;
if (spreadPercent < 0.1) {
pushBusinessLine(interpretationLines, `Обороты есть, но денежный спред узкий: нетто около ${spreadLabel} от входящего потока. Это не прибыль, но сигнал, что маржу надо проверять отдельно.`);
}
else if (netNumber > 0) {
pushBusinessLine(interpretationLines, `Денежный поток в проверенном срезе положительный: нетто около ${spreadLabel} от входящего потока. Это хороший cash-flow сигнал, но не доказанная прибыль.`);
}
else {
pushBusinessLine(interpretationLines, `Денежный поток в проверенном срезе отрицательный: исходящие платежи выше входящих примерно на ${spreadLabel} от входящего потока. Нужна проверка причин и структуры расходов.`);
}
}
hasNet = true;
moneySignalCount += 1;
}
}
const valueFlow = toRecordObject(pilot.derived_value_flow);
if (valueFlow) {
const amount = input.toNonEmptyString(valueFlow.total_amount_human_ru);
const period = input.toNonEmptyString(valueFlow.period_scope);
const direction = String(valueFlow.value_flow_direction ?? "");
const periodPart = period ? ` за ${period}` : "";
if (amount) {
pushBusinessLine(confirmedLines, direction === "outgoing_supplier_payout"
? `Исходящий денежный поток${periodPart}: ${amount}.`
: `Входящий денежный поток${periodPart}: ${amount}.`);
moneySignalCount += 1;
}
}
const activityPeriod = toRecordObject(pilot.derived_activity_period);
if (pilotScope === "counterparty_lifecycle_query_documents_v1" && activityPeriod) {
const duration = input.toNonEmptyString(activityPeriod.duration_human_ru);
const first = input.toNonEmptyString(activityPeriod.first_activity_date);
const latest = input.toNonEmptyString(activityPeriod.latest_activity_date);
if (duration) {
pushBusinessLine(confirmedLines, `Подтвержденная активность в 1С: примерно ${duration}${first && latest ? ` (${first} - ${latest})` : ""}.`);
}
}
if (pilotScope === "inventory_route_template_v1") {
pushBusinessLine(confirmedLines, "Есть проверенный складской/товарный срез; его можно использовать как операционный контекст, но не как финансовую прибыль.");
}
}
if (hasRanking) {
pushBusinessLine(interpretationLines, "По клиентской базе уже виден лидер, но концентрацию выручки надо проверять отдельным рейтингом и долями, а не одной строкой.");
}
if (moneySignalCount > 0 && !hasNet) {
pushBusinessLine(interpretationLines, "Денежный контур частично подтвержден, но без входящие-вс-исходящие нельзя честно говорить о чистом денежном эффекте.");
}
return {
confirmedLines: confirmedLines.slice(0, 6),
interpretationLines: interpretationLines.slice(0, 5),
hasRanking,
hasNet,
moneySignalCount
};
}
function buildAddressMemoryRecapReply(input) {
const contextFacts = (0, assistantContinuityPolicy_1.resolveAddressDebugContextFacts)(input.addressDebug, input.toNonEmptyString);
const item = contextFacts.item;
@ -309,6 +465,13 @@ function buildAddressMemoryRecapReply(input) {
"Могу продолжить по нему без переписывания контекста: поступления, платежи, нетто, документы или пояснение границ ответа."
].join(" ");
}
if (recapFacts.length > 0) {
return [
"Да, помню. В предыдущем проверенном контуре мы уже выяснили:",
...recapFacts.map((fact) => `- ${fact}.`),
"Могу продолжить от этого места: углубиться в данные, документы, движения или границы подтверждения."
].join("\n");
}
if (organization || scopedDate) {
const organizationPart = organization ? ` по компании «${organization}»` : "";
const datePart = scopedDate ? ` на ${scopedDate}` : "";
@ -326,16 +489,43 @@ function buildBroadBusinessEvaluationReply(input) {
sessionItems: input.sessionItems,
item: null,
organization,
toNonEmptyString: input.toNonEmptyString
toNonEmptyString: input.toNonEmptyString,
limit: 5
});
const organizationPart = organization ? ` по компании «${organization}»` : "";
if (recapFacts.length > 0) {
const businessEvidence = collectBusinessEvaluationEvidence({
sessionItems: input.sessionItems,
organization,
toNonEmptyString: input.toNonEmptyString
});
const hasBusinessEvidence = businessEvidence.confirmedLines.length > 0;
if (recapFacts.length > 0 || hasBusinessEvidence) {
const moneyFactCount = recapFacts.filter((fact) => /(?:денежн|нетто|поступлен|платеж|рейтинг|клиент|выруч|оборот|заплатили|получили)/iu.test(fact)).length + businessEvidence.moneySignalCount;
const hasRankingFact = businessEvidence.hasRanking ||
recapFacts.some((fact) => /(?:рейтинг|клиент|единственного клиента)/iu.test(fact));
const hasNetFact = businessEvidence.hasNet || recapFacts.some((fact) => /нетто/iu.test(fact));
const auditLines = [
moneyFactCount > 0
? "- Денежный контур уже выглядит операционно значимым: есть подтвержденные поступления, платежи или клиентские срезы."
: "- Операционная активность подтверждена, но денежный контур пока раскрыт слабо.",
hasRankingFact
? "- По клиентской базе уже есть точечные лидеры, но это еще не полноценная управленческая сегментация всей базы."
: "- Ключевых клиентов и концентрацию выручки стоит добрать отдельным рейтингом.",
hasNetFact
? "- По нетто можно обсуждать направление денежного потока, но прибыль и маржу этим не доказываем."
: "- Прибыль, маржа и качество операционки пока не доказаны: нужны расходы, себестоимость и задолженность."
];
return [
`Коротко: по тому, что мы уже подтвердили в 1С${organizationPart}, компания выглядит операционно живой, но это пока только частичная оценка бизнеса.`,
"Сейчас я опираюсь на такие подтвержденные факты:",
`Коротко: по уже подтвержденным срезам 1С${organizationPart} компания выглядит операционно живой; это предварительная оценка бизнеса, а для взрослого вывода еще нужны прибыль, маржа и долги.`,
"Что уже видно:",
...recapFacts.map((fact) => `- ${ensureSentence(fact)}`),
"Это еще не полная диагностика всего бизнеса и не вывод о прибыли: я честно суммирую только те контуры, которые мы уже проверили в диалоге.",
"Если хочешь, следующим шагом могу сузить оценку до денежного потока, долгов, НДС или ключевых контрагентов."
...(businessEvidence.confirmedLines.length > 0
? ["Подтвержденные метрики:", ...businessEvidence.confirmedLines.map((fact) => `- ${ensureSentence(fact)}`)]
: []),
"Предварительный LLM-аудит:",
...businessEvidence.interpretationLines.map((fact) => `- ${ensureSentence(fact)}`),
...auditLines,
"Что добрать для полной оценки: обороты по годам, топ клиентов, входящие/исходящие деньги, дебиторку/кредиторку, НДС и признаки маржинальности."
].join("\n");
}
return [

View File

@ -112,8 +112,17 @@ function resolveAddressLaneProtectionArbitration(input) {
const semanticDeepInvestigationHintDetected = semanticGuardHints?.deep_investigation_signal_detected === true;
const semanticAggregateShapeDetected = semanticExtraction?.query_shape === "AGGREGATE_LOOKUP" ||
semanticExtraction?.aggregation_profile === "management_profile";
const exactSupportedIntentProtectedFromDeepPreference = Boolean(supportedAddressIntentDetected &&
resolvedIntent &&
ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(resolvedIntent) &&
semanticApplyCanonicalRecommended &&
(!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed));
const unsupportedAggregateFollowupOverride = Boolean(followupContext &&
llmContractMode === "unsupported" &&
(semanticAggregateShapeDetected || !semanticApplyCanonicalRecommended) &&
!exactSupportedIntentProtectedFromDeepPreference);
const followupSemanticOverrideToDeepAllowed = Boolean(followupContext &&
!supportedAddressIntentDetected &&
(!supportedAddressIntentDetected || unsupportedAggregateFollowupOverride) &&
(rootContextOnlyFollowup ||
llmContractMode === "unsupported" ||
semanticAggregateShapeDetected ||
@ -127,11 +136,16 @@ function resolveAddressLaneProtectionArbitration(input) {
!deepAnalysisPreferenceDetected &&
!strictDeepInvestigationCueDetected &&
!semanticAggregateShapeDetected);
const unsupportedSpecificLlmIntent = Boolean(llmContractMode === "unsupported" &&
llmContractIntent &&
llmContractIntent !== "unknown");
const protectAddressLaneFromFallback = Boolean(supportedAddressRouteCandidateDetected &&
!deepAnalysisPreferenceDetected &&
(exactAddressIntentProtectedFromSemanticDeepHint ||
!semanticDeepInvestigationHintDetected ||
strictDeepInvestigationBypassAllowed));
(exactSupportedIntentProtectedFromDeepPreference ||
(!unsupportedSpecificLlmIntent &&
!deepAnalysisPreferenceDetected &&
(exactAddressIntentProtectedFromSemanticDeepHint ||
!semanticDeepInvestigationHintDetected ||
strictDeepInvestigationBypassAllowed))));
return {
supportedAddressIntentDetected,
supportedAddressRouteCandidateDetected,
@ -139,6 +153,7 @@ function resolveAddressLaneProtectionArbitration(input) {
semanticAggregateShapeDetected,
followupSemanticOverrideToDeepAllowed,
exactAddressIntentProtectedFromSemanticDeepHint,
exactSupportedIntentProtectedFromDeepPreference,
protectAddressLaneFromFallback
};
}
@ -953,6 +968,24 @@ function createAssistantRoutePolicy(deps) {
}
const metaAnswerFollowupSignal = metaSignals.metaAnswerFollowupSignal;
const answerInspectionFollowupSignal = metaSignals.answerInspectionFollowupSignal;
const customerValueRankingAddressSignal = [
rawUserMessage,
effectiveAddressUserMessage,
repairedRawUserMessage,
repairedEffectiveAddressUserMessage
].some((value) => {
const normalized = compactWhitespace(repairAddressMojibake(String(value ?? "")).toLowerCase()).replace(/ё/g, "е");
if (!normalized) {
return false;
}
if (capabilityMetaQuery || dataScopeMetaQuery) {
return false;
}
const hasRankingCue = /(?:сам(?:ый|ая|ое|ые)|топ|рейтинг|больше\s+всего|максимальн|лидер|highest|top|best)/iu.test(normalized);
const hasValueCue = /(?:доход|выруч|оборот|денег|принес|поступлен|revenue|turnover|value|money)/iu.test(normalized);
const hasCustomerCue = /(?:клиент|покупател|контрагент|customer|counterparty|кто\s+у\s+нас|кто\s+нам|кто\s+больше)/iu.test(normalized);
return hasRankingCue && hasValueCue && hasCustomerCue;
});
const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected &&
llmPreDecomposeMeta?.applied &&
llmContractMode === "address_query") ||
@ -964,6 +997,7 @@ function createAssistantRoutePolicy(deps) {
hasLooseAllTimeAddressLookupSignal(effectiveAddressUserMessage) ||
hasLooseAllTimeAddressLookupSignal(repairedRawUserMessage) ||
hasLooseAllTimeAddressLookupSignal(repairedEffectiveAddressUserMessage) ||
customerValueRankingAddressSignal ||
hasAddressFollowupContextSignal(rawUserMessage) ||
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
@ -1008,7 +1042,7 @@ function createAssistantRoutePolicy(deps) {
resolvedIntentResolution.intent === "unknown" &&
(!llmContractIntent || llmContractIntent === "unknown"));
const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint;
const protectAddressLaneFromFallback = laneProtectionArbitration.protectAddressLaneFromFallback;
const protectAddressLaneFromFallback = Boolean(laneProtectionArbitration.protectAddressLaneFromFallback || customerValueRankingAddressSignal);
const vatExplainFollowupSignal = Boolean(followupContext &&
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));

View File

@ -282,7 +282,7 @@ exports.INVENTORY_CAPABILITY_CONTRACTS = [
entry_modes: ["root_entry", "root_followup", "clarification_resume"],
transitions: ["T1", "T2", "T7"],
requiresFocusObject: false,
requiredAnchors: ["supplier"],
requiredAnchors: [],
resultShape: "supplier_to_stock_item_overlap",
answerObjectShape: "inventory_supplier_overlap",
bundleReusePolicy: "none",
@ -299,6 +299,17 @@ exports.INVENTORY_CAPABILITY_CONTRACTS = [
answerObjectShape: "inventory_sale_trace_bundle",
bundleReusePolicy: "sale_trace_bundle_preferred"
}),
inventoryExactCapability({
capability_id: "inventory_inventory_profitability_for_item",
intent_ids: ["inventory_profitability_for_item"],
entry_modes: ["selected_object_drilldown", "clarification_resume"],
transitions: ["T3", "T4", "T5", "T7"],
requiresFocusObject: true,
requiredAnchors: ["item"],
resultShape: "item_revenue_purchase_cost_spread_margin_proxy",
answerObjectShape: "inventory_profitability_bundle",
bundleReusePolicy: "sale_trace_bundle_preferred"
}),
inventoryExactCapability({
capability_id: "inventory_inventory_purchase_to_sale_chain",
intent_ids: ["inventory_purchase_to_sale_chain"],

View File

@ -117,7 +117,12 @@ function detectBroadBusinessEvaluation(text) {
if (!normalized) {
return null;
}
if (/(?:как\s+ты\s+оценишь\s+деятельност[ьи]\s+компан|оценк[аи]?\s+деятельност[ьи]\s+компан|что\s+у\s+нас\s+вообще\s+происход|где\s+главн(?:ые|ый)\s+риски|как\s+у\s+нас\s+дела\s+по\s+компан)/iu.test(normalized)) {
if (/(?:\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u0430\u0443\u0434\u0438\u0442|\u043f\u043e\u043b\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u0441\u0432\u043e\u0434\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u043a\u0430\u043a\s+\u0442\u044b\s+\u043e\u0446\u0435\u043d(?:\u0438\u0448\u044c|\u0438)\s+\u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044e|\u0438\u044f)\s+\u0432\s+\u0446\u0435\u043b\u043e\u043c|company\s+(?:analysis|overview)|business\s+(?:overview|audit)|llm[-\s]?audit|бизнес[-\s]?обзор|бизнес[-\s]?аудит)/iu.test(normalized)) {
return {
family: "broad_business_evaluation"
};
}
if (/(?:как\s+ты\s+оценишь\s+деятельност[ьи]\s+компан|оценк[аи]?\s+деятельност[ьи]\s+компан|оцени\s+(?:компан|бизнес|деятельност)|(?:полный|сводный|нормальн\w*|взросл\w*)\s+анализ\s+(?:компан|бизнес|деятельност)|проанализируй\s+(?:компан|бизнес|деятельност)|(?:что\s+думаешь|какое\s+мнение)\s+(?:о|по)\s+(?:компан|бизнес)|(?:llm[-\s]?)?аудит\s+(?:компан|бизнес)|что\s+у\s+нас\s+вообще\s+происход|где\s+главн(?:ые|ый)\s+риски|как\s+у\s+нас\s+дела\s+по\s+компан)/iu.test(normalized)) {
return {
family: "broad_business_evaluation"
};
@ -214,7 +219,7 @@ function createAssistantTurnMeaningPolicy(deps = {}) {
asked_domain_family: askedDomainFamily,
asked_action_family: askedActionFamily,
explicit_intent_candidate: explicitIntentCandidate,
explicit_entity_candidates: buildEntityCandidates(counterpartyTurnover),
explicit_entity_candidates: broadBusinessEvaluation?.family ? [] : buildEntityCandidates(counterpartyTurnover),
meaning_confidence: broadBusinessEvaluation?.family
? "medium"
: supportedIntent?.confidence ?? (counterpartyTurnover?.family ? "medium" : "low"),

View File

@ -31,6 +31,7 @@ const COMPUTE_EXACT_INTENTS = new Set<AddressIntent>([
"inventory_purchase_documents_for_item",
"inventory_supplier_stock_overlap_as_of_date",
"inventory_sale_trace_for_item",
"inventory_profitability_for_item",
"inventory_purchase_to_sale_chain",
"inventory_aging_by_purchase_date",
"customer_revenue_and_payments",
@ -91,6 +92,7 @@ function defaultCapabilityId(intent: AddressIntent): string {
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date"
) {
@ -176,8 +178,15 @@ function resolveCapabilityEnabled(intent: AddressIntent): { enabled: boolean; re
intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain"
) {
if (intent === "inventory_profitability_for_item") {
return {
enabled: true,
reason: "inventory_profitability_route_enabled"
};
}
if (intent === "inventory_purchase_to_sale_chain") {
return {
enabled: true,
@ -275,6 +284,7 @@ export function resolveShadowRouteIntent(
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date"
) {

View File

@ -149,6 +149,7 @@ export function isConfirmedBalanceIntent(intent: AddressIntent): boolean {
intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "open_contracts_confirmed_as_of_date" ||
intent === "payables_confirmed_as_of_date" ||
@ -162,6 +163,12 @@ export function resolveAddressAsOfDateBasis(
filters: AddressFilterSet,
semanticFrame?: AddressSemanticFrame | null
): AddressAsOfDateBasis | null {
if (
semanticFrame?.date_scope_kind === "implicit_current" &&
semanticFrame.date_basis_hint === "implicit_current_snapshot"
) {
return "implicit_current_snapshot";
}
const asOfDate = normalizeIsoDateHint(filters.as_of_date);
if (asOfDate) {
return "explicit_as_of_date";

View File

@ -5,6 +5,7 @@ const ACCOUNT_PATTERN = /(?:сч[её]т|счет|account)[^0-9]{0,12}(\d{2}(?:[
const ACCOUNT_REVERSE_PATTERN =
/(?:^|[\s,.;:!?()\-])(\d{2}(?:[.,]\d{1,2})?)(?=\s*(?:сч[её]т|счет|account|acct))/iu;
const LIMIT_PATTERN = /(?:\btop\b|\blimit\b|первые|топ)[\s\-_:#]*?(\d{1,3})/iu;
const VALUE_ANALYTICS_SAMPLE_LIMIT = 1000;
const COUNTERPARTY_PATTERN =
/(?:по\s+контрагенту|контрагент(?:у|а)?|по\s+контре|контра|по\s+компан(?:ии|ию|ия)|компан(?:ия|ии|ию)|по\s+организац(?:ии|ию|ия)|организац(?:ия|ии|ию)|по\s+поставщик(?:у|а)?|поставщик(?:у|а)?|по\s+клиент(?:у|а)?|клиент(?:у|а)?|по\s+покупател(?:ю|я)|покупател(?:ю|я)|по\s+партнер(?:у|а)?|партнер(?:у|а)?|by\s+counterparty|counterparty|by\s+company|company|by\s+supplier|supplier|by\s+vendor|vendor|by\s+customer|customer|by\s+client|client|by\s+partner|partner)\s+([^\r\n,.;:]+)/iu;
const CONTRACT_PATTERN =
@ -181,12 +182,14 @@ function toIsoDate(year: number, month: number, day: number): string | null {
return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}
function hasImplicitCurrentAsOfDateCue(text: string): boolean {
return /\b(сегодня|на\s+сегодня|на\s+текущ(?:ую|ая|ий|ее|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодняшн(?:юю|ий|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+текущ(?:ий|ую)\s+момент|today|as\s+of\s+today|current\s+date|as\s+of\s+current\s+date)\b/i.test(
text
);
}
function extractAsOfDate(text: string): string | undefined {
if (
/\b(сегодня|на\s+сегодня|на\s+текущ(?:ую|ая|ий|ее|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодняшн(?:юю|ий|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+текущ(?:ий|ую)\s+момент|today|as\s+of\s+today|current\s+date|as\s+of\s+current\s+date)\b/i.test(
text
)
) {
if (hasImplicitCurrentAsOfDateCue(text)) {
return new Date().toISOString().slice(0, 10);
}
@ -751,6 +754,9 @@ function isLowQualityCounterpartyAnchorValue(rawValue: string): boolean {
"ноябрь",
"декабрь"
]);
const isLowQualityTimeToken = (token: string): boolean =>
lowQualityTimeTokens.has(token) ||
/^(?:январ|феврал|март|апрел|ма(?:й|я|е)|июн|июл|август|сентябр|октябр|ноябр|декабр)/iu.test(token);
const lowQualityGenericTokens = new Set([
"деньги",
"денег",
@ -776,7 +782,7 @@ function isLowQualityCounterpartyAnchorValue(rawValue: string): boolean {
const meaningfulNonTemporalTokens = tokens.filter(
(token) =>
isLikelyCounterpartyToken(token) &&
!lowQualityTimeTokens.has(token) &&
!isLowQualityTimeToken(token) &&
!/^(?:19|20)\d{2}$/.test(token)
);
if (meaningfulNonTemporalTokens.length === 0 && hasTemporalCue) {
@ -785,7 +791,7 @@ function isLowQualityCounterpartyAnchorValue(rawValue: string): boolean {
const meaningfulNonGenericTokens = tokens.filter(
(token) =>
isLikelyCounterpartyToken(token) &&
!lowQualityTimeTokens.has(token) &&
!isLowQualityTimeToken(token) &&
!lowQualityGenericTokens.has(token) &&
!/^(?:19|20)\d{2}$/.test(token)
);
@ -1302,6 +1308,9 @@ function isTemporalWarehousePhrase(candidate: string): boolean {
.toLowerCase()
.replace(/ё/g, "е")
.trim();
if (/^(?:в|на)?\s*(?:сейчас|сегодня|текущ(?:ий|ую|ем|его)\s+момент|данн(?:ый|ую|ом|ого)\s+момент)$/iu.test(normalized)) {
return true;
}
if (/^(?:на\s+)?(?:эту|ту|текущ(?:ую|ая|ий|ее|ей)|сегодняшн(?:юю|ая|ий|ее|ей))(?:\s+же)?\s+дат(?:у|а|е|ой)$/iu.test(normalized)) {
return true;
}
@ -1355,6 +1364,12 @@ function isLowQualityWarehouseAnchorValue(rawValue: string): boolean {
"лежали",
"на",
"по",
"компания",
"компании",
"компанию",
"организация",
"организации",
"организацию",
"складе",
"складу",
"складом",
@ -1373,7 +1388,11 @@ function isLowQualityWarehouseAnchorValue(rawValue: string): boolean {
if (tokens.length === 0) {
return true;
}
const meaningfulTokens = tokens.filter((token) => !lowQualityTokens.has(token) && token.length > 1);
const isLowQualityWarehouseToken = (token: string): boolean =>
lowQualityTokens.has(token) ||
/^(?:19|20)\d{2}$/.test(token) ||
/^(?:январ|феврал|март|апрел|ма(?:й|я|е)|июн|июл|август|сентябр|октябр|ноябр|декабр)/iu.test(token);
const meaningfulTokens = tokens.filter((token) => !isLowQualityWarehouseToken(token) && token.length > 1);
if (meaningfulTokens.length === 0) {
return true;
}
@ -1453,16 +1472,18 @@ function extractInventoryWarehouseAnchor(text: string): string | undefined {
function extractInventorySupplierAnchor(text: string): string | undefined {
const match = String(text ?? "").match(
/(?:от\s+поставщика|у\s+поставщика|поставщика|поставщику)\s+([^\r\n?]+?)(?=$|[?])/iu
/(?:от\s+поставщика|у\s+поставщика|поставщик(?:а|у|ом)?|supplier|vendor)\s+([^\r\n?]+?)(?=$|[?])/iu
);
if (!match?.[1]) {
return undefined;
}
const candidate = cleanupAnchorValue(
cleanupAnchorValue(String(match[1])).replace(
/\s+(?:сейчас|на\s+склад(?:е|у|ом)?|на\s+дату|по\s+состоянию\s+на\s+дату|котор(?:ый|ые|ых)|куплен(?:ы|а|о)?|были|был|лежат|лежит|еще|ещё|организац\w*|компани\w*).*$/iu,
""
)
cleanupAnchorValue(String(match[1]))
.replace(
/\s+(?:сейчас|на\s+склад(?:е|у|ом)?|на\s+дату|по\s+состоянию\s+на\s+дату|котор(?:ый|ые|ых)|куплен(?:ы|а|о)?|были|был|лежат|лежит|еще|ещё|организац\w*|компани\w*).*$/iu,
""
)
.replace(/\s*(?:->|=>|)\s*(?:товар|позици|номенклатур|item|product|sku)[\s\S]*$/iu, "")
);
if (
!candidate ||
@ -1595,7 +1616,7 @@ function resolveSemanticDateScopeKind(
}
function resolveSemanticDateBasisHint(filters: AddressFilterSet, warnings: string[]): AddressSemanticFrame["date_basis_hint"] {
if (warnings.includes("as_of_date_defaulted_today")) {
if (warnings.includes("as_of_date_defaulted_today") || warnings.includes("as_of_date_from_implicit_current_phrase")) {
return "implicit_current_snapshot";
}
const hasAsOfDate = typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0;
@ -1685,6 +1706,14 @@ function buildSemanticFrame(
};
}
function shouldExpandSampleForValueAnalytics(intent: AddressIntent): boolean {
return (
intent === "customer_revenue_and_payments" ||
intent === "supplier_payouts_profile" ||
intent === "contract_usage_and_value"
);
}
export function extractAddressFilters(userMessage: string, intent: AddressIntent): AddressFilterExtraction {
const rawText = String(userMessage ?? "").trim();
const text = normalizeMojibakeString(rawText);
@ -1710,6 +1739,7 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
}
}
const warnings: string[] = [];
const implicitCurrentAsOfDateCue = hasImplicitCurrentAsOfDateCue(text);
const explicitAsOfDate = extractAsOfDate(text);
const explicitAsOfDateWithCue = extractAsOfDateWithCue(text);
@ -1732,6 +1762,12 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
filters.limit = Math.min(200, Math.trunc(parsed));
}
}
if (shouldExpandSampleForValueAnalytics(intent)) {
const currentLimit =
typeof filters.limit === "number" && Number.isFinite(filters.limit) ? Math.max(1, Math.trunc(filters.limit)) : 0;
filters.limit = Math.max(currentLimit, VALUE_ANALYTICS_SAMPLE_LIMIT);
warnings.push("value_analytics_sample_limit_expanded");
}
if (isInventoryItemAnchoredIntent(intent)) {
const itemAnchor = extractInventoryItemAnchor(text);
@ -1756,6 +1792,13 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
filters.counterparty = supplierAnchor;
}
}
if (intent === "inventory_purchase_to_sale_chain") {
const supplierAnchor = asksForInventorySupplierIdentity(text) ? undefined : extractInventorySupplierAnchor(text);
if (supplierAnchor) {
filters.counterparty = supplierAnchor;
warnings.push("supplier_anchor_derived_for_inventory_documentary_chain");
}
}
const allowGenericCounterpartyAnchor = !isInventoryTraceIntent(intent);
const counterpartyMatch = allowGenericCounterpartyAnchor ? text.match(COUNTERPARTY_PATTERN) : null;
@ -1923,6 +1966,9 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
if (usesAsOfPrimaryWindow(intent) && explicitAsOfDate) {
filters.as_of_date = explicitAsOfDate;
if (implicitCurrentAsOfDateCue && !warnings.includes("as_of_date_from_implicit_current_phrase")) {
warnings.push("as_of_date_from_implicit_current_phrase");
}
const periodWasDerivedHeuristically =
warnings.includes("period_derived_from_month_phrase") ||
warnings.includes("period_derived_from_year_range_phrase") ||
@ -1941,6 +1987,9 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
const asOfDate = extractAsOfDate(text);
if (asOfDate) {
filters.as_of_date = asOfDate;
if (implicitCurrentAsOfDateCue && !warnings.includes("as_of_date_from_implicit_current_phrase")) {
warnings.push("as_of_date_from_implicit_current_phrase");
}
}
}

View File

@ -1732,6 +1732,18 @@ function hasInventoryPurchaseDocumentsSignalV2(text: string): boolean {
}
function hasInventorySaleTraceSignalV2(text: string): boolean {
const value = String(text ?? "");
const hasPlainItemCue =
/(?:\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|\u043f\u043e\u0437\u0438\u0446|\u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0438|sku|item|product)/iu.test(
value
);
const hasPlainTraceCue =
/(?:\u043a\u043e\u043c\u0443\s+(?:\u0432\s+\u0438\u0442\u043e\u0433\u0435\s+)?(?:\u043c\u044b\s+)?(?:\u043f\u0440\u043e\u0434\u0430\u043b\u0438|\u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438|\u0432\u043f\u0430\u0440\u0438\u043b\u0438)|\u043a\u043e\u043c\u0443\s+(?:\u0431\u044b\u043b[\u0430\u0438\u043e]?|\u0431\u044b\u043b\u0438)?\s*(?:\u043f\u0440\u043e\u0434\u0430\u043d|\u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d)|\u043a\u0442\u043e\s+\u043a\u0443\u043f\u0438\u043b|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|buyer|sale\s+trace|trace\s+of\s+sale)/iu.test(
value
);
if (hasPlainItemCue && (hasPlainTraceCue || hasInventorySaleCue(value))) {
return true;
}
const hasItemCue = /(?:Совар|номенклаССѓСЂ|sku|item|product|РїРѕР·РёСРё(?:СЏ|СЋ|Рё)|РїСЂРѕРґСѓРєСРё(?:СЏ|СЋ|Рё))/iu.test(text);
const hasTraceCue =
/(?:РєРѕРјСѓ\s+(?:РІ\s+РёСРѕРіРµ\s+)?(?:РјС\s+)?продали|РєРѕРјСѓ\s+Р±СР»\s+продан|РєСѓРґР°\s+(?:РІ\s+РёСРѕРіРµ\s+)?(?:РјС\s+)?продали(?:\s+(?:СЌСРѕ|его|Совар|РїРѕР·РёСРёСЋ))?|РєСѓРґР°\s+(?:Р±Сла\s+)?реализована\s+(?:РїРѕР·РёСРёСЏ|номенклаССѓСЂР°|РїСЂРѕРґСѓРєСРёСЏ)|РєСРѕ\s+РєСѓРїРёР»|buyer|sale\s+trace|trace\s+of\s+sale|Серез\s+какие\s+докуменСС\s+РїСЂРѕС[РµС]Р»\s+РїСѓССЊ\s+Совара|закупк.*склад.*РїСЂРѕРґР°Р|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*warehouse\s*->\s*sale|purchase\s*->\s*stock\s*->\s*sale)/iu.test(
@ -1766,6 +1778,15 @@ function hasInventoryAgingSignal(text: string): boolean {
}
function hasInventoryPurchaseToSaleChainSignal(text: string): boolean {
const value = String(text ?? "");
const hasPlainItemCue = /(?:товар|номенклатур|позици|sku|item|product)/iu.test(value);
const hasPlainChainCue =
/(?:закупк[а-яё]*\s*->\s*склад\s*->\s*продаж|закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|через\s+какие\s+документы\s+прош[её]л\s+путь|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|цепочк[а-яё]*\s+движен|документально\s+подтвержденн[а-яё]*\s+цепочк|supplier\s*->\s*item\s*->\s*(?:buyer|customer)|supplier\s+to\s+buyer|supplier\s+to\s+item\s+to\s+buyer|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale)/iu.test(
value
) || value.includes("->");
if (hasPlainItemCue && hasPlainChainCue) {
return true;
}
const hasItemCue = /(?:Совар|номенклаССѓСЂ|sku|item|product)/iu.test(text);
const hasChainCue =
/(?:закупк.*склад.*РїСЂРѕРґР°Р|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|закупка\s*->\s*склад\s*->\s*РїСЂРѕРґР°РР°|СепоСРє[аи]\s+РґРІРёРен|докуменСально\s+РїРѕРґСверРденн\w+\s+СепоСРє|supplier\s*->\s*item\s*->\s*(?:buyer|customer)|supplier\s+to\s+buyer|supplier\s+to\s+item\s+to\s+buyer)/iu.test(
@ -2030,6 +2051,17 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio
const hasRankingCue = /(?:топ|ранк|сам(?:ый|ая|ое|ые)|больше\s+всего|наибольш|крупн|жирн|max|top|rank)/iu.test(
normalized
);
const hasInventoryPurchaseToSaleDocumentChainCue =
/(?:закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|->\s*(?:склад|warehouse|stock)\s*->\s*(?:продаж|sale))/iu.test(
normalized
) && /(?:товар|позици|номенклатур|sku|item|product)/iu.test(normalized);
if (hasInventoryPurchaseToSaleDocumentChainCue) {
return unicodeBridgeResolution(
"inventory_purchase_to_sale_chain",
"high",
"unicode_inventory_purchase_to_sale_chain_bridge_signal_detected"
);
}
const hasOpenItemsAccountCue =
/(?:хвост|долг|незакрыт|вис)/iu.test(normalized) &&
@ -2736,7 +2768,23 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
const unicodeAddressIntent = resolveUnicodeAddressIntentBridge(currentTurnBridgeText);
if (unicodeAddressIntent) {
return unicodeAddressIntent;
const reasons = [...unicodeAddressIntent.reasons];
if (currentTurnBridgeText !== bridgeText && !reasons.includes("current_turn_noise_normalized")) {
reasons.push("current_turn_noise_normalized");
}
if (
unicodeAddressIntent.intent === "customer_revenue_and_payments" &&
[text, repairedText, turnNoiseNormalizedBridgeText, currentTurnBridgeText].some((sample) =>
hasSpecificCounterpartyRevenueBridgeSignal(sample)
) &&
!reasons.includes("specific_counterparty_revenue_bridge_signal_detected")
) {
reasons.push("specific_counterparty_revenue_bridge_signal_detected");
}
return {
...unicodeAddressIntent,
reasons
};
}
const hasLooseVatPayableBridge =

View File

@ -146,13 +146,21 @@ function hasInventoryProvenanceSignalV2(text: string): boolean {
}
function hasInventoryPurchaseDateSignal(text: string): boolean {
const value = String(text ?? "");
const hasItemCue =
/(?:Совар|номенклаССѓСЂ|sku|item|product)/iu.test(text) || hasSelectedObjectInventoryCue(text);
const hasPurchaseDateCue =
/(?:РєРѕРіРґР°\s+(?:примерно\s+)?(?:РјС\s+)?купили|РєРѕРіРґР°\s+Р±СР»\s+куплен|РєРѕРіРґР°\s+куплен|РґР°СР°\s+закупк|purchase\s+date)/iu.test(
text
/(?:\u0442\u043e\u0432\u0430\u0440|\u043f\u043e\u0437\u0438\u0446|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|sku|item|product)/iu.test(
value
) ||
/(?:РєРѕРіРґР°\s+Р±СР»(?:Р°|Рё|Рѕ)?\s+закупк\w*|РєРѕРіРґР°\s+закупк\w*)/iu.test(text);
/(?:Совар|номенклаССѓСЂ|sku|item|product)/iu.test(value) ||
hasSelectedObjectInventoryCue(value);
const hasPurchaseDateCue =
/(?:\u043a\u043e\u0433\u0434\u0430\s+(?:\u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e\s+)?(?:\u043c\u044b\s+)?\u043a\u0443\u043f\u0438\u043b\u0438|\u043a\u043e\u0433\u0434\u0430\s+\u0431\u044b\u043b(?:\u0430|\u0438|\u043e)?\s+\u043a\u0443\u043f\u043b\u0435\u043d|\u043a\u043e\u0433\u0434\u0430\s+\u043a\u0443\u043f\u043b\u0435\u043d|\u0434\u0430\u0442\u0430\s+\u0437\u0430\u043a\u0443\u043f\u043a|purchase\s+date)/iu.test(
value
) ||
/(?:РєРѕРіРґР°\s+(?:примерно\s+)?(?:РјС\s+)?купили|РєРѕРіРґР°\s+Р±СР»\s+куплен|РєРѕРіРґР°\s+куплен|РґР°СР°\s+закупк|purchase\s+date)/iu.test(
value
) ||
/(?:РєРѕРіРґР°\s+Р±СР»(?:Р°|Рё|Рѕ)?\s+закупк\w*|РєРѕРіРґР°\s+закупк\w*)/iu.test(value);
return hasItemCue && hasPurchaseDateCue;
}
@ -177,7 +185,7 @@ function hasInventorySaleTraceSignalV2(text: string): boolean {
const value = String(text ?? "");
const hasPlainItemCue = /(?:товар|номенклатур|позици|продукци|sku|item|product)/iu.test(value);
const hasPlainTraceCue =
/(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?(?:продали|реализовали|впарили)|кому\s+(?:был[аио]?|были)?\s*реализован|кто\s+купил|покупател|buyer|sale\s+trace|trace\s+of\s+sale)/iu.test(
/(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?(?:продали|реализовали|впарили)|кому\s+(?:был[аио]?|были)?\s*(?:продан|реализован)|кто\s+купил|покупател|buyer|sale\s+trace|trace\s+of\s+sale)/iu.test(
value
);
if (hasPlainItemCue && hasPlainTraceCue) {

View File

@ -86,6 +86,8 @@ interface NormalizedAddressRow {
item?: string | null;
warehouse?: string | null;
organization?: string | null;
counterparty?: string | null;
contract?: string | null;
}
interface AddressTryHandleOptions {
@ -350,6 +352,21 @@ function deriveTaxQuarterWindowForDate(value: unknown): { period_from: string; p
};
}
function deriveMonthWindowForDate(value: unknown): { period_from: string; period_to: string } | null {
const isoDate = normalizeIsoDateForQuery(value);
if (!isoDate) {
return null;
}
const match = isoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
return {
period_from: `${match[1]}-${match[2]}-01`,
period_to: isoDate
};
}
function toDateTimeExprForQuery(isoDate: string): string | null {
const match = String(isoDate ?? "").match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
@ -1446,6 +1463,8 @@ function toNormalizedRows(rows: Array<Record<string, unknown>>): NormalizedAddre
row.organization_name,
row.ОрганизацияПредставление
);
const counterparty = firstNonEmptyString(row.Контрагент, row.Counterparty, row.counterparty);
const contract = firstNonEmptyString(row.Договор, row.Contract, row.contract);
const analytics = collectAnalyticsStrings(row);
return {
@ -1458,7 +1477,9 @@ function toNormalizedRows(rows: Array<Record<string, unknown>>): NormalizedAddre
quantity,
item,
warehouse,
organization
organization,
counterparty,
contract
};
})
.filter((item) => Boolean(item.period || item.registrator));
@ -1526,6 +1547,10 @@ function formatMoneyRubForReply(value: number): string {
}
function extractContractNameFromNormalizedRow(row: NormalizedAddressRow): string | null {
const explicitContract = firstNonEmptyString(row.contract);
if (explicitContract) {
return explicitContract;
}
for (const token of row.analytics) {
const normalized = String(token ?? "").trim();
if (!normalized) {
@ -1898,6 +1923,11 @@ function applyPreExecutionOrganizationScopeGrounding(input: {
]);
const resolvedOrganizationFromMessage = resolveOrganizationSelectionFromMessage(input.userMessage, candidateOrganizations);
const referentialOrganizationScopeDetected = hasReferentialOrganizationScopeSignal(input.userMessage);
const counterpartyAnchorProtectsOrganizationScope =
input.semanticFrame?.anchor_kind === "counterparty" &&
typeof input.filters.counterparty === "string" &&
input.filters.counterparty.trim().length > 0 &&
!referentialOrganizationScopeDetected;
if (
!input.filters.organization &&
@ -1916,6 +1946,7 @@ function applyPreExecutionOrganizationScopeGrounding(input: {
if (
resolvedOrganizationFromMessage &&
(!input.filters.organization || input.semanticFrame?.anchor_kind === "organization") &&
!counterpartyAnchorProtectsOrganizationScope &&
!sameNormalizedOrganizationScope(input.filters.organization ?? null, resolvedOrganizationFromMessage)
) {
input.filters.organization = resolvedOrganizationFromMessage;
@ -2377,6 +2408,12 @@ function hasExplicitPeriodWindow(filters: AddressFilterSet): boolean {
);
}
function asksForUnresolvedInventorySupplierLink(userMessage: string | null | undefined): boolean {
return /(?:\u0431\u0435\u0437\s+\u043f\u043e\u043d\u044f\u0442\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0431\u0435\u0437\s+(?:\u044f\u0432\u043d[^\s]*\s+)?\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043d\u0435\s+\u0438\u043c\u0435\u044e\u0442\s+\u044f\u0432\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043d\u0435\u0442\s+\u044f\u0432\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|unresolved\s+supplier\s+link)/iu.test(
String(userMessage ?? "")
);
}
function canAutoBroadenPeriodWindow(intent: AddressIntent, filters: AddressFilterSet): boolean {
const hasRecoverableAsOfOnlyWindow =
!hasExplicitPeriodWindow(filters) &&
@ -2398,6 +2435,7 @@ function canAutoBroadenPeriodWindow(intent: AddressIntent, filters: AddressFilte
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date"
);
@ -2409,6 +2447,7 @@ function shouldBoostAutoBroadenedLimit(intent: AddressIntent): boolean {
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date"
);
@ -2417,6 +2456,7 @@ function shouldBoostAutoBroadenedLimit(intent: AddressIntent): boolean {
function shouldClearAsOfDateForHistoryRecovery(intent: AddressIntent): boolean {
return (
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain"
);
}
@ -2426,13 +2466,17 @@ function shouldDetachLifecycleExecutionFromSnapshotContext(
reasons: string[]
): boolean {
if (
intent !== "inventory_supplier_stock_overlap_as_of_date" &&
intent !== "inventory_sale_trace_for_item" &&
intent !== "inventory_profitability_for_item" &&
intent !== "inventory_purchase_to_sale_chain"
) {
return false;
}
return (
reasons.includes("period_window_semantic_from_inventory_snapshot_context") ||
reasons.includes("period_window_semantic_from_inventory_as_of_month") ||
reasons.includes("as_of_date_from_followup_context") ||
reasons.includes("period_from_followup_context") ||
reasons.includes("as_of_date_from_open_items_followup_context")
@ -3506,6 +3550,91 @@ export class AddressQueryService {
});
const knownOrganizations = mergeKnownOrganizations(options.knownOrganizations ?? []);
const activeOrganization = normalizeOrganizationScopeValue(options.activeOrganization ?? null);
const previousOrganizationFromContext = normalizeOrganizationScopeValue(followupContext?.previous_filters?.organization ?? null);
const chainCounterpartyAnchor = toNonEmptyFilterValue(filters.extracted_filters.counterparty);
const chainOrganizationAnchor = toNonEmptyFilterValue(filters.extracted_filters.organization);
if (
intent.intent === "inventory_purchase_to_sale_chain" &&
chainCounterpartyAnchor &&
chainOrganizationAnchor &&
sameOrganizationEntityReference(chainOrganizationAnchor, chainCounterpartyAnchor)
) {
delete filters.extracted_filters.organization;
const restoredOrganization = activeOrganization ?? previousOrganizationFromContext;
if (restoredOrganization && !sameOrganizationEntityReference(restoredOrganization, chainCounterpartyAnchor)) {
filters.extracted_filters.organization = restoredOrganization;
if (!filters.warnings.includes("organization_restored_from_inventory_chain_context")) {
filters.warnings.push("organization_restored_from_inventory_chain_context");
}
if (!baseReasons.includes("organization_restored_from_inventory_chain_context")) {
baseReasons.push("organization_restored_from_inventory_chain_context");
}
} else {
if (!filters.warnings.includes("organization_cleared_from_inventory_chain_counterparty_anchor")) {
filters.warnings.push("organization_cleared_from_inventory_chain_counterparty_anchor");
}
if (!baseReasons.includes("organization_cleared_from_inventory_chain_counterparty_anchor")) {
baseReasons.push("organization_cleared_from_inventory_chain_counterparty_anchor");
}
}
}
if (
intent.intent === "inventory_supplier_stock_overlap_as_of_date" &&
(followupContext?.root_filters || followupContext?.previous_filters) &&
!toNonEmptyFilterValue(filters.extracted_filters.period_from) &&
!toNonEmptyFilterValue(filters.extracted_filters.period_to) &&
!/(?:за\s+вс[её]\s+время|за\s+любой\s+период|all[\s-]?time|all\s+periods?)/iu.test(userMessage)
) {
const snapshotContextFilters =
followupContext?.root_filters &&
(toNonEmptyFilterValue(followupContext.root_filters.period_from) ||
toNonEmptyFilterValue(followupContext.root_filters.period_to))
? followupContext.root_filters
: followupContext?.previous_filters;
const previousPeriodFrom = toNonEmptyFilterValue(snapshotContextFilters?.period_from);
const previousPeriodTo = toNonEmptyFilterValue(snapshotContextFilters?.period_to);
if (previousPeriodFrom || previousPeriodTo) {
filters.extracted_filters = {
...filters.extracted_filters,
...(previousPeriodFrom ? { period_from: previousPeriodFrom } : {}),
...(previousPeriodTo ? { period_to: previousPeriodTo } : {})
};
if (!toNonEmptyFilterValue(filters.extracted_filters.as_of_date)) {
const inheritedAsOfDate =
toNonEmptyFilterValue(snapshotContextFilters?.as_of_date) ?? previousPeriodTo ?? previousPeriodFrom;
if (inheritedAsOfDate) {
filters.extracted_filters.as_of_date = inheritedAsOfDate;
}
}
if (!filters.warnings.includes("period_window_semantic_from_inventory_snapshot_context")) {
filters.warnings.push("period_window_semantic_from_inventory_snapshot_context");
}
if (!baseReasons.includes("period_window_semantic_from_inventory_snapshot_context")) {
baseReasons.push("period_window_semantic_from_inventory_snapshot_context");
}
}
}
if (
intent.intent === "inventory_supplier_stock_overlap_as_of_date" &&
!toNonEmptyFilterValue(filters.extracted_filters.period_from) &&
!toNonEmptyFilterValue(filters.extracted_filters.period_to) &&
asksForUnresolvedInventorySupplierLink(userMessage) &&
!/(?:Р·Р°\s+РІСЃ[РµС]\s+время|Р·Р°\s+любоР\s+период|all[\s-]?time|all\s+periods?)/iu.test(userMessage)
) {
const monthWindow = deriveMonthWindowForDate(filters.extracted_filters.as_of_date);
if (monthWindow) {
filters.extracted_filters = {
...filters.extracted_filters,
...monthWindow
};
if (!filters.warnings.includes("period_window_semantic_from_inventory_as_of_month")) {
filters.warnings.push("period_window_semantic_from_inventory_as_of_month");
}
if (!baseReasons.includes("period_window_semantic_from_inventory_as_of_month")) {
baseReasons.push("period_window_semantic_from_inventory_as_of_month");
}
}
}
if (
isOrganizationScopedValueFlowIntent(intent.intent) &&
hasExplicitSingleOrganizationValueFlowScopeRequest(userMessage) &&
@ -3678,6 +3807,7 @@ export class AddressQueryService {
const detachedExecutionFilters: AddressFilterSet = { ...executionFilters };
let periodDetached = false;
let asOfDetached = false;
const keepAsOfDateForInventorySnapshotOverlap = intent.intent === "inventory_supplier_stock_overlap_as_of_date";
if (
toNonEmptyFilterValue(detachedExecutionFilters.period_from) ||
toNonEmptyFilterValue(detachedExecutionFilters.period_to)
@ -3686,7 +3816,7 @@ export class AddressQueryService {
delete detachedExecutionFilters.period_to;
periodDetached = true;
}
if (toNonEmptyFilterValue(detachedExecutionFilters.as_of_date)) {
if (!keepAsOfDateForInventorySnapshotOverlap && toNonEmptyFilterValue(detachedExecutionFilters.as_of_date)) {
delete detachedExecutionFilters.as_of_date;
asOfDetached = true;
}
@ -4231,6 +4361,20 @@ export class AddressQueryService {
: anchor.anchor_type === "contract" && anchor.anchor_value_resolved
? { ...executionFilters, contract: anchor.anchor_value_resolved }
: executionFilters;
if (
intent.intent === "inventory_purchase_to_sale_chain" &&
toNonEmptyFilterValue(filtersForMatching.item) &&
toNonEmptyFilterValue(filtersForMatching.counterparty)
) {
filtersForMatching = { ...filtersForMatching };
delete filtersForMatching.counterparty;
if (!filters.warnings.includes("inventory_chain_counterparty_anchor_kept_for_verification")) {
filters.warnings.push("inventory_chain_counterparty_anchor_kept_for_verification");
}
if (!baseReasons.includes("inventory_chain_counterparty_anchor_kept_for_verification")) {
baseReasons.push("inventory_chain_counterparty_anchor_kept_for_verification");
}
}
const accountScopeAudit = buildAccountScopeAudit({
intent: intent.intent,
filters: filtersForMatching,

View File

@ -857,6 +857,17 @@ const BASE_RECIPES: AddressRecipeDefinition[] = [
account_scope_mode: "strict",
query_template: "inventory_sale_trace_profile"
},
{
recipe_id: "address_inventory_trading_margin_proxy_for_organization_v1",
intent: "inventory_trading_margin_proxy_for_organization",
purpose: "Trace organization-period purchase and sale document rows and derive bounded trading revenue, purchase-cost proxy, spread, and margin proxy",
required_filters: [],
optional_filters: ["period_from", "period_to", "organization", "warehouse", "limit", "sort"],
default_limit: 600,
account_scope: ["41.01"],
account_scope_mode: "strict",
query_template: "inventory_trading_margin_proxy_profile"
},
{
recipe_id: "address_inventory_purchase_to_sale_chain_v1",
intent: "inventory_purchase_to_sale_chain",
@ -868,6 +879,17 @@ const BASE_RECIPES: AddressRecipeDefinition[] = [
account_scope_mode: "strict",
query_template: "inventory_purchase_to_sale_chain_profile"
},
{
recipe_id: "address_inventory_profitability_for_item_v1",
intent: "inventory_profitability_for_item",
purpose: "Trace purchase and sale document rows for one inventory item and derive bounded revenue, purchase-cost proxy, spread, and margin",
required_filters: ["item"],
optional_filters: ["as_of_date", "period_from", "period_to", "organization", "warehouse", "limit", "sort"],
default_limit: 600,
account_scope: ["41.01"],
account_scope_mode: "strict",
query_template: "inventory_profitability_profile"
},
{
recipe_id: "address_inventory_aging_by_purchase_date_v1",
intent: "inventory_aging_by_purchase_date",
@ -1334,6 +1356,29 @@ function buildInventoryPurchaseDocumentQuery(filters: AddressFilterSet, resolved
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
}
function stripTrailingOrderBy(query: string): string {
return String(query ?? "").replace(/\r?\nУПОРЯДОЧИТЬ ПО[\s\S]*$/u, "").trimEnd();
}
function removeTopLimit(query: string): string {
return String(query ?? "").replace(/ВЫБРАТЬ ПЕРВЫЕ\s+\d+/u, "ВЫБРАТЬ");
}
function buildInventoryPurchaseToSaleDocumentQuery(filters: AddressFilterSet, resolvedLimit: number): string {
const purchaseQuery = removeTopLimit(stripTrailingOrderBy(buildInventoryPurchaseDocumentQuery(filters, resolvedLimit)));
const saleQuery = removeTopLimit(stripTrailingOrderBy(buildInventorySaleDocumentQuery(filters, resolvedLimit)));
return [
purchaseQuery,
"",
"ОБЪЕДИНИТЬ ВСЕ",
"",
saleQuery,
"",
"УПОРЯДОЧИТЬ ПО",
` Период ${resolveOrderDirection(filters.sort)}`
].join("\n");
}
export function buildCounterpartyPurchaseDocumentQuery(filters: AddressFilterSet, resolvedLimit: number): string {
const goodsCounterpartyCondition = buildCounterpartyReferenceCondition(filters, ["Товары.Ссылка.Контрагент"]);
const servicesCounterpartyCondition = buildCounterpartyReferenceCondition(filters, ["Услуги.Ссылка.Контрагент"]);
@ -1404,6 +1449,8 @@ function maxLimitForIntent(intent: AddressIntent): number {
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_trading_margin_proxy_for_organization" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date" ||
intent === "open_contracts_confirmed_as_of_date" ||
@ -1628,9 +1675,13 @@ export function buildAddressRecipePlan(
: recipe.query_template === "inventory_supplier_stock_overlap_profile"
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
: recipe.query_template === "inventory_sale_trace_profile"
? buildInventoryMovementQuery(filters, resolvedLimit, "kt")
? buildInventorySaleDocumentQuery(filters, resolvedLimit)
: recipe.query_template === "inventory_profitability_profile"
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
: recipe.query_template === "inventory_trading_margin_proxy_profile"
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
: recipe.query_template === "inventory_purchase_to_sale_chain_profile"
? buildInventoryMovementQuery(filters, resolvedLimit, "either")
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
: recipe.query_template === "inventory_aging_by_purchase_date_profile"
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
: recipe.query_template === "contracts_by_counterparty_profile"

View File

@ -29,6 +29,8 @@ export interface ComposeStageRow {
item?: string | null;
warehouse?: string | null;
organization?: string | null;
counterparty?: string | null;
contract?: string | null;
}
export interface VatDirectSourceProbeItem {
@ -1098,6 +1100,18 @@ function extractInventoryCounterpartyCandidates(row: ComposeStageRow, excludedTo
}
candidates.push(normalized);
}
const explicitCounterparty = normalizeCounterpartyDisplayLabel(row.counterparty);
const explicitComparable = normalizeEntityToken(explicitCounterparty);
if (
explicitCounterparty &&
explicitComparable &&
explicitComparable !== itemToken &&
explicitComparable !== warehouseToken &&
explicitComparable !== organizationToken &&
!excludedComparableTokens.includes(explicitComparable)
) {
candidates.unshift(explicitCounterparty);
}
return uniqueStrings(candidates);
}
@ -1156,6 +1170,7 @@ function summarizeInventoryTraceRows(rows: ComposeStageRow[], excludedCounterpar
function formatInventoryTraceRows(rows: ComposeStageRow[], limit = 10, excludedCounterpartyTokens: string[] = []): string[] {
return rows.slice(0, limit).map((row, index) => {
const parties = extractInventoryCounterpartyCandidates(row, excludedCounterpartyTokens);
const item = extractInventoryItemName(row);
const warehouse = extractInventoryWarehouseName(row);
const organization = extractInventoryOrganizationName(row);
const amount =
@ -1165,6 +1180,9 @@ function formatInventoryTraceRows(rows: ComposeStageRow[], limit = 10, excludedC
`дата: ${inventoryTraceDateLabel(row.period)}`,
`сумма: ${amount}`
];
if (item) {
parts.push(`товар: ${item}`);
}
if (warehouse) {
parts.push(`склад: ${warehouse}`);
}

View File

@ -787,15 +787,24 @@ export function composeCounterpartyAnalyticsReply(
}
const visible = rankedByTotal.slice(0, limit);
const heading = isSupplier
? `Топ-${visible.length} поставщиков по сумме выплат:`
: `Топ-${visible.length} заказчиков по сумме поступлений:`;
const singleCandidateOnly = rankedByTotal.length === 1;
const heading = singleCandidateOnly
? isSupplier
? "Найденный поставщик по сумме выплат:"
: "Найденный заказчик по сумме поступлений:"
: isSupplier
? `Топ-${visible.length} поставщиков по сумме выплат:`
: `Топ-${visible.length} заказчиков по сумме поступлений:`;
const leadingCounterparty = visible[0] ?? null;
lines.unshift(heading);
if (leadingCounterparty) {
const directAnswerLine = isSupplier
? `Крупнейший поставщик по подтвержденным выплатам за доступное время: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям).`
: `Самый доходный клиент за доступное время по подтвержденным поступлениям: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это денежный поток, а не чистая прибыль.`;
const directAnswerLine = singleCandidateOnly
? isSupplier
? `В выбранном срезе найден один поставщик: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это не полноценный сравнительный рейтинг.`
: `В выбранном срезе найден один клиент: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это не полноценный сравнительный рейтинг; сумма является денежным потоком, а не чистой прибылью.`
: isSupplier
? `Крупнейший поставщик по подтвержденным выплатам за доступное время: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям).`
: `Самый доходный клиент за доступное время по подтвержденным поступлениям: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это денежный поток, а не чистая прибыль.`;
lines.unshift(directAnswerLine);
}
lines.push(

View File

@ -401,6 +401,7 @@ function isInventoryLifecycleHistoryIntent(intent: AddressIntent | undefined): b
intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain"
);
}
@ -1272,6 +1273,7 @@ function mergeFollowupFilters(
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_profitability_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date" ||
intent === "payables_confirmed_as_of_date" ||
@ -1331,6 +1333,22 @@ function mergeFollowupFilters(
}
}
if (
intent === "inventory_profitability_for_item" &&
previousHasPeriod &&
hasSelectedObjectInventorySignal(userMessage) &&
!hasExplicitPeriodInMessage &&
!hasExplicitCurrentDateInMessage
) {
if (previousPeriodFrom && merged.period_from !== previousPeriodFrom) {
merged.period_from = previousPeriodFrom;
}
if (previousPeriodTo && merged.period_to !== previousPeriodTo) {
merged.period_to = previousPeriodTo;
}
reasons.push("period_from_followup_context");
}
if (
!currentHasPeriod &&
previousHasPeriod &&

View File

@ -73,6 +73,93 @@ interface InventoryReplyDeps {
isInventorySaleMovement: (row: ComposeStageRow) => boolean;
}
function cleanupInventoryRequestedParty(value: string): string | null {
const cleaned = String(value ?? "")
.replace(/\s*(?:->|=>|)\s*(?:товар|позици|номенклатур|покупател|buyer|customer|item|product|sku)[\s\S]*$/iu, "")
.replace(/\s+(?:на\s+дату|по\s+состоянию|за\s+период)\b[\s\S]*$/iu, "")
.replace(/[«»"]/gu, "")
.replace(/[.,;:\s]+$/u, "")
.trim();
return cleaned.length > 0 ? cleaned : null;
}
function extractRequestedInventoryParty(userMessage: string | null | undefined, role: "supplier" | "buyer"): string | null {
const text = String(userMessage ?? "");
const patterns =
role === "supplier"
? [
/(?:от\s+поставщика|у\s+поставщика|поставщик(?:а|у|ом)?|supplier|vendor)\s+([^\r\n?]+?)(?=$|[?]|(?:\s*(?:->|=>|)\s*(?:товар|позици|номенклатур|item|product|sku|покупател|buyer|customer)))/iu
]
: [
/(?:покупател(?:ь|я|ю|ем)?|buyer|customer|client)\s+([^\r\n?]+?)(?=$|[?]|(?:\s*(?:->|=>|))|(?:\s+на\s+дату))/iu
];
for (const pattern of patterns) {
const match = text.match(pattern);
const candidate = match?.[1] ? cleanupInventoryRequestedParty(match[1]) : null;
if (candidate) {
return candidate;
}
}
return null;
}
function inventoryPartyComparableTokens(value: string | null | undefined): string[] {
const stopWords = new Set(["ооо", "ао", "пао", "зао", "ип", "llc", "ltd", "inc", "corp"]);
return String(value ?? "")
.toLowerCase()
.replace(/ё/gu, "е")
.replace(/[^a-zа-я0-9]+/giu, " ")
.split(/\s+/u)
.map((token) => token.trim())
.filter((token) => token.length >= 3 && !stopWords.has(token));
}
function inventoryRequestedPartyMatches(requested: string | null, actualParties: string[]): boolean {
if (!requested) {
return true;
}
const requestedTokens = inventoryPartyComparableTokens(requested);
if (requestedTokens.length === 0) {
return false;
}
return actualParties.some((actual) => {
const actualTokens = inventoryPartyComparableTokens(actual);
return requestedTokens.every((token) => actualTokens.includes(token));
});
}
function inventoryPartyListOrUnknown(parties: string[]): string {
return parties.length > 0 ? parties.slice(0, 4).join("; ") : "не выделен отдельным полем";
}
function sumInventoryRowAmount(rows: ComposeStageRow[]): number {
return rows.reduce((sum, row) => sum + (typeof row.amount === "number" && Number.isFinite(row.amount) ? row.amount : 0), 0);
}
function sumInventoryRowQuantity(rows: ComposeStageRow[]): number {
return rows.reduce((sum, row) => sum + (typeof row.quantity === "number" && Number.isFinite(row.quantity) ? row.quantity : 0), 0);
}
function formatInventoryPercent(value: number | null, formatNumberWithDots: (value: number, fractionDigits?: number) => string): string {
return value === null || !Number.isFinite(value) ? "не подтверждена" : `${formatNumberWithDots(value, 2)}%`;
}
function inventoryProfitabilityPeriodLabel(options: InventoryComposeOptions, deps: InventoryReplyDeps): string {
const from = typeof options.periodFrom === "string" && options.periodFrom.trim().length > 0 ? options.periodFrom : null;
const to = typeof options.periodTo === "string" && options.periodTo.trim().length > 0 ? options.periodTo : null;
if (from && to) {
return `${deps.formatDateRu(from)} - ${deps.formatDateRu(to)}`;
}
if (from) {
return `с ${deps.formatDateRu(from)}`;
}
if (to) {
return `до ${deps.formatDateRu(to)}`;
}
const asOfDate = typeof options.asOfDate === "string" && options.asOfDate.trim().length > 0 ? options.asOfDate : null;
return asOfDate ? `до ${deps.formatDateRu(asOfDate)}` : "по доступной выборке";
}
export function composeInventoryReply(
intent: AddressIntent,
rows: ComposeStageRow[],
@ -263,6 +350,39 @@ export function composeInventoryReply(
const purchaseRows = rows.filter((row) => deps.isInventoryPurchaseMovement(row));
const summary = deps.summarizeInventoryTraceRows(purchaseRows);
const unresolvedRows = purchaseRows.filter((row) => deps.extractInventoryCounterpartyCandidates(row).length === 0);
const unresolvedSupplierQuestion =
/(?:\u0431\u0435\u0437\s+\u043f\u043e\u043d\u044f\u0442\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0431\u0435\u0437\s+(?:\u044f\u0432\u043d[^\s]*\s+)?\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043d\u0435\s+\u0438\u043c\u0435\u044e\u0442\s+\u044f\u0432\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043d\u0435\u0442\s+\u044f\u0432\u043d[^\s]*\s+\u043f\u0440\u0438\u0432\u044f\u0437\u043a[^\s]*\s+\u043a\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|unresolved\s+supplier\s+link)/iu.test(
String(options.userMessage ?? "")
);
if (unresolvedSupplierQuestion) {
const directAnswerLine =
unresolvedRows.length > 0
? `В текущем складском срезе найдено операций без явно выделенного поставщика: ${deps.formatNumberWithDots(unresolvedRows.length)}.`
: "В текущем складском срезе товары без явно выделенной привязки к поставщику в доступных данных не найдены.";
const lines: string[] = [directAnswerLine];
appendInventoryBulletSection(lines, "Что проверили:", [
`Дата среза: ${deps.formatDateRu(asOfDate)}.`,
`Закупочных операций в выборке: ${deps.formatNumberWithDots(purchaseRows.length)}.`,
`Операций без явно выделенного поставщика: ${deps.formatNumberWithDots(unresolvedRows.length)}.`,
`Поставщиков, выделенных в остальных операциях: ${deps.formatNumberWithDots(summary.counterparties.length)}.`
]);
appendInventoryBulletSection(lines, "Ограничения:", [
"Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии."
]);
if (unresolvedRows.length > 0) {
appendInventorySection(
lines,
"Позиции без явно выделенного поставщика:",
deps.formatInventoryTraceRows(unresolvedRows, 12)
);
} else if (summary.counterparties.length > 0) {
lines.push(`- В доступном закупочном следе встречаются поставщики: ${summary.counterparties.slice(0, 6).join("; ")}.`);
}
return buildFactualSummaryReply(
lines,
buildConfirmedBalanceSemantics(unresolvedRows.length > 0 ? "medium" : "strong", true)
);
}
const warehouseLabel = summary.warehouses[0] ?? "не указанного склада";
const directAnswerLine =
summary.counterparties.length === 1
@ -389,19 +509,109 @@ export function composeInventoryReply(
: buildFactualSummaryReply(lines, buildConfirmedBalanceSemantics("medium", false));
}
if (intent === "inventory_profitability_for_item") {
const purchaseRows = rows.filter((row) => deps.isInventoryPurchaseMovement(row));
const saleRows = rows.filter((row) => deps.isInventorySaleMovement(row));
const requestedItemHint = String(options.itemHint ?? "").trim();
const excludedCounterpartyTokens = requestedItemHint ? [requestedItemHint] : [];
const purchaseSummary = deps.summarizeInventoryTraceRows(purchaseRows, excludedCounterpartyTokens);
const saleSummary = deps.summarizeInventoryTraceRows(saleRows, excludedCounterpartyTokens);
const itemLabel = requestedItemHint || purchaseSummary.item || saleSummary.item || "товар не определен";
const revenue = sumInventoryRowAmount(saleRows);
const purchaseCostProxy = sumInventoryRowAmount(purchaseRows);
const spread = revenue - purchaseCostProxy;
const marginPct = revenue > 0 ? (spread / revenue) * 100 : null;
const markupPct = purchaseCostProxy > 0 ? (spread / purchaseCostProxy) * 100 : null;
const saleQuantity = sumInventoryRowQuantity(saleRows);
const purchaseQuantity = sumInventoryRowQuantity(purchaseRows);
const periodLabel = inventoryProfitabilityPeriodLabel(options, deps);
const hasSales = saleRows.length > 0;
const hasPurchases = purchaseRows.length > 0;
const directAnswerLine =
hasSales && hasPurchases
? `По товару ${itemLabel} за период ${periodLabel} подтверждена выручка продаж ${deps.formatMoneyRub(revenue)} и закупочный след ${deps.formatMoneyRub(purchaseCostProxy)}; расчетный валовый спред по доступным документам: ${deps.formatMoneyRub(spread)}. Маржинальность к выручке: ${formatInventoryPercent(marginPct, deps.formatNumberWithDots)}.`
: hasSales
? `По товару ${itemLabel} за период ${periodLabel} подтверждена выручка продаж ${deps.formatMoneyRub(revenue)}, но закупочный след в доступных строках не найден; прибыль и маржа не подтверждены.`
: hasPurchases
? `По товару ${itemLabel} за период ${periodLabel} найден закупочный след ${deps.formatMoneyRub(purchaseCostProxy)}, но продажи в доступных строках не найдены; выручка, прибыль и маржа не подтверждены.`
: `По товару ${itemLabel} за период ${periodLabel} не найдено ни продаж, ни закупочного следа в доступных строках 41.01.`;
const lines: string[] = [directAnswerLine];
appendInventoryBulletSection(lines, "Расчет:", [
`Строк продаж со счета 41.01: ${deps.formatNumberWithDots(saleRows.length)}.`,
`Строк закупки на счет 41.01: ${deps.formatNumberWithDots(purchaseRows.length)}.`,
`Выручка по документам продажи: ${deps.formatMoneyRub(revenue)}.`,
`Закупочная сумма по доступным документам: ${deps.formatMoneyRub(purchaseCostProxy)}.`,
`Расчетный валовый спред: ${deps.formatMoneyRub(spread)}.`,
`Маржинальность к выручке: ${formatInventoryPercent(marginPct, deps.formatNumberWithDots)}.`,
`Наценка к закупочному следу: ${formatInventoryPercent(markupPct, deps.formatNumberWithDots)}.`
]);
if (saleQuantity > 0 || purchaseQuantity > 0) {
lines.push(`- Количество в продажах: ${deps.formatNumberWithDots(saleQuantity, 3)}; количество в закупках: ${deps.formatNumberWithDots(purchaseQuantity, 3)}.`);
}
if (saleSummary.counterparties.length > 0) {
lines.push(`- Покупатели в продаже: ${saleSummary.counterparties.slice(0, 4).join("; ")}.`);
}
if (purchaseSummary.counterparties.length > 0) {
lines.push(`- Поставщики в закупочном следе: ${purchaseSummary.counterparties.slice(0, 4).join("; ")}.`);
}
appendInventoryBulletSection(lines, "Ограничения:", [
"Это не чистая прибыль компании и не бухгалтерский финансовый результат.",
"Закупочная сумма является proxy по найденным документам поступления; без партионного/управленческого учета нельзя доказать точную себестоимость конкретной продажи.",
"Если продажи и закупки попали в разные периоды или разные организации/склады, вывод нужно читать как документальный срез по доступной выборке, а не как полный P&L."
]);
if (saleRows.length > 0) {
appendInventorySection(lines, "Продажи:", [
`- Первая дата продажи: ${deps.inventoryTraceDateLabel(saleSummary.firstPeriod)}.`,
`- Последняя дата продажи: ${deps.inventoryTraceDateLabel(saleSummary.lastPeriod)}.`,
...deps.formatInventoryTraceRows(saleRows, 8, [itemLabel])
]);
}
if (purchaseRows.length > 0) {
appendInventorySection(lines, "Закупки:", [
`- Первая дата закупки: ${deps.inventoryTraceDateLabel(purchaseSummary.firstPeriod)}.`,
`- Последняя дата закупки: ${deps.inventoryTraceDateLabel(purchaseSummary.lastPeriod)}.`,
...deps.formatInventoryTraceRows(purchaseRows, 8, [itemLabel])
]);
}
return buildFactualSummaryReply(
lines,
buildConfirmedBalanceSemantics(
hasSales && hasPurchases ? "strong" : hasSales || hasPurchases ? "medium" : "weak",
hasSales || hasPurchases
)
);
}
if (intent === "inventory_purchase_to_sale_chain") {
const purchaseRows = rows.filter((row) => deps.isInventoryPurchaseMovement(row));
const saleRows = rows.filter((row) => deps.isInventorySaleMovement(row));
const purchaseSummary = deps.summarizeInventoryTraceRows(purchaseRows);
const saleSummary = deps.summarizeInventoryTraceRows(saleRows);
const itemLabel = purchaseSummary.item ?? saleSummary.item ?? "товар не определен";
const requestedSupplier = extractRequestedInventoryParty(options.userMessage, "supplier");
const requestedBuyer = extractRequestedInventoryParty(options.userMessage, "buyer");
const supplierMatches = inventoryRequestedPartyMatches(requestedSupplier, purchaseSummary.counterparties);
const buyerMatches = inventoryRequestedPartyMatches(requestedBuyer, saleSummary.counterparties);
const mismatchParts: string[] = [];
if (requestedSupplier && purchaseRows.length > 0 && !supplierMatches) {
mismatchParts.push(
`запрошенный поставщик ${requestedSupplier} не совпал с найденным поставщиком: ${inventoryPartyListOrUnknown(purchaseSummary.counterparties)}`
);
}
if (requestedBuyer && saleRows.length > 0 && !buyerMatches) {
mismatchParts.push(
`запрошенный покупатель ${requestedBuyer} не совпал с найденным покупателем: ${inventoryPartyListOrUnknown(saleSummary.counterparties)}`
);
}
const directAnswerLine =
purchaseSummary.counterparties.length === 1 && saleSummary.counterparties.length === 1
mismatchParts.length > 0
? `Запрошенная цепочка по товару ${itemLabel} полностью не подтверждена: ${mismatchParts.join("; ")}.`
: purchaseSummary.counterparties.length === 1 && saleSummary.counterparties.length === 1
? `По товару ${itemLabel} цепочка поставки и продажи связана с поставщиком ${purchaseSummary.counterparties[0]} и покупателем ${saleSummary.counterparties[0]}.`
: `По товару ${itemLabel} цепочка поставки и продажи подтверждена частично или разнообразно: детали идут следом.`;
const lines: string[] = [directAnswerLine, "", "Подтверждение:"];
lines.push(`- Закупочных движений на 41.01: ${deps.formatNumberWithDots(purchaseRows.length)}.`);
lines.push(`- Движений выбытия со счета 41.01: ${deps.formatNumberWithDots(saleRows.length)}.`);
lines.push(`- Строк закупки на 41.01: ${deps.formatNumberWithDots(purchaseRows.length)}.`);
lines.push(`- Строк продажи со счета 41.01: ${deps.formatNumberWithDots(saleRows.length)}.`);
if (purchaseRows.length > 0 && saleRows.length > 0) {
lines.push("- В доступных данных найдены обе стороны цепочки: поступление и последующее выбытие.");
} else if (purchaseRows.length > 0) {

View File

@ -9,6 +9,21 @@ export const ASSISTANT_MCP_CATALOG_PLAN_REVIEW_SCHEMA_VERSION = "assistant_mcp_c
export type AssistantMcpCatalogEvidenceFloor = "none" | "rows_received" | "rows_matched" | "source_summary";
export type AssistantMcpCatalogPlanReviewStatus = "catalog_compatible" | "needs_more_axes" | "catalog_blocked";
export type AssistantMcpCatalogChainTemplateId =
| "metadata_inspection"
| "catalog_drilldown"
| "inventory_stock_snapshot"
| "inventory_supplier_overlap"
| "inventory_purchase_provenance"
| "inventory_sale_trace"
| "value_flow"
| "value_flow_comparison"
| "value_flow_ranking"
| "lifecycle"
| "business_overview"
| "movement_evidence"
| "document_evidence"
| "entity_resolution";
export interface AssistantMcpCatalogPrimitiveContract {
primitive_id: AssistantMcpDiscoveryPrimitive;
@ -29,9 +44,23 @@ export interface AssistantMcpCatalogIndexContract {
schema_version: typeof ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION;
policy_owner: "assistantMcpCatalogIndex";
primitives: AssistantMcpCatalogPrimitiveContract[];
chain_templates: AssistantMcpCatalogChainTemplateContract[];
reason_codes: string[];
}
export interface AssistantMcpCatalogChainTemplateContract {
chain_id: AssistantMcpCatalogChainTemplateId;
semantic_data_need: string;
chain_summary: string;
fallback_primitives: AssistantMcpDiscoveryPrimitive[];
base_required_axes: string[];
supported_fact_families: string[];
supported_action_families: string[];
planning_tags: string[];
safe_for_model_planning: true;
requires_evidence_gate: true;
}
export interface AssistantMcpCatalogPlanReview {
schema_version: typeof ASSISTANT_MCP_CATALOG_PLAN_REVIEW_SCHEMA_VERSION;
policy_owner: "assistantMcpCatalogIndex";
@ -76,8 +105,30 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
primitive_id: "resolve_entity_reference",
purpose: "Resolve a user-visible entity name to a concrete 1C reference candidate.",
decomposition_hints: ["resolve_entity_reference"],
supported_fact_families: ["entity_grounding", "value_flow", "document_evidence", "movement_evidence", "activity_lifecycle"],
supported_action_families: ["search_business_entity", "turnover", "payout", "net_value_flow", "list_documents", "list_movements", "activity_duration"],
supported_fact_families: [
"entity_grounding",
"value_flow",
"document_evidence",
"movement_evidence",
"activity_lifecycle",
"business_overview",
"inventory_purchase_provenance",
"inventory_sale_trace",
"inventory_supplier_overlap"
],
supported_action_families: [
"search_business_entity",
"turnover",
"payout",
"net_value_flow",
"list_documents",
"list_movements",
"activity_duration",
"broad_evaluation",
"purchase_provenance",
"sale_trace",
"supplier_overlap"
],
planning_tags: ["subject_resolution"],
required_axes_any_of: [["business_entity"], ["counterparty"], ["organization"], ["contract"], ["item"]],
optional_axes: ["period", "inn", "document"],
@ -95,18 +146,35 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
"collect_outgoing_movements",
"fetch_scoped_movements"
],
supported_fact_families: ["value_flow", "movement_evidence"],
supported_action_families: ["turnover", "payout", "net_value_flow", "list_movements"],
supported_fact_families: [
"value_flow",
"movement_evidence",
"business_overview",
"inventory_stock_snapshot",
"inventory_supplier_overlap"
],
supported_action_families: [
"turnover",
"payout",
"net_value_flow",
"list_movements",
"broad_evaluation",
"stock_snapshot",
"supplier_overlap"
],
planning_tags: ["movement", "comparison", "ranking", "aggregation", "monthly_aggregation"],
required_axes_any_of: [
["period", "account"],
["period", "counterparty"],
["period", "organization"],
["as_of_date", "organization"],
["as_of_date", "warehouse"],
["as_of_date", "item"],
["all_time_scope", "counterparty"],
["all_time_scope", "organization"]
],
optional_axes: ["contract", "document", "amount", "item", "warehouse"],
output_fact_kinds: ["movement_rows", "turnover", "balance_delta"],
optional_axes: ["contract", "document", "amount", "item", "warehouse", "quantity", "supplier"],
output_fact_kinds: ["movement_rows", "turnover", "balance_delta", "stock_rows", "stock_quantity"],
evidence_floor: "rows_matched",
safe_for_model_planning: true,
runtime_must_execute: true
@ -115,19 +183,35 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
primitive_id: "query_documents",
purpose: "Fetch documents related to a scoped entity, period, contract, or movement explanation.",
decomposition_hints: ["fetch_scoped_documents", "fetch_supporting_documents"],
supported_fact_families: ["document_evidence", "activity_lifecycle"],
supported_action_families: ["list_documents", "activity_duration"],
supported_fact_families: [
"document_evidence",
"activity_lifecycle",
"business_overview",
"inventory_purchase_provenance",
"inventory_sale_trace",
"inventory_supplier_overlap"
],
supported_action_families: [
"list_documents",
"activity_duration",
"broad_evaluation",
"purchase_provenance",
"sale_trace",
"supplier_overlap"
],
planning_tags: ["document"],
required_axes_any_of: [
["document"],
["item"],
["supplier"],
["counterparty"],
["contract"],
["period", "organization"],
["all_time_scope", "counterparty"],
["all_time_scope", "organization"]
],
optional_axes: ["account", "amount", "item", "warehouse"],
output_fact_kinds: ["document_rows", "document_dates", "document_amounts"],
optional_axes: ["account", "amount", "item", "warehouse", "as_of_date", "supplier", "buyer"],
output_fact_kinds: ["document_rows", "document_dates", "document_amounts", "purchase_documents", "sale_documents"],
evidence_floor: "rows_matched",
safe_for_model_planning: true,
runtime_must_execute: true
@ -136,18 +220,19 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
primitive_id: "aggregate_by_axis",
purpose: "Aggregate already-scoped 1C evidence by a business axis such as counterparty, contract, or period.",
decomposition_hints: ["aggregate_checked_amounts", "aggregate_ranked_axis_values", "aggregate_by_month"],
supported_fact_families: ["value_flow"],
supported_action_families: ["turnover", "payout", "net_value_flow"],
supported_fact_families: ["value_flow", "business_overview", "inventory_stock_snapshot", "inventory_supplier_overlap"],
supported_action_families: ["turnover", "payout", "net_value_flow", "broad_evaluation", "stock_snapshot", "supplier_overlap"],
planning_tags: ["aggregation", "ranking", "monthly_aggregation"],
required_axes_any_of: [
["aggregate_axis", "period"],
["aggregate_axis", "as_of_date"],
["aggregate_axis", "counterparty"],
["aggregate_axis", "account"],
["aggregate_axis", "counterparty", "all_time_scope"],
["aggregate_axis", "organization", "all_time_scope"]
],
optional_axes: ["organization", "contract", "document", "amount"],
output_fact_kinds: ["aggregate_totals", "ranked_axis_values"],
optional_axes: ["organization", "contract", "document", "amount", "quantity", "item", "warehouse", "supplier"],
output_fact_kinds: ["aggregate_totals", "ranked_axis_values", "stock_totals", "item_totals"],
evidence_floor: "rows_matched",
safe_for_model_planning: true,
runtime_must_execute: true
@ -156,12 +241,12 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
primitive_id: "drilldown_related_objects",
purpose: "Drill from a known entity or document into related contracts, documents, movements, or payments.",
decomposition_hints: ["drilldown_related_objects"],
supported_fact_families: [],
supported_action_families: [],
planning_tags: ["drilldown"],
required_axes_any_of: [["business_entity"], ["document"], ["contract"], ["counterparty"]],
optional_axes: ["period", "account", "amount"],
output_fact_kinds: ["related_objects", "relationship_edges"],
supported_fact_families: ["inventory_purchase_provenance", "inventory_sale_trace", "inventory_supplier_overlap"],
supported_action_families: ["purchase_provenance", "sale_trace", "supplier_overlap", "purchase_to_sale_chain"],
planning_tags: ["drilldown", "inventory"],
required_axes_any_of: [["business_entity"], ["document"], ["contract"], ["counterparty"], ["item"], ["supplier"]],
optional_axes: ["period", "account", "amount", "warehouse", "as_of_date", "buyer"],
output_fact_kinds: ["related_objects", "relationship_edges", "supplier_item_edges", "buyer_item_edges"],
evidence_floor: "rows_received",
safe_for_model_planning: true,
runtime_must_execute: true
@ -176,7 +261,12 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
"value_flow",
"document_evidence",
"movement_evidence",
"activity_lifecycle"
"activity_lifecycle",
"business_overview",
"inventory_stock_snapshot",
"inventory_purchase_provenance",
"inventory_sale_trace",
"inventory_supplier_overlap"
],
supported_action_families: [
"inspect_catalog",
@ -190,11 +280,17 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
"net_value_flow",
"list_documents",
"list_movements",
"activity_duration"
"activity_duration",
"broad_evaluation",
"stock_snapshot",
"purchase_provenance",
"sale_trace",
"supplier_overlap",
"purchase_to_sale_chain"
],
planning_tags: ["coverage"],
required_axes_any_of: [["coverage_target"], ["domain_family"], ["primitive_id"]],
optional_axes: ["period", "organization", "counterparty", "document", "account"],
optional_axes: ["period", "organization", "counterparty", "document", "account", "item", "warehouse", "as_of_date"],
output_fact_kinds: ["coverage_status", "known_gaps"],
evidence_floor: "source_summary",
safe_for_model_planning: true,
@ -204,11 +300,26 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
primitive_id: "explain_evidence_basis",
purpose: "Produce a machine-readable explanation of which checked MCP evidence supports, limits, or fails the answer.",
decomposition_hints: ["explain_evidence_basis"],
supported_fact_families: ["activity_lifecycle"],
supported_action_families: ["activity_duration"],
planning_tags: ["explanation"],
supported_fact_families: [
"activity_lifecycle",
"business_overview",
"inventory_stock_snapshot",
"inventory_purchase_provenance",
"inventory_sale_trace",
"inventory_supplier_overlap"
],
supported_action_families: [
"activity_duration",
"broad_evaluation",
"stock_snapshot",
"purchase_provenance",
"sale_trace",
"supplier_overlap",
"purchase_to_sale_chain"
],
planning_tags: ["explanation", "inventory", "business_overview", "bounded_inference"],
required_axes_any_of: [["evidence_basis"], ["primitive_id"], ["source_rows_summary"]],
optional_axes: ["coverage_target", "domain_family"],
optional_axes: ["coverage_target", "domain_family", "item", "warehouse", "as_of_date", "supplier", "buyer"],
output_fact_kinds: ["confirmed_facts", "inferred_facts", "unknown_facts"],
evidence_floor: "source_summary",
safe_for_model_planning: true,
@ -220,6 +331,217 @@ const PRIMITIVE_CONTRACT_MAP = new Map<AssistantMcpDiscoveryPrimitive, Assistant
PRIMITIVE_CONTRACTS.map((contract) => [contract.primitive_id, contract])
);
const CHAIN_TEMPLATES: AssistantMcpCatalogChainTemplateContract[] = [
{
chain_id: "metadata_inspection",
semantic_data_need: "1C metadata evidence",
chain_summary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.",
fallback_primitives: ["inspect_1c_metadata"],
base_required_axes: ["metadata_scope"],
supported_fact_families: ["schema_surface"],
supported_action_families: ["inspect_catalog", "inspect_documents", "inspect_registers", "inspect_fields", "inspect_surface"],
planning_tags: ["metadata", "surface_inspection"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "catalog_drilldown",
semantic_data_need: "catalog drilldown metadata evidence",
chain_summary:
"Drill deeper into the confirmed catalog-oriented metadata surface, inspect related metadata objects, and keep the next safe lane grounded in checked schema evidence.",
fallback_primitives: ["inspect_1c_metadata"],
base_required_axes: ["metadata_scope"],
supported_fact_families: ["schema_surface"],
supported_action_families: ["inspect_catalog", "inspect_surface"],
planning_tags: ["metadata", "surface_inspection", "drilldown"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "entity_resolution",
semantic_data_need: "entity discovery evidence",
chain_summary:
"Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.",
fallback_primitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"],
base_required_axes: ["business_entity", "coverage_target"],
supported_fact_families: ["entity_grounding"],
supported_action_families: ["search_business_entity"],
planning_tags: ["subject_resolution", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "document_evidence",
semantic_data_need: "document evidence",
chain_summary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.",
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
base_required_axes: ["coverage_target"],
supported_fact_families: ["document_evidence", "activity_lifecycle"],
supported_action_families: ["list_documents", "activity_duration"],
planning_tags: ["document", "subject_resolution", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "movement_evidence",
semantic_data_need: "movement evidence",
chain_summary:
"Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.",
fallback_primitives: ["resolve_entity_reference", "query_movements", "probe_coverage"],
base_required_axes: ["coverage_target"],
supported_fact_families: ["movement_evidence", "value_flow"],
supported_action_families: ["list_movements", "turnover", "payout", "net_value_flow"],
planning_tags: ["movement", "subject_resolution", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "inventory_stock_snapshot",
semantic_data_need: "inventory stock snapshot evidence",
chain_summary:
"Fetch checked inventory movement or balance rows for the requested as-of date, aggregate item quantities, then probe coverage and explain stock-snapshot limits before answering.",
fallback_primitives: ["query_movements", "aggregate_by_axis", "probe_coverage", "explain_evidence_basis"],
base_required_axes: ["as_of_date", "organization", "warehouse", "aggregate_axis", "quantity", "coverage_target", "evidence_basis"],
supported_fact_families: ["inventory_stock_snapshot"],
supported_action_families: ["stock_snapshot"],
planning_tags: ["inventory", "movement", "aggregation", "coverage", "explanation"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "inventory_supplier_overlap",
semantic_data_need: "inventory supplier overlap evidence",
chain_summary:
"Ground the supplier or item anchor, fetch checked stock and supporting purchase evidence for the requested slice, aggregate item overlap, then state supplier-link limits explicitly.",
fallback_primitives: [
"resolve_entity_reference",
"query_movements",
"query_documents",
"aggregate_by_axis",
"probe_coverage",
"explain_evidence_basis"
],
base_required_axes: ["supplier", "item", "warehouse", "as_of_date", "aggregate_axis", "coverage_target", "evidence_basis"],
supported_fact_families: ["inventory_supplier_overlap"],
supported_action_families: ["supplier_overlap"],
planning_tags: ["inventory", "subject_resolution", "movement", "document", "aggregation", "coverage", "explanation"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "inventory_purchase_provenance",
semantic_data_need: "inventory purchase provenance evidence",
chain_summary:
"Ground the selected item, fetch checked purchase documents, drill into related supplier evidence, then probe coverage before stating purchase provenance.",
fallback_primitives: [
"resolve_entity_reference",
"query_documents",
"drilldown_related_objects",
"probe_coverage",
"explain_evidence_basis"
],
base_required_axes: ["item", "document", "supplier", "coverage_target", "evidence_basis"],
supported_fact_families: ["inventory_purchase_provenance"],
supported_action_families: ["purchase_provenance"],
planning_tags: ["inventory", "document", "drilldown", "subject_resolution", "coverage", "explanation"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "inventory_sale_trace",
semantic_data_need: "inventory sale trace evidence",
chain_summary:
"Ground the selected item, fetch checked sale documents, drill into related buyer evidence, then probe coverage before stating the sale or purchase-to-sale trace.",
fallback_primitives: [
"resolve_entity_reference",
"query_documents",
"drilldown_related_objects",
"probe_coverage",
"explain_evidence_basis"
],
base_required_axes: ["item", "document", "buyer", "coverage_target", "evidence_basis"],
supported_fact_families: ["inventory_sale_trace"],
supported_action_families: ["sale_trace", "purchase_to_sale_chain"],
planning_tags: ["inventory", "document", "drilldown", "subject_resolution", "coverage", "explanation"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "value_flow",
semantic_data_need: "counterparty value-flow evidence",
chain_summary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.",
fallback_primitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"],
base_required_axes: ["aggregate_axis", "amount", "coverage_target"],
supported_fact_families: ["value_flow"],
supported_action_families: ["turnover", "payout", "net_value_flow"],
planning_tags: ["movement", "aggregation", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "value_flow_comparison",
semantic_data_need: "bidirectional value-flow comparison evidence",
chain_summary:
"Query incoming and outgoing movements for the checked period and organization, compare the checked sides, and probe coverage before answering a bounded comparison.",
fallback_primitives: ["query_movements", "probe_coverage"],
base_required_axes: ["amount", "coverage_target"],
supported_fact_families: ["value_flow"],
supported_action_families: ["net_value_flow"],
planning_tags: ["movement", "comparison", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "value_flow_ranking",
semantic_data_need: "ranked value-flow evidence",
chain_summary:
"Query scoped movements for the checked period and organization, aggregate checked amounts by counterparty, then probe coverage before answering a bounded ranking.",
fallback_primitives: ["query_movements", "aggregate_by_axis", "probe_coverage"],
base_required_axes: ["aggregate_axis", "amount", "coverage_target"],
supported_fact_families: ["value_flow"],
supported_action_families: ["turnover", "payout"],
planning_tags: ["movement", "ranking", "aggregation", "coverage"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "lifecycle",
semantic_data_need: "counterparty lifecycle evidence with bounded activity-window inference",
chain_summary:
"Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window without presenting it as legal registration age.",
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
base_required_axes: ["document_date", "coverage_target", "evidence_basis", "activity_window", "legal_fact_boundary"],
supported_fact_families: ["activity_lifecycle"],
supported_action_families: ["activity_duration"],
planning_tags: ["document", "explanation", "coverage", "bounded_inference", "activity_window", "legal_fact_boundary"],
safe_for_model_planning: true,
requires_evidence_gate: true
},
{
chain_id: "business_overview",
semantic_data_need: "business overview evidence with bounded analyst interpretation",
chain_summary:
"Collect organization-scoped movement and document evidence, aggregate checked business signals, probe coverage, and explain profit/margin limits before giving a bounded analyst overview.",
fallback_primitives: [
"query_movements",
"aggregate_by_axis",
"query_documents",
"probe_coverage",
"explain_evidence_basis"
],
base_required_axes: ["organization", "aggregate_axis", "amount", "coverage_target", "evidence_basis", "profit_margin_boundary"],
supported_fact_families: ["business_overview"],
supported_action_families: ["broad_evaluation"],
planning_tags: ["business_overview", "movement", "document", "aggregation", "coverage", "explanation", "bounded_inference"],
safe_for_model_planning: true,
requires_evidence_gate: true
}
];
const CHAIN_TEMPLATE_MAP = new Map<AssistantMcpCatalogChainTemplateId, AssistantMcpCatalogChainTemplateContract>(
CHAIN_TEMPLATES.map((template) => [template.chain_id, template])
);
function toStringSet(values: string[]): Set<string> {
return new Set(values.map((item) => item.trim()).filter((item) => item.length > 0));
}
@ -290,6 +612,34 @@ function tagSetFromFactAxisInput(input: AssistantMcpCatalogFactAxisSearchInput):
if (input.business_fact_family === "value_flow") {
tags.add("movement");
}
if (input.business_fact_family === "business_overview") {
tags.add("business_overview");
tags.add("movement");
tags.add("document");
tags.add("aggregation");
tags.add("coverage");
tags.add("explanation");
tags.add("bounded_inference");
}
if (input.business_fact_family?.startsWith("inventory_")) {
tags.add("inventory");
}
if (input.business_fact_family === "inventory_stock_snapshot") {
tags.add("movement");
tags.add("aggregation");
}
if (input.business_fact_family === "inventory_supplier_overlap") {
tags.add("movement");
tags.add("document");
tags.add("aggregation");
}
if (
input.business_fact_family === "inventory_purchase_provenance" ||
input.business_fact_family === "inventory_sale_trace"
) {
tags.add("document");
tags.add("drilldown");
}
if (input.comparison_need) {
tags.add("comparison");
}
@ -329,6 +679,15 @@ export interface AssistantMcpCatalogFactAxisSearchInput {
allow_aggregate_by_axis?: boolean;
}
export interface AssistantMcpCatalogChainTemplateSearchInput {
business_fact_family?: string | null;
action_family?: string | null;
required_axes?: string[];
comparison_need?: string | null;
ranking_need?: string | null;
aggregation_need?: string | null;
}
export interface AssistantMcpCatalogPrimitiveSearchInput {
decomposition_candidates: string[];
allow_aggregate_by_axis?: boolean;
@ -464,11 +823,7 @@ export function searchAssistantMcpCatalogPrimitivesByDecompositionCandidates(
}
for (const contract of PRIMITIVE_CONTRACTS) {
if (
contract.primitive_id === "aggregate_by_axis" &&
normalizedCandidate === "aggregate_by_month" &&
!allowAggregateByAxis
) {
if (contract.primitive_id === "aggregate_by_axis" && !allowAggregateByAxis) {
continue;
}
if (!contract.decomposition_hints.includes(normalizedCandidate)) {
@ -517,6 +872,9 @@ export function searchAssistantMcpCatalogPrimitivesByFactAxis(
if (contract.primitive_id === "explain_evidence_basis" && !desiredTags.has("explanation")) {
continue;
}
if (contract.primitive_id === "drilldown_related_objects" && !desiredTags.has("drilldown")) {
continue;
}
if (!factMatch && !actionMatch && tagMatches.length <= 0) {
continue;
}
@ -551,6 +909,62 @@ export function searchAssistantMcpCatalogPrimitivesByFactAxis(
.map((item) => item.primitive);
}
export function searchAssistantMcpCatalogChainTemplatesByFactAxis(
input: AssistantMcpCatalogChainTemplateSearchInput
): AssistantMcpCatalogChainTemplateId[] {
const requiredAxisSet = toStringSet(input.required_axes ?? []);
const desiredTags = tagSetFromFactAxisInput({
business_fact_family: input.business_fact_family,
action_family: input.action_family,
required_axes: input.required_axes,
comparison_need: input.comparison_need,
ranking_need: input.ranking_need,
aggregation_need: input.aggregation_need
});
const scored: Array<{ chainId: AssistantMcpCatalogChainTemplateId; score: number }> = [];
for (const template of CHAIN_TEMPLATES) {
const factMatch = matchesPlanningToken(input.business_fact_family, template.supported_fact_families);
const actionMatch = matchesPlanningToken(input.action_family, template.supported_action_families);
const tagMatches = template.planning_tags.filter((tag) => desiredTags.has(normalizePlanningToken(tag)));
const axisOverlap = template.base_required_axes.filter((axis) => requiredAxisSet.has(axis)).length;
let score = 0;
if (factMatch) {
score += 8;
}
if (actionMatch) {
score += 5;
}
score += tagMatches.length * 2;
score += axisOverlap;
if (input.comparison_need && template.planning_tags.some((tag) => normalizePlanningToken(tag) === "comparison")) {
score += 6;
}
if (input.ranking_need && template.planning_tags.some((tag) => normalizePlanningToken(tag) === "ranking")) {
score += 6;
}
if (
input.aggregation_need === "by_month" &&
template.planning_tags.some((tag) => normalizePlanningToken(tag) === "monthly_aggregation")
) {
score += 4;
}
if (score <= 0) {
continue;
}
scored.push({
chainId: template.chain_id,
score
});
}
return scored
.sort((left, right) => right.score - left.score)
.map((item) => item.chainId);
}
export function searchAssistantMcpCatalogPrimitivesByMetadataSurface(
input: AssistantMcpCatalogMetadataSurfaceSearchInput
): AssistantMcpDiscoveryPrimitive[] {
@ -626,10 +1040,19 @@ export function buildAssistantMcpCatalogIndex(): AssistantMcpCatalogIndexContrac
} else {
pushReason(reasonCodes, "catalog_covers_all_discovery_primitives");
}
const unknownChainPrimitives = CHAIN_TEMPLATES.flatMap((template) =>
template.fallback_primitives.filter((primitive) => !PRIMITIVE_CONTRACT_MAP.has(primitive))
);
if (unknownChainPrimitives.length > 0) {
pushReason(reasonCodes, "catalog_chain_template_references_unknown_primitive");
} else {
pushReason(reasonCodes, "catalog_chain_templates_reference_reviewed_primitives");
}
return {
schema_version: ASSISTANT_MCP_CATALOG_INDEX_SCHEMA_VERSION,
policy_owner: "assistantMcpCatalogIndex",
primitives: PRIMITIVE_CONTRACTS,
chain_templates: CHAIN_TEMPLATES,
reason_codes: reasonCodes
};
}
@ -644,6 +1067,16 @@ export function getAssistantMcpCatalogPrimitive(
return contract;
}
export function getAssistantMcpCatalogChainTemplate(
chainId: AssistantMcpCatalogChainTemplateId
): AssistantMcpCatalogChainTemplateContract {
const template = CHAIN_TEMPLATE_MAP.get(chainId);
if (!template) {
throw new Error(`Missing MCP catalog chain template: ${chainId}`);
}
return template;
}
export function reviewAssistantMcpDiscoveryPlanAgainstCatalog(
plan: AssistantMcpDiscoveryPlanContract
): AssistantMcpCatalogPlanReview {

View File

@ -25,6 +25,8 @@ export interface AssistantMcpDiscoveryAnswerDraftContract {
reason_codes: string[];
}
type BusinessOverview = NonNullable<AssistantMcpDiscoveryPilotExecutionContract["derived_business_overview"]>;
function normalizeReasonCode(value: string): string | null {
const normalized = value
.trim()
@ -62,6 +64,11 @@ function formatNamedChoiceList(values: string[]): string {
function isInternalMechanicsLine(value: string): boolean {
const text = value.toLowerCase();
return (
text.includes("mcp fetch failed") ||
text.includes("this operation was aborted") ||
text.includes("entity-resolution") ||
text.includes("could not continue") ||
text.includes("checked catalog search step") ||
text.includes("primitive") ||
text.includes("query_documents") ||
text.includes("query_movements") ||
@ -73,8 +80,22 @@ function isInternalMechanicsLine(value: string): boolean {
text.includes("runtime_") ||
text.includes("planner_") ||
text.includes("catalog_") ||
text.includes("scope is not implemented yet") ||
text.includes("needs more scope before execution") ||
text.includes("mcp_execution_performed")
|| text.includes("confirmed 1c metadata surface")
|| text.includes("metadata surface family scores")
|| text.includes("available metadata object sets")
|| text.includes("selected metadata")
);
}
function isMcpTransportFailureLine(value: string): boolean {
const text = value.toLowerCase();
return (
text.includes("mcp fetch failed") ||
text.includes("this operation was aborted") ||
text.includes("operation was aborted")
);
}
@ -92,7 +113,20 @@ function rankedValueFlowUnknownLines(pilot: AssistantMcpDiscoveryPilotExecutionC
}
function userFacingLimitations(values: string[]): string[] {
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
const result: string[] = [];
for (const value of uniqueStrings(values)) {
if (isMcpTransportFailureLine(value)) {
const line = "Доступ к 1С во время проверки оборвался; подтвержденные строки не получены.";
if (!result.includes(line)) {
result.push(line);
}
continue;
}
if (!isInternalMechanicsLine(value)) {
result.push(value);
}
}
return result;
}
function modeFor(pilot: AssistantMcpDiscoveryPilotExecutionContract): AssistantMcpDiscoveryAnswerMode {
@ -126,6 +160,10 @@ function isValueFlowPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): b
);
}
function isBusinessOverviewPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
return pilot.pilot_scope === "business_overview_route_template_v1";
}
function isDocumentPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
return pilot.pilot_scope === "counterparty_document_evidence_query_documents_v1";
}
@ -138,6 +176,10 @@ function isMetadataPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): bo
return pilot.pilot_scope === "metadata_inspection_v1";
}
function isInventoryTemplatePilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
return pilot.pilot_scope === "inventory_route_template_v1";
}
function isCatalogDrilldownPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
return (
isMetadataPilot(pilot) &&
@ -379,6 +421,52 @@ function metadataRouteFamilyLabelRu(
return null;
}
function businessOverviewInventoryUnknownLabel(overview: BusinessOverview): string {
if (overview.inventory_staleness_risk_proxy) {
return "резервы/списания/ликвидационная стоимость склада";
}
if (overview.inventory_turnover_proxy) {
return "FIFO-оборачиваемость/подтвержденная складская ликвидность";
}
if (overview.inventory_position) {
return "полноценная складская ликвидность";
}
return "склад";
}
function businessOverviewNextStepLine(overview: BusinessOverview): string {
const missing = new Set(overview.missing_signal_families);
const checks: string[] = [];
if (missing.has("profit_or_clean_margin")) {
checks.push("чистую прибыль через себестоимость продаж, расходы и закрытие периода");
} else if (missing.has("profit_or_margin")) {
checks.push("прибыль/маржу по проверенному финрезультату");
}
if (missing.has("tax_position")) {
checks.push("НДС/налоговую позицию за явный период");
}
if (missing.has("debt_due_date_aging_quality")) {
checks.push("договорные сроки оплаты и due-date aging");
} else if (missing.has("debt_open_settlement_quality")) {
checks.push("качество открытых расчетов");
} else if (missing.has("debt_position")) {
checks.push("долговой срез на дату");
}
if (missing.has("inventory_reserve_liquidation_quality")) {
checks.push("резервы, списания, неликвидность и ликвидационную стоимость склада");
} else if (missing.has("inventory_liquidity_quality")) {
checks.push("FIFO-оборачиваемость и подтвержденную складскую ликвидность");
} else if (missing.has("inventory_turnover_quality")) {
checks.push("скорость продаж и оборачиваемость склада");
} else if (missing.has("inventory_position")) {
checks.push("складской остаток на дату");
}
const target = checks.length > 0
? checks.join("; ")
: "оставшиеся непроверенные области по выбранному контуру";
return `Следующий шаг для полного бизнес-аудита: отдельно проверить ${target}, не смешивая эти будущие проверки с уже подтвержденным обзором.`;
}
function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpDiscoveryPilotExecutionContract): string {
const askedMonthlyBreakdown =
pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" ||
@ -386,6 +474,77 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
if (isEntityResolutionPilot(pilot) && mode === "confirmed_with_bounded_inference") {
return "По каталогу 1С найден вероятный контрагент; это заземление сущности для следующего шага, а не еще бизнес-ответ по данным.";
}
if (isInventoryTemplatePilot(pilot) && mode === "confirmed_with_bounded_inference") {
return "По exact inventory runtime в 1С найдены подтвержденные строки; ответ ограничен проверенным складским/товарным срезом.";
}
if (isInventoryTemplatePilot(pilot) && mode === "checked_sources_only") {
if (pilot.mcp_execution_performed) {
return "Exact inventory runtime был проверен, но подтвержденный складской/товарный факт в найденных строках не получен.";
}
return "Инвентарный route-template уже выбран, но live-исполнение этого generic MCP контура еще не подключено; складской/товарный факт не подтвержден.";
}
if (isBusinessOverviewPilot(pilot) && pilot.derived_business_overview && mode === "confirmed_with_bounded_inference") {
const overview = pilot.derived_business_overview;
const families: string[] = [];
if (
overview.incoming_customer_revenue.rows_with_amount > 0 ||
overview.outgoing_supplier_payout.rows_with_amount > 0
) {
families.push("денежный поток");
}
if (overview.yearly_breakdown?.length) {
families.push("годовая operating-flow динамика");
}
if (overview.activity_period) {
families.push("активность");
}
if (overview.tax_position) {
families.push("НДС-позиция");
}
if (overview.trading_margin_proxy) {
families.push("торговый margin proxy");
}
if (overview.debt_position) {
families.push("долговой срез на дату");
}
if (overview.debt_open_settlement_quality) {
families.push("качество открытых расчетов");
if (overview.debt_open_settlement_quality.age_signal) {
families.push("возрастной сигнал открытых расчетов");
}
}
if (overview.debt_staleness_risk_proxy) {
families.push("staleness risk proxy открытых расчетов");
}
if (overview.inventory_position) {
families.push("складской срез на дату");
}
if (overview.inventory_turnover_proxy) {
families.push("оборотный proxy склада");
}
if (overview.inventory_staleness_risk_proxy) {
families.push("staleness risk proxy склада");
}
const unknownFamilies = [overview.trading_margin_proxy ? "чистая прибыль/точная маржа" : "прибыль/маржа"];
if (!overview.tax_position) {
unknownFamilies.push("НДС");
}
if (!overview.debt_position) {
unknownFamilies.push("долговой срез");
}
unknownFamilies.push(
overview.debt_staleness_risk_proxy
? "договорные сроки оплаты/due-date просрочка"
: overview.debt_open_settlement_quality
? "due-date просрочка"
: "качество открытых расчетов"
);
unknownFamilies.push(businessOverviewInventoryUnknownLabel(overview));
return `По данным 1С собран ограниченный бизнес-обзор: ${families.join(", ")} подтверждены найденными строками; ${unknownFamilies.join(", ")} остаются отдельными непроверенными областями.`;
}
if (isBusinessOverviewPilot(pilot) && mode === "checked_sources_only") {
return "Бизнес-обзор был запущен, но подтвержденные денежные или activity-сигналы в найденных строках не получены.";
}
if (isEntityResolutionPilot(pilot) && mode === "needs_clarification") {
return "По каталогу 1С нашлось несколько похожих контрагентов, и без уточнения нельзя честно выбрать правильную сущность.";
}
@ -403,16 +562,16 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
return `По движениям${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
}
if (isCatalogDrilldownPilot(pilot) && mode === "confirmed_with_bounded_inference") {
return "По метаданным 1С удалось углубиться в контур справочников и связанных объектов; это уже не общий обзор схемы, а следующий безопасный catalog drilldown.";
return "По схеме 1С удалось углубиться в контур справочников и связанных объектов; это следующий безопасный шаг по проверенной схеме, а не бизнес-обороты.";
}
if (pilot.derived_metadata_surface && mode === "confirmed_with_bounded_inference") {
if (pilot.derived_metadata_surface.ambiguity_detected) {
return "По метаданным 1С найдены конкурирующие schema-поверхности; перед следующим шагом нужно удержать неоднозначность явно.";
return "По схеме 1С найдены несколько конкурирующих контуров; перед следующим шагом нужно явно выбрать нужный тип данных.";
}
if (pilot.derived_metadata_surface.downstream_route_family) {
return "По метаданным 1С найдена схема и заземлена вероятная поверхность для следующего безопасного шага.";
return "По схеме 1С найдены подходящие объекты; можно безопасно выбрать следующий контур проверки.";
}
return "По метаданным 1С найдена доступная схема для дальнейшего безопасного поиска.";
return "По схеме 1С найдены доступные объекты для дальнейшего безопасного поиска.";
}
if (askedMonthlyBreakdown && pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто и помесячная раскладка могут называться только как расчет по найденным строкам и проверенному периоду.";
@ -451,7 +610,7 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С.";
}
if (mode === "needs_clarification" && isMetadataLaneChoiceClarification(pilot)) {
return "По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя.";
return "По проверенной схеме 1С видно несколько возможных контуров, и без явного выбора дальше идти нельзя.";
}
if (mode === "needs_clarification" && isMovementLaneClarification(pilot)) {
const need = clarificationNeedRu(pilot);
@ -523,18 +682,29 @@ function nextStepFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
if (mode === "needs_clarification") {
return "Уточните контрагента, период или организацию, и я смогу выполнить проверку по 1С.";
}
if (mode === "checked_sources_only" && isInventoryTemplatePilot(pilot)) {
if (pilot.mcp_execution_performed) {
return "Можно уточнить дату, организацию, склад, поставщика или позицию и повторить exact inventory проверку.";
}
return "Следующий шаг - связать inventory route-template с exact inventory runtime и затем проверить live-прогоном.";
}
if (mode === "confirmed_with_bounded_inference" && isBusinessOverviewPilot(pilot)) {
return pilot.derived_business_overview
? businessOverviewNextStepLine(pilot.derived_business_overview)
: "Если нужен уже управленческий вывод, следующим шагом стоит отдельно проверить прибыль/маржу, долги, НДС и складскую ликвидность, а затем собрать полный бизнес-аудит.";
}
if (mode === "confirmed_with_bounded_inference" && pilot.derived_metadata_surface) {
const surface = pilot.derived_metadata_surface;
if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) {
return `Следующим шагом лучше сузить surface до одного семейства: ${surface.ambiguity_entity_sets.join(", ")}.`;
return `Следующим шагом лучше выбрать один контур схемы: ${surface.ambiguity_entity_sets.join(", ")}.`;
}
const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family);
if (surface.selected_entity_set && routeLabel) {
return `Следующим шагом могу пойти в ${routeLabel} по surface «${surface.selected_entity_set}» и уже искать подтвержденные данные, а не только схему.`;
return `Следующим шагом могу пойти в ${routeLabel} по типу «${surface.selected_entity_set}» и уже искать подтвержденные данные, а не только схему.`;
}
}
if (mode === "checked_sources_only" && pilot.query_limitations.length > 0) {
return "Можно повторить проверку после восстановления MCP-доступа или сузить вопрос до конкретного контрагента/периода.";
return "Можно повторить проверку после восстановления доступа к 1С или сузить вопрос до конкретного контрагента/периода.";
}
if (mode === "blocked") {
return "Нужно сначала снять policy/blocking причину, иначе данные 1С использовать нельзя.";
@ -555,6 +725,20 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
claims.push("Do not claim full all-time turnover unless the checked period and coverage prove it.");
claims.push("Do not present a derived sum as a legal/accounting final total outside the checked 1C rows.");
}
if (isBusinessOverviewPilot(pilot)) {
claims.push("Do not present business overview cash-flow spread as profit or margin.");
claims.push("Do not present business overview yearly operating-flow breakdown as profit, financial result, or a complete annual P&L.");
claims.push("Do not present business overview trading-margin proxy as clean profit, accounting financial result, or exact cost-of-sales margin.");
claims.push("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked.");
claims.push("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis.");
claims.push("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt.");
claims.push("Do not present business overview debt staleness risk proxy as confirmed overdue debt, contractual delinquency, credit risk, or due-date aging.");
claims.push("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health.");
claims.push("Do not present business overview inventory turnover proxy as full inventory liquidity, FIFO turnover, obsolescence analysis, or liquidation value.");
claims.push("Do not present business overview inventory staleness risk proxy as confirmed obsolete stock, reserve, write-off, or liquidation value.");
claims.push("Do not expose business_overview_route_template_v1 or MCP primitive names in the user answer.");
}
if (pilot.derived_ranked_value_flow) {
claims.push("Do not present a bounded ranking as a complete all-time ranking outside the checked period and organization.");
claims.push("Do not imply the top-ranked counterparty is globally final when probe-limit or scope boundaries still exist.");
@ -577,6 +761,13 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
claims.push("Do not claim legal identity uniqueness when several catalog candidates are still plausible.");
claims.push("Do not imply that the resolved entity has already been used in a downstream data probe.");
}
if (isInventoryTemplatePilot(pilot)) {
if (!pilot.mcp_execution_performed) {
claims.push("Do not present inventory route-template planning as executed stock, supplier, purchase, or sale evidence.");
}
claims.push("Do not expose inventory_route_template_v1 or MCP primitive names in the user answer.");
claims.push("Do not claim full inventory coverage outside the checked rows, date, organization, item, or supplier scope.");
}
if (pilot.evidence.confirmed_facts.length === 0) {
claims.push("Do not claim a confirmed business fact when confirmed_facts is empty.");
}
@ -638,22 +829,21 @@ function derivedMetadataConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecution
const scope = surface.metadata_scope ? ` по области "${surface.metadata_scope}"` : "";
const entitySets =
surface.available_entity_sets.length > 0
? ` Типы объектов: ${surface.available_entity_sets.join(", ")}.`
? ` Тип объектов: ${surface.available_entity_sets.join(", ")}.`
: "";
const objects =
surface.matched_objects.length > 0
? ` Найденные объекты: ${surface.matched_objects.slice(0, 8).join(", ")}.`
: "";
const selectedEntitySet = surface.selected_entity_set ? ` Выбранное family: ${surface.selected_entity_set}.` : "";
const selectedObjects =
surface.selected_surface_objects.length > 0
? ` Выбранные surface-объекты: ${surface.selected_surface_objects.slice(0, 6).join(", ")}.`
? ` Для следующего шага подходят: ${surface.selected_surface_objects.slice(0, 6).join(", ")}.`
: "";
const fields =
surface.available_fields.length > 0
? ` Доступные поля/секции: ${surface.available_fields.slice(0, 12).join(", ")}.`
: "";
return `Подтвержденная metadata-поверхность 1С${scope}: ${surface.matched_rows} строк metadata-ответа.${entitySets}${objects}${selectedEntitySet}${selectedObjects}${fields}`.replace(/\s+/g, " ").trim();
return `В схеме 1С${scope} найдены подтвержденные объекты: ${surface.matched_rows}.${entitySets}${objects}${selectedObjects}${fields}`.replace(/\s+/g, " ").trim();
}
function derivedMetadataInferenceLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
@ -662,13 +852,13 @@ function derivedMetadataInferenceLine(pilot: AssistantMcpDiscoveryPilotExecution
return null;
}
if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) {
return `По подтвержденной metadata-поверхности видно несколько конкурирующих family: ${surface.ambiguity_entity_sets.join(", ")}. Следующий data-lane пока нельзя выбрать без явного сужения.`;
return `По проверенной схеме видно несколько возможных контуров: ${surface.ambiguity_entity_sets.join(", ")}. Следующий шаг пока нельзя выбрать без явного сужения.`;
}
const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family);
if (!surface.selected_entity_set || !routeLabel) {
return null;
}
return `По подтвержденной metadata-поверхности следующий проверяемый шаг можно ограниченно оценить как ${routeLabel} через family «${surface.selected_entity_set}». Это еще не выполненный data-fetch, а только grounded выбор следующего контура.`;
return `Следующий проверяемый шаг можно вести в ${routeLabel} через тип «${surface.selected_entity_set}». Это пока выбор контура по схеме 1С, а не уже полученные бизнес-строки.`;
}
function derivedEntityResolutionConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
@ -720,6 +910,16 @@ function derivedRankedValueFlowConfirmedLine(pilot: AssistantMcpDiscoveryPilotEx
const leader = ranking.ranked_values[0];
const organization = ranking.organization_scope ? ` по организации ${ranking.organization_scope}` : "";
const period = ranking.period_scope ? ` за период ${ranking.period_scope}` : " в проверенном окне";
if (ranking.ranked_values.length === 1) {
const singleLead =
ranking.value_flow_direction === "outgoing_supplier_payout"
? "В проверенных исходящих платежах найден один контрагент"
: "В проверенных входящих поступлениях найден один контрагент";
const limitCaveat = ranking.coverage_limited_by_probe_limit
? " Лимит строк проверки достигнут; сравнение с другими контрагентами может быть неполным."
: " Других контрагентов в этом проверенном срезе не найдено, поэтому это не полноценный сравнительный рейтинг.";
return `${singleLead} ${leader.axis_value}${organization}${period}: ${leader.total_amount_human_ru} по ${leader.rows_with_amount} строкам с суммой.${limitCaveat}`;
}
const directionLead =
ranking.ranking_need === "bottom_asc"
? ranking.value_flow_direction === "outgoing_supplier_payout"
@ -832,6 +1032,379 @@ function derivedBidirectionalValueFlowMonthlyLines(pilot: AssistantMcpDiscoveryP
);
}
function businessOverviewNetDirectionRu(direction: "net_incoming" | "net_outgoing" | "balanced"): string {
if (direction === "net_incoming") {
return "операционный денежный поток в проверенном срезе больше входящий, чем исходящий";
}
if (direction === "net_outgoing") {
return "операционный денежный поток в проверенном срезе больше исходящий, чем входящий";
}
return "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
}
function amountHumanRu(value: number): string {
const rounded = Math.round(Math.abs(value) * 100) / 100;
return `${new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 2 }).format(rounded)} руб.`;
}
function yearCountHumanRu(count: number): string {
const abs = Math.abs(count) % 100;
const last = abs % 10;
const noun =
abs >= 11 && abs <= 14
? "лет"
: last === 1
? "год"
: last >= 2 && last <= 4
? "года"
: "лет";
return `${count} ${noun}`;
}
function percentOfTotal(part: number, total: number): number | null {
if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) {
return null;
}
return Math.round((part / total) * 10_000) / 100;
}
function percentText(part: number, total: number): string | null {
const pct = percentOfTotal(part, total);
return pct === null ? null : `${pct}%`;
}
function inventoryStalenessRiskBandRu(
riskBand: NonNullable<BusinessOverview["inventory_staleness_risk_proxy"]>["risk_band"]
): string {
if (riskBand === "high") {
return "высокая зона внимания";
}
if (riskBand === "elevated") {
return "повышенная зона внимания";
}
if (riskBand === "watch") {
return "зона наблюдения";
}
return "низкий видимый риск";
}
function debtStalenessRiskBandRu(
riskBand: NonNullable<BusinessOverview["debt_staleness_risk_proxy"]>["risk_band"]
): string {
if (riskBand === "high") {
return "высокая зона внимания";
}
if (riskBand === "elevated") {
return "повышенная зона внимания";
}
if (riskBand === "watch") {
return "зона наблюдения";
}
return "низкий видимый риск";
}
function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
const overview = pilot.derived_business_overview;
if (!overview) {
return [];
}
const organization = overview.organization_scope ? ` по организации ${overview.organization_scope}` : "";
const period = overview.period_scope ? ` за ${overview.period_scope}` : " за все доступное проверенное окно";
const lines: string[] = [];
if (overview.incoming_customer_revenue.rows_with_amount > 0) {
lines.push(
`Входящие поступления${organization}${period}: ${overview.incoming_customer_revenue.total_amount_human_ru} по ${overview.incoming_customer_revenue.rows_with_amount} строкам с суммой.`
);
}
if (overview.outgoing_supplier_payout.rows_with_amount > 0) {
lines.push(
`Исходящие платежи/списания${organization}${period}: ${overview.outgoing_supplier_payout.total_amount_human_ru} по ${overview.outgoing_supplier_payout.rows_with_amount} строкам с суммой.`
);
}
const leader = overview.top_customers[0];
if (leader) {
lines.push(`Самый крупный подтвержденный клиент в проверенном срезе: ${leader.axis_value}${leader.total_amount_human_ru}.`);
}
const supplierLeader = overview.top_suppliers?.[0];
if (supplierLeader) {
lines.push(
`Самый крупный подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${supplierLeader.axis_value}${supplierLeader.total_amount_human_ru}.`
);
}
if (overview.yearly_breakdown?.length) {
lines.push(
`Годовая раскладка операционного денежного потока построена по подтвержденным строкам 1С за ${yearCountHumanRu(overview.yearly_breakdown.length)}.`
);
}
if (overview.activity_period) {
lines.push(
`Окно подтвержденной активности в 1С: ${overview.activity_period.first_activity_date}${overview.activity_period.latest_activity_date}; ориентировочно ${overview.activity_period.duration_human_ru}.`
);
}
if (overview.tax_position) {
const taxDirection =
overview.tax_position.net_vat_direction === "vat_to_pay"
? "к уплате"
: overview.tax_position.net_vat_direction === "vat_to_recover_or_offset"
? "к вычету/зачету"
: "сбалансирован";
lines.push(
`НДС-позиция за ${overview.tax_position.period_scope}: книга продаж ${overview.tax_position.sales_vat_amount_human_ru}, книга покупок/вычеты ${overview.tax_position.purchase_vat_amount_human_ru}, нетто ${taxDirection} ${overview.tax_position.net_vat_amount_human_ru}.`
);
}
if (overview.trading_margin_proxy) {
const proxy = overview.trading_margin_proxy;
const marginText = proxy.margin_to_revenue_pct === null ? "не рассчитана" : `${proxy.margin_to_revenue_pct}%`;
lines.push(
`Торговый margin proxy за ${proxy.period_scope}: выручка продаж ${proxy.sales_revenue_human_ru}, закупочный документный след ${proxy.purchase_cost_proxy_human_ru}, валовый спред proxy ${proxy.gross_spread_proxy_human_ru}, маржинальность к выручке ${marginText}. Это не чистая прибыль и не бухгалтерский финрезультат.`
);
}
if (overview.debt_position) {
const debtDirection =
overview.debt_position.net_debt_position_direction === "net_receivable"
? "в пользу дебиторки"
: overview.debt_position.net_debt_position_direction === "net_payable"
? "в сторону кредиторки"
: "сбалансировано";
lines.push(
`Долговой срез на ${overview.debt_position.as_of_date}: дебиторка ${overview.debt_position.receivables.total_amount_human_ru}, кредиторка ${overview.debt_position.payables.total_amount_human_ru}, нетто ${debtDirection} ${overview.debt_position.net_debt_position_amount_human_ru}.`
);
}
if (overview.debt_open_settlement_quality) {
const quality = overview.debt_open_settlement_quality;
const topContract = quality.top_contracts[0];
const topContractText = topContract
? ` Крупнейший открытый договор: ${topContract.contract}${topContract.counterparty ? ` / ${topContract.counterparty}` : ""}${topContract.total_amount_human_ru}${topContract.share_of_gross_open_amount_pct === null ? "" : ` (${topContract.share_of_gross_open_amount_pct}%)`}.`
: "";
lines.push(
`Качество открытых расчетов на ${quality.as_of_date}: брутто открытых договорных остатков ${quality.gross_open_amount_human_ru}, договоров ${quality.unique_contracts}, контрагентов ${quality.unique_counterparties}.${topContractText}`
);
if (quality.age_signal?.oldest_start_date) {
const ageText = quality.age_signal.max_age_days === null
? ""
: `, максимальный возраст сигнала ${quality.age_signal.max_age_days} дн.`;
lines.push(
`Возрастной сигнал открытых расчетов: самая ранняя найденная дата договора ${quality.age_signal.oldest_start_date}${ageText}. Это не просрочка и не due-date анализ.`
);
}
}
if (overview.debt_staleness_risk_proxy) {
const proxy = overview.debt_staleness_risk_proxy;
const counterpartyText = proxy.top_contract_counterparty ? ` / ${proxy.top_contract_counterparty}` : "";
lines.push(
`Staleness risk proxy открытых расчетов на ${proxy.as_of_date}: самый старый договорный сигнал ${proxy.oldest_contract_start_date}, возраст ${proxy.max_contract_age_days} дн.; старейший крупный договор ${proxy.top_contract}${counterpartyText} держит ${proxy.top_contract_amount_human_ru} (${proxy.top_contract_share_pct}% брутто открытых остатков); оценка ${debtStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная просрочка, не кредитный риск и не due-date aging.`
);
}
if (overview.inventory_position) {
const leader = overview.inventory_position.top_items[0];
const leaderText = leader
? ` Крупнейшая подтвержденная позиция: ${leader.item}${leader.total_amount_human_ru}.`
: "";
lines.push(
`Складской срез на ${overview.inventory_position.as_of_date}: остаток ${overview.inventory_position.total_amount_human_ru} по ${overview.inventory_position.rows_with_amount} строкам с суммой и ${overview.inventory_position.rows_with_quantity} строкам с количеством.${leaderText}`
);
if (overview.inventory_position.aging_signal?.oldest_purchase_date) {
const ageText = overview.inventory_position.aging_signal.max_age_days === null
? ""
: `, максимальный возраст сигнала ${overview.inventory_position.aging_signal.max_age_days} дн.`;
lines.push(
`Возрастной сигнал склада: самая ранняя найденная дата закупки ${overview.inventory_position.aging_signal.oldest_purchase_date}${ageText}.`
);
}
}
if (overview.inventory_turnover_proxy) {
const proxy = overview.inventory_turnover_proxy;
const ratioText = proxy.sales_to_stock_amount_ratio === null
? "не рассчитано"
: `${proxy.sales_to_stock_amount_ratio}x`;
const stockShareText = proxy.stock_to_sales_revenue_pct === null
? "не рассчитана"
: `${proxy.stock_to_sales_revenue_pct}%`;
lines.push(
`Оборотный proxy склада за ${proxy.period_scope}: продажи ${proxy.sales_revenue_human_ru}, остаток на ${proxy.as_of_date} ${proxy.inventory_amount_human_ru}, sales-to-stock ratio ${ratioText}, остаток к продажам ${stockShareText}. Это не полноценная складская ликвидность, не FIFO-оборачиваемость и не анализ устаревания.`
);
}
if (overview.inventory_staleness_risk_proxy) {
const proxy = overview.inventory_staleness_risk_proxy;
lines.push(
`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`
);
}
return lines;
}
function businessOverviewCashSynthesisLine(overview: BusinessOverview): string | null {
const incoming = overview.incoming_customer_revenue;
const outgoing = overview.outgoing_supplier_payout;
if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) {
return null;
}
const checkedOperationalScale = Math.abs(incoming.total_amount) + Math.abs(outgoing.total_amount);
return [
`Аналитический вывод по оборотам: проверенный операционный размах ${amountHumanRu(checkedOperationalScale)}; входящий поток ${incoming.total_amount_human_ru}, исходящий ${outgoing.total_amount_human_ru}.`,
`${businessOverviewNetDirectionRu(overview.net_direction)}; расчетное нетто ${overview.net_amount_human_ru}.`
].join(" ");
}
function businessOverviewCustomerConcentrationLine(overview: BusinessOverview): string | null {
const leader = overview.top_customers[0];
if (!leader || overview.incoming_customer_revenue.total_amount <= 0) {
return null;
}
const share = percentText(leader.total_amount, overview.incoming_customer_revenue.total_amount);
return share
? `Концентрация входящего потока: крупнейший подтвержденный клиент ${leader.axis_value} дает около ${share} проверенных входящих поступлений (${leader.total_amount_human_ru}). Это сигнал зависимости от клиента, а не полный customer-risk аудит.`
: `Крупнейший подтвержденный клиент в проверенном срезе: ${leader.axis_value}${leader.total_amount_human_ru}.`;
}
function businessOverviewSupplierConcentrationLine(overview: BusinessOverview): string | null {
const leader = overview.top_suppliers?.[0];
if (!leader || overview.outgoing_supplier_payout.total_amount <= 0) {
return null;
}
const share = percentText(leader.total_amount, overview.outgoing_supplier_payout.total_amount);
return share
? `Концентрация исходящего потока: крупнейший подтвержденный поставщик/получатель исходящих платежей ${leader.axis_value} держит около ${share} проверенных исходящих платежей (${leader.total_amount_human_ru}). Это сигнал procurement concentration по найденным строкам, а не полный vendor-risk аудит или структура всех расходов.`
: `Крупнейший подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${leader.axis_value}${leader.total_amount_human_ru}.`;
}
function businessOverviewYearlyOperatingLine(overview: BusinessOverview): string | null {
const years = overview.yearly_breakdown ?? [];
if (years.length === 0) {
return null;
}
const strongestIncomingYear = [...years]
.filter((bucket) => bucket.incoming_total_amount > 0)
.sort((left, right) => right.incoming_total_amount - left.incoming_total_amount || left.year_bucket.localeCompare(right.year_bucket))[0];
const strongestNetYear = [...years]
.filter((bucket) => bucket.net_amount !== 0)
.sort((left, right) => right.net_amount - left.net_amount || left.year_bucket.localeCompare(right.year_bucket))[0];
if (!strongestIncomingYear && !strongestNetYear) {
return null;
}
const parts: string[] = [];
if (strongestIncomingYear) {
parts.push(`самый сильный год по подтвержденным входящим поступлениям ${strongestIncomingYear.year_bucket}: ${strongestIncomingYear.incoming_total_amount_human_ru}`);
}
if (strongestNetYear) {
const netText = strongestNetYear.net_direction === "net_outgoing"
? `нетто исходящее ${strongestNetYear.net_amount_human_ru}`
: `нетто в плюс ${strongestNetYear.net_amount_human_ru}`;
parts.push(`лучший год по расчетному операционному нетто ${strongestNetYear.year_bucket}: ${netText}`);
}
return `Годовая динамика по проверенным строкам: ${parts.join("; ")}. Это operating-flow proxy, не бухгалтерская прибыль и не финрезультат.`;
}
function businessOverviewRiskSynthesisLine(overview: BusinessOverview): string | null {
const signals: string[] = [];
if (overview.tax_position) {
const taxDirection =
overview.tax_position.net_vat_direction === "vat_to_pay"
? `НДС к уплате ${overview.tax_position.net_vat_amount_human_ru}`
: overview.tax_position.net_vat_direction === "vat_to_recover_or_offset"
? `НДС к вычету/зачету ${overview.tax_position.net_vat_amount_human_ru}`
: "НДС-позиция сбалансирована";
signals.push(taxDirection);
}
if (overview.trading_margin_proxy) {
const marginText = overview.trading_margin_proxy.margin_to_revenue_pct === null
? "маржинальность не рассчитана"
: `маржинальность proxy ${overview.trading_margin_proxy.margin_to_revenue_pct}%`;
signals.push(`торговый спред proxy ${overview.trading_margin_proxy.gross_spread_proxy_human_ru}, ${marginText}`);
}
if (overview.debt_position) {
const debtDirection =
overview.debt_position.net_debt_position_direction === "net_receivable"
? `дебиторка больше кредиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
: overview.debt_position.net_debt_position_direction === "net_payable"
? `кредиторка больше дебиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
: "дебиторка и кредиторка сбалансированы";
signals.push(debtDirection);
}
if (overview.debt_open_settlement_quality?.concentration_top_contract_pct !== null && overview.debt_open_settlement_quality?.top_contracts[0]) {
const topContract = overview.debt_open_settlement_quality.top_contracts[0];
signals.push(`крупнейший открытый договор держит ${overview.debt_open_settlement_quality.concentration_top_contract_pct}% открытых остатков (${topContract.total_amount_human_ru})`);
}
if (overview.debt_open_settlement_quality?.age_signal?.max_age_days !== null && overview.debt_open_settlement_quality?.age_signal?.max_age_days !== undefined) {
signals.push(`самый старый договорный возрастной сигнал ${overview.debt_open_settlement_quality.age_signal.max_age_days} дн.`);
}
if (overview.debt_staleness_risk_proxy) {
signals.push(
`staleness risk proxy открытых расчетов: ${debtStalenessRiskBandRu(overview.debt_staleness_risk_proxy.risk_band)}, возраст ${overview.debt_staleness_risk_proxy.max_contract_age_days} дн., концентрация старейшего крупного договора ${overview.debt_staleness_risk_proxy.top_contract_share_pct}%`
);
}
if (overview.inventory_position) {
signals.push(`складской остаток на дату ${overview.inventory_position.total_amount_human_ru}`);
if (overview.inventory_position.aging_signal?.max_age_days !== null && overview.inventory_position.aging_signal?.max_age_days !== undefined) {
signals.push(`самый старый складской purchase-date сигнал ${overview.inventory_position.aging_signal.max_age_days} дн.`);
}
}
if (overview.inventory_turnover_proxy) {
const ratioText = overview.inventory_turnover_proxy.sales_to_stock_amount_ratio === null
? "sales-to-stock не рассчитан"
: `sales-to-stock ${overview.inventory_turnover_proxy.sales_to_stock_amount_ratio}x`;
signals.push(`оборотный proxy склада: ${ratioText}`);
}
if (overview.inventory_staleness_risk_proxy) {
signals.push(
`staleness risk proxy склада: ${inventoryStalenessRiskBandRu(overview.inventory_staleness_risk_proxy.risk_band)}, возраст ${overview.inventory_staleness_risk_proxy.max_purchase_age_days} дн.`
);
}
return signals.length > 0
? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.`
: null;
}
function businessOverviewExecutiveVerdictLine(overview: BusinessOverview): string | null {
const hasCash = overview.incoming_customer_revenue.rows_with_amount > 0 || overview.outgoing_supplier_payout.rows_with_amount > 0;
const hasExtraSignals = Boolean(
overview.tax_position ||
overview.trading_margin_proxy ||
overview.debt_position ||
overview.debt_open_settlement_quality ||
overview.debt_staleness_risk_proxy ||
overview.inventory_position ||
overview.inventory_turnover_proxy ||
overview.inventory_staleness_risk_proxy
);
if (!hasCash && !hasExtraSignals) {
return null;
}
const cashTone =
overview.net_direction === "net_incoming"
? "операционно входящий поток сильнее исходящего"
: overview.net_direction === "net_outgoing"
? "операционно исходящий поток сильнее входящего, это зона внимания к расходам/закупкам"
: "операционный поток выглядит сбалансированным";
const evidenceTone = hasExtraSignals
? "часть налоговых, долговых или складских контуров уже отдельно проверена"
: "налоги, долги и склад еще не дают проверенного управленческого контекста";
return `Сводный LLM-аудит по подтвержденному: ${cashTone}; ${evidenceTone}. Это полезный управленческий срез по найденным строкам 1С, но не финальный вывод о прибыльности, марже или здоровье компании.`;
}
function derivedBusinessOverviewInferenceLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
const overview = pilot.derived_business_overview;
if (!overview) {
return [];
}
return [
businessOverviewCashSynthesisLine(overview),
businessOverviewCustomerConcentrationLine(overview),
businessOverviewSupplierConcentrationLine(overview),
businessOverviewYearlyOperatingLine(overview),
businessOverviewRiskSynthesisLine(overview),
businessOverviewExecutiveVerdictLine(overview),
"Это аналитическая интерпретация подтвержденных строк, а не прибыль и не маржа: для финального управленческого вывода нужны отдельные расходы, себестоимость, закрывающие документы, долги, налоги и складская оборачиваемость."
].filter((line): line is string => Boolean(line));
}
function businessOverviewUnknownLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
if (!pilot.derived_business_overview) {
return userFacingUnknowns(pilot.evidence.unknown_facts);
}
return userFacingUnknowns(pilot.evidence.unknown_facts);
}
export function buildAssistantMcpDiscoveryAnswerDraft(
pilot: AssistantMcpDiscoveryPilotExecutionContract
): AssistantMcpDiscoveryAnswerDraftContract {
@ -841,17 +1414,23 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
if (pilot.evidence.unknown_facts.length > 0) {
pushReason(reasonCodes, "answer_contains_unknown_fact_boundary");
}
if (pilot.evidence.inferred_facts.length > 0) {
pushReason(reasonCodes, "answer_contains_bounded_inference");
}
const businessOverviewInferenceLines = derivedBusinessOverviewInferenceLines(pilot);
const derivedInferenceLine =
derivedActivityInferenceLine(pilot) ??
derivedMetadataInferenceLine(pilot) ??
derivedRankedValueFlowInferenceLine(pilot) ??
derivedEntityResolutionInferenceLine(pilot);
const inferenceLines = derivedInferenceLine
const inferenceLines = businessOverviewInferenceLines.length > 0
? businessOverviewInferenceLines
: derivedInferenceLine
? [derivedInferenceLine]
: pilot.evidence.inferred_facts;
if (inferenceLines.length > 0) {
pushReason(reasonCodes, "answer_contains_bounded_inference");
}
if (businessOverviewInferenceLines.length > 0) {
pushReason(reasonCodes, "answer_contains_business_overview_analyst_synthesis");
}
const derivedMetadataLine = derivedMetadataConfirmedLine(pilot);
const derivedEntityResolutionLine = derivedEntityResolutionConfirmedLine(pilot);
const derivedValueLine =
@ -862,18 +1441,64 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
derivedBidirectionalValueFlowMonthlyLines(pilot).length > 0
? derivedBidirectionalValueFlowMonthlyLines(pilot)
: derivedValueFlowMonthlyLines(pilot);
const businessOverviewLines = derivedBusinessOverviewConfirmedLines(pilot);
if (monthlyConfirmedLines.length > 0) {
pushReason(reasonCodes, "answer_contains_monthly_breakdown");
}
const confirmedLines = pilot.derived_ranked_value_flow && derivedValueLine
if (businessOverviewLines.length > 0) {
pushReason(reasonCodes, "answer_contains_business_overview");
}
if (pilot.derived_business_overview?.tax_position) {
pushReason(reasonCodes, "answer_contains_business_overview_tax_position");
}
if (pilot.derived_business_overview?.trading_margin_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_trading_margin_proxy");
}
if (pilot.derived_business_overview?.top_suppliers?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_supplier_concentration");
}
if (pilot.derived_business_overview?.yearly_breakdown?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_yearly_operating_breakdown");
}
if (pilot.derived_business_overview?.debt_position) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_position");
}
if (pilot.derived_business_overview?.debt_open_settlement_quality) {
pushReason(reasonCodes, "answer_contains_business_overview_open_settlement_quality");
if (pilot.derived_business_overview.debt_open_settlement_quality.age_signal) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_age_signal");
}
}
if (pilot.derived_business_overview?.debt_staleness_risk_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_staleness_risk_proxy");
}
if (pilot.derived_business_overview?.inventory_position) {
pushReason(reasonCodes, "answer_contains_business_overview_inventory_position");
}
if (pilot.derived_business_overview?.inventory_turnover_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_inventory_turnover_proxy");
}
if (pilot.derived_business_overview?.inventory_staleness_risk_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_inventory_staleness_risk_proxy");
}
const confirmedLines = businessOverviewLines.length > 0
? businessOverviewLines
: pilot.derived_ranked_value_flow && derivedValueLine
? [derivedValueLine]
: derivedValueLine
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
: derivedEntityResolutionLine
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
: derivedMetadataLine
? [...pilot.evidence.confirmed_facts, derivedMetadataLine]
? [derivedMetadataLine]
: pilot.evidence.confirmed_facts;
const unknownLines = pilot.derived_business_overview
? businessOverviewUnknownLines(pilot)
: pilot.derived_metadata_surface
? pilot.derived_metadata_surface.available_fields.length > 0
? userFacingUnknowns(pilot.evidence.unknown_facts)
: ["Детальный список полей этих объектов этим шагом не получен."]
: rankedValueFlowUnknownLines(pilot);
return {
schema_version: ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
@ -882,7 +1507,7 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
headline: headlineFor(mode, pilot),
confirmed_lines: uniqueStrings(confirmedLines),
inference_lines: uniqueStrings(inferenceLines),
unknown_lines: rankedValueFlowUnknownLines(pilot),
unknown_lines: unknownLines,
limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]),
next_step_line: nextStepFor(mode, pilot),
internal_mechanics_allowed: false,

View File

@ -86,6 +86,50 @@ function businessFactFamilyFor(input: {
if (combined.includes("entity discovery") || combined.includes("entity_resolution")) {
return "entity_grounding";
}
if (
combined.includes("broad_business_evaluation") ||
combined.includes("broad_evaluation") ||
combined.includes("business overview") ||
combined.includes("business_overview") ||
combined.includes("company analysis") ||
combined.includes("business audit")
) {
return "business_overview";
}
if (
combined.includes("inventory") ||
combined.includes("stock") ||
combined.includes("warehouse") ||
combined.includes("item provenance") ||
combined.includes("purchase provenance")
) {
if (
combined.includes("sale_trace") ||
combined.includes("sale trace") ||
combined.includes("buyer") ||
combined.includes("purchase_to_sale") ||
combined.includes("purchase-to-sale")
) {
return "inventory_sale_trace";
}
if (
combined.includes("supplier_overlap") ||
combined.includes("supplier overlap") ||
combined.includes("supplier stock") ||
combined.includes("stock by supplier")
) {
return "inventory_supplier_overlap";
}
if (
combined.includes("purchase_provenance") ||
combined.includes("purchase provenance") ||
combined.includes("purchase document") ||
combined.includes("supplier provenance")
) {
return "inventory_purchase_provenance";
}
return "inventory_stock_snapshot";
}
if (combined.includes("lifecycle") || combined.includes("activity")) {
return "activity_lifecycle";
}
@ -140,6 +184,15 @@ function timeScopeNeedFor(input: {
if (input.family === "activity_lifecycle") {
return "open_activity_window";
}
if (input.family === "business_overview") {
return input.explicitDateScope ? "explicit_period" : "all_time_scope";
}
if (input.family === "inventory_stock_snapshot" || input.family === "inventory_supplier_overlap") {
return input.explicitDateScope ? "explicit_period" : "as_of_date_required";
}
if (input.family === "inventory_purchase_provenance" || input.family === "inventory_sale_trace") {
return input.explicitDateScope ? "explicit_period" : null;
}
return null;
}
@ -251,6 +304,9 @@ function proofExpectationFor(input: {
if (input.family === "activity_lifecycle") {
return "bounded_inference";
}
if (input.family === "business_overview") {
return "bounded_inference";
}
return "coverage_checked_fact";
}
@ -328,6 +384,39 @@ function decompositionCandidatesFor(input: {
pushUnique(result, "fetch_supporting_documents");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
return result;
}
if (input.family === "business_overview") {
pushUnique(result, "collect_scoped_movements");
pushUnique(result, "aggregate_checked_amounts");
pushUnique(result, "aggregate_ranked_axis_values");
pushUnique(result, "fetch_supporting_documents");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
return result;
}
if (input.family === "inventory_stock_snapshot") {
pushUnique(result, "fetch_scoped_movements");
pushUnique(result, "aggregate_checked_amounts");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
return result;
}
if (input.family === "inventory_supplier_overlap") {
pushUnique(result, "resolve_entity_reference");
pushUnique(result, "fetch_scoped_movements");
pushUnique(result, "fetch_scoped_documents");
pushUnique(result, "aggregate_checked_amounts");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
return result;
}
if (input.family === "inventory_purchase_provenance" || input.family === "inventory_sale_trace") {
pushUnique(result, "resolve_entity_reference");
pushUnique(result, "fetch_scoped_documents");
pushUnique(result, "drilldown_related_objects");
pushUnique(result, "probe_coverage");
pushUnique(result, "explain_evidence_basis");
}
return result;
}
@ -343,9 +432,23 @@ function forbiddenOverclaimFlagsFor(family: string | null): string[] {
if (family === "activity_lifecycle") {
pushUnique(result, "no_legal_age_claim_without_evidence");
}
if (family === "business_overview") {
pushUnique(result, "no_unchecked_fact_totals");
pushUnique(result, "no_unchecked_business_health_claim");
pushUnique(result, "no_profit_or_margin_claim_without_evidence");
}
if (family === "value_flow" || family === "movement_evidence" || family === "document_evidence") {
pushUnique(result, "no_unchecked_fact_totals");
}
if (family === "inventory_stock_snapshot") {
pushUnique(result, "no_unchecked_stock_snapshot");
}
if (family === "inventory_purchase_provenance" || family === "inventory_supplier_overlap") {
pushUnique(result, "no_unproven_supplier_attribution");
}
if (family === "inventory_sale_trace") {
pushUnique(result, "no_unproven_buyer_or_sale_trace");
}
return result;
}
@ -394,6 +497,9 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
family: businessFactFamily,
subjectResolutionOptional
});
const inventoryStockSnapshotWithoutSubject =
subjectCandidates.length === 0 &&
businessFactFamily === "inventory_stock_snapshot";
const clarificationGaps: string[] = [];
if (unsupported === "metadata_lane_choice_clarification" || action === "resolve_next_lane") {
pushUnique(clarificationGaps, "lane_family_choice");
@ -411,11 +517,19 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
!explicitOrganizationScope
) {
pushUnique(clarificationGaps, "organization");
} else if (
subjectCandidates.length === 0 &&
businessFactFamily === "business_overview" &&
!explicitOrganizationScope
) {
pushUnique(clarificationGaps, "organization");
} else if (
subjectCandidates.length === 0 &&
businessFactFamily !== "schema_surface" &&
businessFactFamily !== "business_overview" &&
!openScopeWithoutSubject &&
!metadataScopedOpenLaneWithoutSubject
!metadataScopedOpenLaneWithoutSubject &&
!inventoryStockSnapshotWithoutSubject
) {
pushUnique(clarificationGaps, "subject");
}
@ -427,6 +541,9 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
if (timeScopeNeed === "period_required" && !explicitDateScope) {
pushUnique(clarificationGaps, "period");
}
if (timeScopeNeed === "as_of_date_required" && !explicitDateScope) {
pushUnique(clarificationGaps, "as_of_date");
}
const decompositionCandidates = decompositionCandidatesFor({
family: businessFactFamily,
action,
@ -461,6 +578,9 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
if (allTimeScopeHint) {
pushReason(reasonCodes, "data_need_graph_all_time_scope_hint");
}
if (businessFactFamily === "business_overview" && !explicitDateScope) {
pushReason(reasonCodes, "data_need_graph_business_overview_defaults_to_all_time_scope");
}
if (clarificationGaps.includes("organization")) {
pushReason(reasonCodes, "data_need_graph_open_scope_total_needs_organization");
}

View File

@ -6,6 +6,11 @@ export interface AssistantMcpDiscoveryDebugAttachmentFields {
mcp_discovery_attempted: boolean;
mcp_discovery_hot_runtime_wired: false;
mcp_discovery_bridge_status: string | null;
mcp_discovery_selected_chain_id: string | null;
mcp_discovery_catalog_chain_template_matches: string[];
mcp_discovery_catalog_chain_alignment_status: string | null;
mcp_discovery_catalog_chain_top_match: string | null;
mcp_discovery_catalog_chain_selected_matches_top: boolean;
mcp_discovery_answer_mode: string | null;
mcp_discovery_business_fact_answer_allowed: boolean;
mcp_discovery_user_facing_response_allowed: boolean;
@ -32,6 +37,20 @@ function toNonEmptyString(value: unknown): string | null {
return text.length > 0 ? text : null;
}
function toStringArray(value: unknown): string[] {
if (!Array.isArray(value)) {
return [];
}
const result: string[] = [];
for (const item of value) {
const text = toNonEmptyString(item);
if (text && !result.includes(text)) {
result.push(text);
}
}
return result;
}
function isMcpDiscoveryEntryPointContract(value: unknown): value is AssistantMcpDiscoveryRuntimeEntryPointContract {
const record = toRecordObject(value);
return (
@ -56,6 +75,8 @@ export function buildAssistantMcpDiscoveryDebugAttachmentFields(
): AssistantMcpDiscoveryDebugAttachmentFields {
const entryPoint = resolveEntryPoint(input);
const bridge = toRecordObject(entryPoint?.bridge);
const planner = toRecordObject(bridge?.planner);
const chainAlignment = toRecordObject(planner?.catalog_chain_template_alignment);
const answerDraft = toRecordObject(bridge?.answer_draft);
return {
@ -64,6 +85,11 @@ export function buildAssistantMcpDiscoveryDebugAttachmentFields(
mcp_discovery_attempted: Boolean(entryPoint?.discovery_attempted),
mcp_discovery_hot_runtime_wired: false,
mcp_discovery_bridge_status: toNonEmptyString(bridge?.bridge_status),
mcp_discovery_selected_chain_id: toNonEmptyString(planner?.selected_chain_id),
mcp_discovery_catalog_chain_template_matches: toStringArray(planner?.catalog_chain_template_matches),
mcp_discovery_catalog_chain_alignment_status: toNonEmptyString(chainAlignment?.alignment_status),
mcp_discovery_catalog_chain_top_match: toNonEmptyString(chainAlignment?.top_chain_template_match),
mcp_discovery_catalog_chain_selected_matches_top: chainAlignment?.selected_chain_matches_top === true,
mcp_discovery_answer_mode: toNonEmptyString(answerDraft?.answer_mode),
mcp_discovery_business_fact_answer_allowed: bridge?.business_fact_answer_allowed === true,
mcp_discovery_user_facing_response_allowed: bridge?.user_facing_response_allowed === true,

Some files were not shown because too many files have changed in this diff Show More