diff --git a/docs/orchestration/active_domain_contract.json b/docs/orchestration/active_domain_contract.json index 8f4d3f6..7556355 100644 --- a/docs/orchestration/active_domain_contract.json +++ b/docs/orchestration/active_domain_contract.json @@ -1,9 +1,11 @@ { - "schema_version": "active_domain_contract_v1", + "schema_version": "domain_scenario_pack_v1", + "pack_id": "active_counterparty_value_flow_boundary_20260615", + "domain": "agent_counterparty_value_flow_boundary", + "runtime_domain": "business_overview_counterparty_value_flow", + "title": "Counterparty value-flow and bank-role boundary", + "description": "Active slice for the autonomous agent loop: verify that company overview, explicit counterparty value-flow, selected counterparty follow-ups, and bank-role caveats stay separated in user-facing answers.", "status": "active", - "domain_id": "inventory_stock_supplier_provenance", - "runtime_domain": "inventory_stock", - "title": "Складские остатки, происхождение товара и связь с поставщиком", "source_of_truth_policy": { "purpose": "single mutable domain source for the current orchestration target", "switching_rule": "when the project switches to another domain, replace this file instead of rewriting the stable agent canon", @@ -17,1928 +19,216 @@ "docs/orchestration/active_domain_contract.json" ] }, - "domain_goal": "Показать остатки товаров на складе на дату, затем по выбранной позиции или по связанному срезу пройти в происхождение товара, поставщика, закупочные документы, возраст закупки и дальнейшую продажу без потери контекста.", - "default_analysis_context": { - "as_of_date": "2021-09-30", - "source": "active_domain_contract_default" + "analysis_context": { + "as_of_date": "2026-06-15", + "source": "agent_loop_next_domain_slice_after_margin_p12" }, - "business_value": [ - "Пользователь должен сначала получить подтвержденный срез остатков на дату.", - "Пользователь должен затем выбрать реальную позицию из ответа и углубиться без ручного переписывания сущности.", - "Система должна понимать не только канонический вопрос, но и разговорные follow-up формулировки.", - "Для сценария склада acceptance определяется не одиночным ответом, а проходимостью дерева от root snapshot к критическим drilldown-веткам." + "target_score": 88, + "bindings": { + "main_organization": "ООО Альтернатива Плюс", + "control_year": "2020", + "svk_counterparty": "Группа СВК" + }, + "issue_codes_under_test": [ + "counterparty_value_flow_misrouted_to_company_profit", + "bank_counterparty_misclassified_as_business_partner", + "technical_garbage_in_answer", + "business_next_step_missing" ], - "observed_anchors": { - "warehouse": "Основной склад", - "organization": "ООО \\Альтернатива Плюс\\", - "historical_as_of_date": "2019-03-31", - "current_as_of_date_example": "2021-09-30", - "focus_item_current": "Диван трехместный", - "focus_item_historical": "Столешница 600*3050*26 дуб ниагара", - "focus_item_small_residue": "Четки Пост (84*117)", - "supplier_candidate": "Торговый дом \\Союз", - "supplier_candidate_alt": "Гамма-мебель, ООО", - "buyer_candidate": "Департамент капитального ремонта города Москвы" - }, - "question_pool": { - "total_questions": 31, - "core_questions_total": 22, - "followup_checkpoints_total": 9, - "questions": [ - { - "question_id": "Q01", - "text": "Какие товары сейчас лежат на складе", - "layer": "root_snapshot", - "node_id": "N01_stock_snapshot", - "role": "root", - "wording_family": "canonical", - "semantic_goal": "получить актуальный item-level срез остатков на складе" - }, - { - "question_id": "Q02", - "text": "Какие товары лежат на складе на дату ...", - "layer": "root_snapshot", - "node_id": "N01_stock_snapshot", - "role": "root", - "wording_family": "canonical", - "semantic_goal": "получить item-level срез остатков на конкретную дату" - }, - { - "question_id": "Q03", - "text": "Из каких товаров состоит остаток по 41 счету", - "layer": "root_snapshot", - "node_id": "N02_account_41_snapshot", - "role": "root_variant", - "wording_family": "canonical", - "semantic_goal": "получить item-level состав остатка по 41.01" - }, - { - "question_id": "Q04", - "text": "Какие товары числятся на 41 счете на дату ...", - "layer": "root_snapshot", - "node_id": "N02_account_41_snapshot", - "role": "root_variant", - "wording_family": "colloquial", - "semantic_goal": "получить датированный item-level состав остатка по 41.01" - }, - { - "question_id": "Q05", - "text": "Какие конкретно номенклатуры формируют остаток по складу на дату ...", - "layer": "root_snapshot", - "node_id": "N01_stock_snapshot", - "role": "root_variant", - "wording_family": "canonical", - "semantic_goal": "получить номенклатурный состав складского остатка на дату" - }, - { - "question_id": "Q28", - "text": "что лежит у нас на складе в марте 2019", - "layer": "root_snapshot", - "node_id": "N01_stock_snapshot", - "role": "root_variant", - "wording_family": "colloquial", - "semantic_goal": "проверить разговорный root snapshot с предлогом `в` и месячной словоформой без развала в годовой диапазон" - }, - { - "question_id": "Q06", - "text": "От какого поставщика куплен товар ...", - "layer": "selected_item_provenance", - "node_id": "N03_selected_item_supplier", - "role": "critical_child", - "wording_family": "canonical", - "semantic_goal": "получить прямой ответ о поставщике выбранного товара" - }, - { - "question_id": "Q07", - "text": "У какого поставщика были куплены товары, которые сейчас лежат на складе", - "layer": "supplier_overlap", - "node_id": "N06_supplier_overlap_now", - "role": "critical_child", - "wording_family": "canonical", - "semantic_goal": "понять supplier coverage по текущему складскому срезу" - }, - { - "question_id": "Q08", - "text": "По какому поставщику проходит текущий товарный остаток", - "layer": "supplier_overlap", - "node_id": "N06_supplier_overlap_now", - "role": "supporting_child", - "wording_family": "canonical", - "semantic_goal": "атрибутировать текущий остаток по поставщику без потери item/slice контекста" - }, - { - "question_id": "Q09", - "text": "Когда был куплен товар ...", - "layer": "selected_item_provenance", - "node_id": "N04_selected_item_purchase_date", - "role": "critical_child", - "wording_family": "canonical", - "semantic_goal": "получить дату первой/наблюдаемой закупки выбранного товара" - }, - { - "question_id": "Q26", - "text": "когда примерно мы купили?", - "layer": "selected_item_provenance", - "node_id": "N04_selected_item_purchase_date", - "role": "followup_checkpoint", - "wording_family": "short_action_followup", - "semantic_goal": "получить компактный direct answer о дате закупки через reuse already resolved answer object, а не повторять полный provenance packet" - }, - { - "question_id": "Q10", - "text": "По каким документам был куплен товар ...", - "layer": "selected_item_provenance", - "node_id": "N05_selected_item_purchase_documents", - "role": "critical_child", - "wording_family": "canonical", - "semantic_goal": "получить закупочные документы для выбранного товара" - }, - { - "question_id": "Q11", - "text": "Какие товары от поставщика ... сейчас еще лежат на складе", - "layer": "supplier_overlap", - "node_id": "N07_supplier_items_on_stock", - "role": "supporting_child", - "wording_family": "canonical", - "semantic_goal": "получить item-level пересечение supplier -> current stock" - }, - { - "question_id": "Q12", - "text": "Какие товары по состоянию на дату ... были куплены у поставщика ...", - "layer": "supplier_overlap", - "node_id": "N08_supplier_items_on_date", - "role": "supporting_child", - "wording_family": "canonical", - "semantic_goal": "получить датированное item-level пересечение supplier -> stock slice" - }, - { - "question_id": "Q13", - "text": "Какие остатки по товарам на эту дату относятся к старым закупкам", - "layer": "aging", - "node_id": "N09_old_purchase_aging", - "role": "critical_child", - "wording_family": "followup_date_carryover", - "semantic_goal": "показать item-level старые закупки в рамках уже выбранной даты, а не на текущий момент" - }, - { - "question_id": "Q14", - "text": "Какие товары сейчас висят в остатке без понятной привязки к поставщику", - "layer": "aging", - "node_id": "N10_unresolved_supplier_link", - "role": "supporting_child", - "wording_family": "canonical", - "semantic_goal": "найти остатки без надежной supplier attribution" - }, - { - "question_id": "Q15", - "text": "Есть ли остатки товара, которые закупались очень давно", - "layer": "aging", - "node_id": "N09_old_purchase_aging", - "role": "supporting_child", - "wording_family": "canonical", - "semantic_goal": "показать very-old stock в item-level виде" - }, - { - "question_id": "Q16", - "text": "Кому был продан товар ...", - "layer": "sale_trace", - "node_id": "N11_selected_item_buyer", - "role": "critical_child", - "wording_family": "canonical", - "semantic_goal": "получить покупателя выбранного товара" - }, - { - "question_id": "Q17", - "text": "Через какие документы прошел путь товара: закупка -> склад -> продажа", - "layer": "sale_trace", - "node_id": "N12_purchase_to_sale_chain", - "role": "critical_child", - "wording_family": "canonical", - "semantic_goal": "получить полную документальную цепочку purchase -> stock -> sale" - }, - { - "question_id": "Q18", - "text": "Какие товары были куплены у поставщика ... и позже проданы покупателю ...", - "layer": "sale_trace", - "node_id": "N13_supplier_to_buyer_overlap", - "role": "supporting_child", - "wording_family": "canonical", - "semantic_goal": "проверить supplier -> item -> buyer overlap" - }, - { - "question_id": "Q19", - "text": "По выбранному объекту \"...\": кто это поставил нам", - "layer": "selected_item_provenance", - "node_id": "N03_selected_item_supplier", - "role": "critical_child", - "wording_family": "ui_selected_object_colloquial", - "semantic_goal": "проверить, что selected-object follow-up и разговорная supplier-фраза идут в exact provenance contour" - }, - { - "question_id": "Q20", - "text": "По выбранному объекту \"...\": по каким документам это купили", - "layer": "selected_item_provenance", - "node_id": "N05_selected_item_purchase_documents", - "role": "critical_child", - "wording_family": "ui_selected_object_colloquial", - "semantic_goal": "проверить selected-object follow-up в закупочные документы без ручного переписывания item" - }, - { - "question_id": "Q21", - "text": "покажи документы по этой позиции", - "layer": "selected_item_provenance", - "node_id": "N05_selected_item_purchase_documents", - "role": "critical_child", - "wording_family": "pronoun_followup", - "semantic_goal": "проверить короткий местоименный follow-up по активному товару без съезда в counterparty drilldown" - }, - { - "question_id": "Q22", - "text": "По выбранному объекту \"...\": от какого поставщика куплен товар", - "layer": "selected_item_provenance", - "node_id": "N03_selected_item_supplier", - "role": "critical_child", - "wording_family": "ui_selected_object", - "semantic_goal": "проверить selected-object follow-up в exact provenance contour без разговорной supplier-лексики" - }, - { - "question_id": "Q23", - "text": "кто это поставил нам", - "layer": "selected_item_provenance", - "node_id": "N03_selected_item_supplier", - "role": "critical_child", - "wording_family": "pronoun_followup", - "semantic_goal": "проверить короткий supplier follow-up по уже активному selected object без повторного item anchor" - }, - { - "question_id": "Q27", - "text": "По выбранному объекту \"...\": у кого купили", - "layer": "selected_item_provenance", - "node_id": "N03_selected_item_supplier", - "role": "critical_child", - "wording_family": "ui_selected_object_colloquial", - "semantic_goal": "проверить разговорный selected-object follow-up про поставщика без слова `поставщик`" - }, - { - "question_id": "Q31", - "text": "По выбранному объекту \"...\": у кого куплено", - "layer": "selected_item_provenance", - "node_id": "N03_selected_item_supplier", - "role": "critical_child", - "wording_family": "ui_selected_object_colloquial", - "semantic_goal": "проверить selected-object follow-up с краткой страдательной формой `у кого куплено`, который должен оставаться в supplier provenance и наследовать историческую дату root-среза" - }, - { - "question_id": "Q29", - "text": "По выбранному объекту \"...\": где мы купили это", - "layer": "selected_item_provenance", - "node_id": "N03_selected_item_supplier", - "role": "critical_child", - "wording_family": "ui_selected_object_colloquial", - "semantic_goal": "проверить разговорный selected-object follow-up с локационной формулировкой, который по смыслу должен вернуться к supplier provenance, а не в документы" - }, - { - "question_id": "Q30", - "text": "По выбранному объекту \"...\": где куплено!!", - "layer": "selected_item_provenance", - "node_id": "N03_selected_item_supplier", - "role": "critical_child", - "wording_family": "ui_selected_object_colloquial", - "semantic_goal": "проверить короткий selected-object follow-up с emphatic punctuation и усеченной формулировкой `где куплено`" - }, - { - "question_id": "Q24", - "text": "По выбранному объекту \"...\": кому был продан товар", - "layer": "sale_trace", - "node_id": "N11_selected_item_buyer", - "role": "critical_child", - "wording_family": "ui_selected_object", - "semantic_goal": "проверить selected-object follow-up в exact buyer trace contour" - }, - { - "question_id": "Q25", - "text": "По выбранному объекту \"...\": через какие документы прошел путь товара: закупка -> склад -> продажа", - "layer": "sale_trace", - "node_id": "N12_purchase_to_sale_chain", - "role": "critical_child", - "wording_family": "ui_selected_object", - "semantic_goal": "проверить selected-object follow-up в exact purchase-to-sale chain contour" - } - ] - }, - "wording_families": [ - { - "family_id": "canonical", - "description": "нормальная каноническая формулировка доменного вопроса" - }, - { - "family_id": "colloquial", - "description": "разговорная или короткая пользовательская формулировка" - }, - { - "family_id": "ui_selected_object", - "description": "follow-up, собранный через выбор объекта из ответа" - }, - { - "family_id": "ui_selected_object_colloquial", - "description": "выбранный объект + разговорная формулировка drilldown-вопроса" - }, - { - "family_id": "followup_date_carryover", - "description": "follow-up с фразой `на эту дату` или `на ту дату`, где дата обязана тянуться из предыдущего шага" - }, - { - "family_id": "pronoun_followup", - "description": "короткий follow-up по активному объекту через местоимение или указатель типа `по ней`, `по этой позиции`, `ее`" - } + "detectors_under_test": [ + "counterparty_value_flow_required_surface", + "counterparty_value_flow_profit_accounts_forbidden", + "bank_counterparty_boundary_required", + "runtime_tokens_in_user_answer", + "capability_ids_in_user_answer", + "limited_answer_without_next_action" ], - "scenario_tree": { - "root_nodes": [ - { - "node_id": "N01_stock_snapshot", - "title": "Складской snapshot на дату", - "covers_question_ids": [ - "Q01", - "Q02", - "Q05", - "Q28" - ], - "expected_intents": [ - "inventory_on_hand_as_of_date" - ], - "expected_answer_shape": "item_list_with_quantity_cost_warehouse_organization", - "required_wording_families": [ - "canonical", - "colloquial" - ], - "children": [ - "N03_selected_item_supplier", - "N06_supplier_overlap_now", - "N09_old_purchase_aging" - ] - }, - { - "node_id": "N02_account_41_snapshot", - "title": "Состав остатка по 41.01 на дату", - "covers_question_ids": [ - "Q03", - "Q04" - ], - "expected_intents": [ - "inventory_on_hand_as_of_date" - ], - "expected_answer_shape": "item_list_with_account_41_scope", - "required_wording_families": [ - "canonical", - "colloquial" - ], - "children": [ - "N03_selected_item_supplier", - "N11_selected_item_buyer" - ] - } - ], - "critical_nodes": [ - { - "node_id": "N03_selected_item_supplier", - "title": "Поставщик выбранного товара", - "covers_question_ids": [ - "Q06", - "Q19", - "Q22", - "Q23", - "Q27", - "Q29", - "Q30" - ], - "expected_intents": [ - "inventory_purchase_provenance_for_item" - ], - "expected_answer_shape": "direct_supplier_answer_first_then_evidence", - "required_wording_families": [ - "canonical", - "colloquial", - "ui_selected_object", - "ui_selected_object_colloquial", - "pronoun_followup" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "warehouse_scope", - "organization_scope", - "reusable_bundle" - ], - "children": [ - "N04_selected_item_purchase_date", - "N05_selected_item_purchase_documents", - "N09_old_purchase_aging" - ] - }, - { - "node_id": "N04_selected_item_purchase_date", - "title": "Дата закупки выбранного товара", - "covers_question_ids": [ - "Q09", - "Q26" - ], - "expected_intents": [ - "inventory_purchase_provenance_for_item" - ], - "expected_answer_shape": "direct_date_answer_first_then_evidence", - "required_wording_families": [ - "canonical", - "ui_selected_object", - "short_action_followup" - ], - "required_carryover_invariants": [ - "selected_object", - "date_scope", - "reusable_bundle", - "followup_action_resolution" - ] - }, - { - "node_id": "N05_selected_item_purchase_documents", - "title": "Закупочные документы выбранного товара", - "covers_question_ids": [ - "Q10", - "Q20", - "Q21" - ], - "expected_intents": [ - "inventory_purchase_documents_for_item" - ], - "expected_answer_shape": "document_list_for_selected_item", - "required_wording_families": [ - "canonical", - "ui_selected_object", - "ui_selected_object_colloquial", - "pronoun_followup" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "warehouse_scope", - "reusable_bundle", - "followup_action_resolution" - ] - }, - { - "node_id": "N09_old_purchase_aging", - "title": "Старые закупки по остаткам на выбранную дату", - "covers_question_ids": [ - "Q13", - "Q15" - ], - "expected_intents": [ - "inventory_aging_by_purchase_date" - ], - "expected_answer_shape": "item_level_oldest_first_stock_aging_list", - "required_wording_families": [ - "canonical", - "followup_date_carryover" - ], - "required_carryover_invariants": [ - "date_scope", - "warehouse_scope", - "organization_scope" - ], - "ordering_rule": "oldest_first" - }, - { - "node_id": "N11_selected_item_buyer", - "title": "Покупатель выбранного товара", - "covers_question_ids": [ - "Q16", - "Q24" - ], - "expected_intents": [ - "inventory_sale_trace_for_item" - ], - "expected_answer_shape": "direct_buyer_answer_first_then_evidence", - "required_wording_families": [ - "canonical", - "ui_selected_object" - ], - "required_carryover_invariants": [ - "selected_object", - "date_scope" - ] - }, - { - "node_id": "N12_purchase_to_sale_chain", - "title": "Документальная цепочка purchase -> stock -> sale", - "covers_question_ids": [ - "Q17", - "Q25" - ], - "expected_intents": [ - "inventory_purchase_to_sale_chain" - ], - "expected_answer_shape": "ordered_chain_with_purchase_stock_sale_documents", - "required_wording_families": [ - "canonical", - "ui_selected_object" - ], - "required_carryover_invariants": [ - "selected_object", - "date_scope" - ] - } - ], - "supporting_nodes": [ - { - "node_id": "N06_supplier_overlap_now", - "title": "Supplier overlap по текущему stock slice", - "covers_question_ids": [ - "Q07", - "Q08" - ], - "expected_intents": [ - "inventory_supplier_stock_overlap_as_of_date" - ], - "expected_answer_shape": "supplier_to_stock_slice_summary" - }, - { - "node_id": "N07_supplier_items_on_stock", - "title": "Какие товары поставщика лежат на складе сейчас", - "covers_question_ids": [ - "Q11" - ], - "expected_intents": [ - "inventory_supplier_stock_overlap_as_of_date" - ], - "expected_answer_shape": "item_list_filtered_by_supplier" - }, - { - "node_id": "N08_supplier_items_on_date", - "title": "Какие товары поставщика были в остатке на дату", - "covers_question_ids": [ - "Q12" - ], - "expected_intents": [ - "inventory_supplier_stock_overlap_as_of_date" - ], - "expected_answer_shape": "dated_item_list_filtered_by_supplier" - }, - { - "node_id": "N10_unresolved_supplier_link", - "title": "Остатки без понятной supplier attribution", - "covers_question_ids": [ - "Q14" - ], - "expected_intents": [ - "inventory_supplier_stock_overlap_as_of_date" - ], - "expected_answer_shape": "item_list_with_unresolved_supplier_linkage" - }, - { - "node_id": "N13_supplier_to_buyer_overlap", - "title": "Supplier -> item -> buyer overlap", - "covers_question_ids": [ - "Q18" - ], - "expected_intents": [ - "inventory_purchase_to_sale_chain" - ], - "expected_answer_shape": "confirmed_or_honestly_limited_supplier_item_buyer_overlap" - } - ], - "critical_edges": [ - { - "edge_id": "E01_snapshot_to_selected_item_supplier", - "from_node": "N01_stock_snapshot", - "to_node": "N03_selected_item_supplier", - "transition_type": "selected_object_drilldown", - "primary_user_path": true, - "required_carryover_invariants": [ - "selected_object", - "date_scope", - "warehouse_scope", - "organization_scope" - ], - "failure_means": "домен не принят, даже если root snapshot уже exact" - }, - { - "edge_id": "E02_selected_item_supplier_to_purchase_documents", - "from_node": "N03_selected_item_supplier", - "to_node": "N05_selected_item_purchase_documents", - "transition_type": "selected_object_deeper_trace", - "primary_user_path": true, - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "reusable_bundle", - "followup_action_resolution" - ], - "failure_means": "сломано углубление из поставщика в документы закупки" - }, - { - "edge_id": "E03_snapshot_or_provenance_to_aging", - "from_node": "N01_stock_snapshot", - "to_node": "N09_old_purchase_aging", - "transition_type": "date_sensitive_followup", - "primary_user_path": true, - "required_carryover_invariants": [ - "date_scope", - "warehouse_scope", - "organization_scope" - ], - "ordering_rule": "oldest_first", - "failure_means": "сломано понимание `на эту дату` или неверная answer shape по старым закупкам" - }, - { - "edge_id": "E04_snapshot_to_sale_trace", - "from_node": "N02_account_41_snapshot", - "to_node": "N11_selected_item_buyer", - "transition_type": "historical_selected_object_drilldown", - "primary_user_path": true, - "required_carryover_invariants": [ - "selected_object", - "date_scope" - ], - "failure_means": "sale-trace ветка незакрыта" - } - ], - "primary_user_paths": [ - { - "path_id": "P01_snapshot_to_supplier", - "nodes": [ - "N01_stock_snapshot", - "N03_selected_item_supplier" - ], - "description": "Самый типичный живой путь: получить список остатков, выбрать позицию, спросить кто поставил." - }, - { - "path_id": "P02_snapshot_to_supplier_to_documents", - "nodes": [ - "N01_stock_snapshot", - "N03_selected_item_supplier", - "N05_selected_item_purchase_documents" - ], - "description": "После поставщика пользователь углубляется в закупочные документы." - }, - { - "path_id": "P03_snapshot_to_aging_on_same_date", - "nodes": [ - "N01_stock_snapshot", - "N09_old_purchase_aging" - ], - "description": "Пользователь спрашивает про старые закупки на ту же дату, что и исходный snapshot." - }, - { - "path_id": "P04_account_41_to_sale_trace", - "nodes": [ - "N02_account_41_snapshot", - "N11_selected_item_buyer", - "N12_purchase_to_sale_chain" - ], - "description": "Пользователь берет исторический остаток по 41 счету, выбирает позицию и углубляется в buyer/sale trace." - } - ] - }, - "orchestration_pack": { - "pack_id": "inventory_stock_active_contract_pool", - "title": "Warehouse stock active domain pack", - "description": "Scenario pack generated from the single active warehouse domain contract.", - "analysis_context": { - "as_of_date": "2021-09-30", - "source": "active_domain_contract_pack" - }, - "bindings": { - "target_date_historical": "2019-03-31", - "target_date_current": "2021-09-30", - "observed_warehouse": "Основной склад", - "observed_organization": "ООО \\Альтернатива Плюс\\", - "focus_item_current": "Диван трехместный", - "focus_item_historical": "Столешница 600*3050*26 дуб ниагара", - "focus_item_small_residue": "Четки Пост (84*117)", - "observed_supplier_candidate": "Торговый дом \\Союз", - "observed_supplier_candidate_alt": "Гамма-мебель, ООО", - "observed_customer_candidate": "Департамент капитального ремонта города Москвы" - }, - "scenarios": [ - { - "scenario_id": "inventory_snapshot_roots", - "title": "Root stock snapshots", - "question_ids": [ - "Q01", - "Q02", - "Q28", - "Q03", - "Q04", - "Q05" - ], - "node_ids": [ - "N01_stock_snapshot", - "N02_account_41_snapshot" - ], - "acceptance_canon": { - "root_step_id": "step_01_stock_now", - "primary_user_path": [ - "step_01_stock_now", - "step_02_stock_on_historical_date", - "step_05_nomenclature_on_historical_date" - ], - "required_paraphrase_families": [ - "canonical", - "colloquial" - ], - "required_carryover_invariants": [ - "date_scope", - "warehouse_scope", - "organization_scope", - "answer_shape" - ] - }, - "steps": [ - { - "step_id": "step_01_stock_now", - "question_id": "Q01", - "node_id": "N01_stock_snapshot", - "node_role": "root", - "paraphrase_family": "canonical", - "title": "Current stock root", - "question": "Какие товары сейчас лежат на складе", - "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" - }, - "invariant_severity": { - "wrong_as_of_date": "P0", - "wrong_period_from": "P0", - "wrong_period_to": "P0" - } - }, - { - "step_id": "step_02_stock_on_historical_date", - "question_id": "Q02", - "node_id": "N01_stock_snapshot", - "node_role": "root", - "paraphrase_family": "colloquial", - "title": "Historical stock slice", - "question": "покажи остатки на складе на март 2019", - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "confirmed_inventory_on_hand_as_of_date", - "expected_result_mode": "confirmed_balance", - "required_filters": { - "as_of_date": "2019-03-31", - "period_from": "2019-03-01", - "period_to": "2019-03-31" - }, - "invariant_severity": { - "wrong_as_of_date": "P0", - "wrong_period_from": "P0", - "wrong_period_to": "P0" - } - }, - { - "step_id": "step_02b_stock_on_named_month_prepositional", - "question_id": "Q28", - "node_id": "N01_stock_snapshot", - "node_role": "root_variant", - "paraphrase_family": "colloquial", - "title": "Historical stock slice with prepositional month wording", - "question": "что лежит у нас на складе в марте 2019", - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "confirmed_inventory_on_hand_as_of_date", - "expected_result_mode": "confirmed_balance", - "required_filters": { - "as_of_date": "2019-03-31", - "period_from": "2019-03-01", - "period_to": "2019-03-31" - }, - "invariant_severity": { - "wrong_as_of_date": "P0", - "wrong_period_from": "P0", - "wrong_period_to": "P0" - } - }, - { - "step_id": "step_03_account_41_now", - "question_id": "Q03", - "analysis_context": { - "as_of_date": "2021-09-30", - "source": "binding_target_date_current" - }, - "required_filters": { - "as_of_date": "2021-09-30", - "account": "41" - }, - "invariant_severity": { - "wrong_as_of_date": "P0", - "wrong_period_from": "P0", - "wrong_period_to": "P0" - }, - "node_id": "N02_account_41_snapshot", - "node_role": "root_variant", - "paraphrase_family": "canonical", - "title": "Account 41 current composition", - "question": "Из каких товаров состоит остаток по 41 счету", - "expected_capability": "confirmed_inventory_on_hand_as_of_date", - "expected_result_mode": "confirmed_balance" - }, - { - "step_id": "step_04_account_41_on_historical_date", - "question_id": "Q04", - "node_id": "N02_account_41_snapshot", - "node_role": "root_variant", - "paraphrase_family": "colloquial", - "title": "Account 41 on historical date", - "question": "Какие товары числятся на 41 счете на дату {{bindings.target_date_historical}}", - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "confirmed_inventory_on_hand_as_of_date", - "expected_result_mode": "confirmed_balance" - }, - { - "step_id": "step_05_nomenclature_on_historical_date", - "question_id": "Q05", - "node_id": "N01_stock_snapshot", - "node_role": "root_variant", - "paraphrase_family": "canonical", - "title": "Nomenclature on historical date", - "question": "Какие конкретно номенклатуры формируют остаток по складу {{bindings.observed_warehouse}} на дату {{bindings.target_date_historical}}", - "depends_on": [ - "step_02_stock_on_historical_date" - ], - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "confirmed_inventory_on_hand_as_of_date", - "expected_result_mode": "confirmed_balance" - } - ] - }, - { - "scenario_id": "inventory_selected_item_provenance", - "title": "Selected-item supplier provenance", - "question_ids": [ - "Q02", - "Q06", - "Q09", - "Q26", - "Q10", - "Q19", - "Q27", - "Q29", - "Q30", - "Q20", - "Q21", - "Q22", - "Q23" - ], - "node_ids": [ - "N01_stock_snapshot", - "N03_selected_item_supplier", - "N04_selected_item_purchase_date", - "N05_selected_item_purchase_documents" - ], - "acceptance_canon": { - "root_step_id": "step_01_snapshot_historical", - "primary_user_path": [ - "step_01_snapshot_historical", - "step_02_selected_item_supplier_ui", - "step_08_selected_item_documents_pronoun" - ], - "required_paraphrase_families": [ - "canonical", - "colloquial", - "ui_selected_object", - "ui_selected_object_colloquial", - "pronoun_followup" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "warehouse_scope", - "organization_scope", - "answer_shape", - "reusable_bundle", - "followup_action_resolution" - ] - }, - "steps": [ - { - "step_id": "step_01_snapshot_historical", - "question_id": "Q02", - "node_id": "N01_stock_snapshot", - "node_role": "root", - "paraphrase_family": "colloquial", - "title": "Historical stock anchor", - "question": "покажи остатки на складе на март 2019", - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "confirmed_inventory_on_hand_as_of_date", - "expected_result_mode": "confirmed_balance" - }, - { - "step_id": "step_02_selected_item_supplier_ui", - "question_id": "Q22", - "node_id": "N03_selected_item_supplier", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object", - "title": "Selected item supplier UI", - "question": "По выбранному объекту \"{{bindings.focus_item_historical}}\": от какого поставщика куплен товар", - "depends_on": [ - "step_01_snapshot_historical" - ], - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "inventory_purchase_provenance_for_item", - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "warehouse_scope", - "organization_scope" - ] - }, - { - "step_id": "step_03_selected_item_supplier_colloquial", - "question_id": "Q19", - "node_id": "N03_selected_item_supplier", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object_colloquial", - "title": "Selected item supplier colloquial", - "question": "По выбранному объекту \"{{bindings.focus_item_historical}}\": кто это поставил нам", - "depends_on": [ - "step_01_snapshot_historical" - ], - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "inventory_purchase_provenance_for_item", - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "warehouse_scope", - "organization_scope" - ] - }, - { - "step_id": "step_03b_selected_item_supplier_u_kogo_kupili", - "question_id": "Q27", - "node_id": "N03_selected_item_supplier", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object_colloquial", - "title": "Selected item supplier colloquial without supplier noun", - "question": "По выбранному объекту \"{{bindings.focus_item_historical}}\": у кого купили", - "depends_on": [ - "step_01_snapshot_historical" - ], - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "inventory_purchase_provenance_for_item", - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1" - ], - "required_state_objects": [ - "focus_object" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "warehouse_scope", - "organization_scope" - ] - }, - { - "step_id": "step_03bb_selected_item_supplier_u_kogo_kupleno", - "question_id": "Q31", - "node_id": "N03_selected_item_supplier", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object_colloquial", - "title": "Selected item supplier passive colloquial wording", - "question": "По выбранному объекту \"{{bindings.focus_item_historical}}\": у кого куплено", - "depends_on": [ - "step_01_snapshot_historical" - ], - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "inventory_purchase_provenance_for_item", - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1" - ], - "required_state_objects": [ - "focus_object" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "warehouse_scope", - "organization_scope" - ], - "required_filters": { - "as_of_date": "2019-03-31" - }, - "invariant_severity": { - "wrong_as_of_date": "P0", - "wrong_period_from": "P0", - "wrong_period_to": "P0", - "forbidden_route_selected": "P0", - "focus_object_missing": "P0" - } - }, - { - "step_id": "step_03c_selected_item_supplier_gde_my_kupili", - "question_id": "Q29", - "node_id": "N03_selected_item_supplier", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object_colloquial", - "title": "Selected item supplier colloquial with where-bought wording", - "question": "По выбранному объекту \"{{bindings.focus_item_historical}}\": где мы купили это", - "depends_on": [ - "step_01_snapshot_historical" - ], - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "inventory_purchase_provenance_for_item", - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1" - ], - "required_state_objects": [ - "focus_object" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "warehouse_scope", - "organization_scope" - ] - }, - { - "step_id": "step_03d_selected_item_supplier_gde_kupleno", - "question_id": "Q30", - "node_id": "N03_selected_item_supplier", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object_colloquial", - "title": "Selected item supplier terse where-bought wording", - "question": "По выбранному объекту \"{{bindings.focus_item_historical}}\": где куплено!!", - "depends_on": [ - "step_01_snapshot_historical" - ], - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "inventory_purchase_provenance_for_item", - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1" - ], - "required_state_objects": [ - "focus_object" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "warehouse_scope", - "organization_scope" - ] - }, - { - "step_id": "step_04_selected_item_supplier_pronoun", - "question_id": "Q23", - "node_id": "N03_selected_item_supplier", - "node_role": "critical_child", - "paraphrase_family": "pronoun_followup", - "title": "Selected item supplier pronoun follow-up", - "question": "кто это поставил нам", - "depends_on": [ - "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", - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1" - ], - "required_state_objects": [ - "focus_object" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "reusable_bundle" - ] - }, - { - "step_id": "step_05_selected_item_supplier_canonical", - "question_id": "Q06", - "node_id": "N03_selected_item_supplier", - "node_role": "critical_child", - "paraphrase_family": "canonical", - "title": "Selected item supplier canonical", - "question": "От какого поставщика куплен товар {{bindings.focus_item_historical}}", - "depends_on": [ - "step_01_snapshot_historical" - ], - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "inventory_purchase_provenance_for_item", - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1" - ], - "required_state_objects": [ - "focus_object" - ] - }, - { - "step_id": "step_06_selected_item_purchase_date", - "question_id": "Q09", - "node_id": "N04_selected_item_purchase_date", - "node_role": "critical_child", - "paraphrase_family": "canonical", - "title": "Selected item purchase date", - "question": "Когда был куплен товар {{bindings.focus_item_historical}}", - "depends_on": [ - "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" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1" - ] - }, - { - "step_id": "step_06b_selected_item_purchase_date_short", - "question_id": "Q26", - "node_id": "N04_selected_item_purchase_date", - "node_role": "critical_child", - "paraphrase_family": "short_action_followup", - "title": "Selected item purchase date short follow-up", - "question": "когда", - "depends_on": [ - "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", - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1" - ], - "required_state_objects": [ - "focus_object" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "reusable_bundle", - "followup_action_resolution" - ] - }, - { - "step_id": "step_07_selected_item_documents_ui", - "question_id": "Q20", - "node_id": "N05_selected_item_purchase_documents", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object_colloquial", - "title": "Selected item purchase documents UI", - "question": "По выбранному объекту \"{{bindings.focus_item_historical}}\": по каким документам это купили", - "depends_on": [ - "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_documents_for_item", - "required_state_objects": [ - "focus_object" - ], - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date", - "inventory_purchase_provenance_for_item" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1", - "address_inventory_purchase_provenance_for_item_v1" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "reusable_bundle", - "followup_action_resolution" - ] - }, - { - "step_id": "step_08_selected_item_documents_pronoun", - "question_id": "Q21", - "node_id": "N05_selected_item_purchase_documents", - "node_role": "critical_child", - "paraphrase_family": "pronoun_followup", - "title": "Selected item purchase documents pronoun follow-up", - "question": "покажи документы по этой позиции", - "depends_on": [ - "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_documents_for_item", - "required_state_objects": [ - "focus_object" - ], - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date", - "inventory_purchase_provenance_for_item" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1", - "address_inventory_purchase_provenance_for_item_v1" - ], - "required_carryover_invariants": [ - "selected_object", - "focus_object", - "date_scope", - "reusable_bundle", - "followup_action_resolution" - ] - }, - { - "step_id": "step_09_selected_item_documents_canonical", - "question_id": "Q10", - "node_id": "N05_selected_item_purchase_documents", - "node_role": "critical_child", - "paraphrase_family": "canonical", - "title": "Selected item purchase documents canonical", - "question": "По каким документам был куплен товар {{bindings.focus_item_historical}}", - "depends_on": [ - "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_documents_for_item", - "required_state_objects": [ - "focus_object" - ], - "forbidden_capabilities": [ - "confirmed_inventory_on_hand_as_of_date", - "inventory_purchase_provenance_for_item" - ], - "forbidden_recipes": [ - "address_inventory_on_hand_as_of_date_v1", - "address_inventory_purchase_provenance_for_item_v1" - ] - } - ] - }, - { - "scenario_id": "inventory_supplier_overlap", - "title": "Supplier overlap and supplier-scoped stock", - "question_ids": [ - "Q01", - "Q07", - "Q08", - "Q11", - "Q12" - ], - "node_ids": [ - "N01_stock_snapshot", - "N06_supplier_overlap_now", - "N07_supplier_items_on_stock", - "N08_supplier_items_on_date" - ], - "acceptance_canon": { - "root_step_id": "step_01_snapshot_current", - "primary_user_path": [ - "step_01_snapshot_current", - "step_02_supplier_overlap_now", - "step_04_supplier_items_on_stock" - ], - "required_paraphrase_families": [ - "canonical", - "colloquial" - ], - "required_carryover_invariants": [ - "date_scope", - "warehouse_scope", - "organization_scope", - "answer_shape" - ] - }, - "steps": [ - { - "step_id": "step_01_snapshot_current", - "question_id": "Q01", - "node_id": "N01_stock_snapshot", - "node_role": "root", - "paraphrase_family": "colloquial", - "title": "Current stock anchor", - "question": "какие остатки на складе на сентябрь 2021", - "analysis_context": { - "as_of_date": "2021-09-30", - "source": "binding_target_date_current" - }, - "expected_capability": "confirmed_inventory_on_hand_as_of_date", - "expected_result_mode": "confirmed_balance" - }, - { - "step_id": "step_02_supplier_overlap_now", - "question_id": "Q07", - "node_id": "N06_supplier_overlap_now", - "node_role": "critical_child", - "paraphrase_family": "canonical", - "title": "Suppliers behind current stock", - "question": "У какого поставщика были куплены товары, которые сейчас лежат на складе {{bindings.observed_warehouse}}", - "depends_on": [ - "step_01_snapshot_current" - ] - }, - { - "step_id": "step_03_supplier_residue_now", - "question_id": "Q08", - "node_id": "N06_supplier_overlap_now", - "node_role": "supporting_child", - "paraphrase_family": "canonical", - "title": "Supplier attribution of current residue", - "question": "По какому поставщику проходит текущий товарный остаток на складе {{bindings.observed_warehouse}}", - "depends_on": [ - "step_01_snapshot_current" - ] - }, - { - "step_id": "step_04_supplier_items_on_stock", - "question_id": "Q11", - "node_id": "N07_supplier_items_on_stock", - "node_role": "supporting_child", - "paraphrase_family": "canonical", - "title": "Current items for observed supplier", - "question": "Какие товары от поставщика {{bindings.observed_supplier_candidate}} сейчас еще лежат на складе {{bindings.observed_warehouse}}", - "depends_on": [ - "step_01_snapshot_current" - ] - }, - { - "step_id": "step_05_supplier_items_on_date", - "question_id": "Q12", - "required_filters": { - "as_of_date": "2019-03-31", - "period_from": "2019-03-01", - "period_to": "2019-03-31" - }, - "required_carryover_invariants": [ - "date_scope" - ], - "invariant_severity": { - "wrong_as_of_date": "P0", - "wrong_period_from": "P0", - "wrong_period_to": "P0" - }, - "node_id": "N08_supplier_items_on_date", - "node_role": "supporting_child", - "paraphrase_family": "canonical", - "title": "Supplier items on historical date", - "question": "Какие товары по состоянию на дату {{bindings.target_date_historical}} были куплены у поставщика {{bindings.observed_supplier_candidate}}", - "analysis_context": { - "as_of_date": "2019-03-31", - "source": "binding_target_date_historical" - } - } - ] - }, - { - "scenario_id": "inventory_aging_and_unresolved", - "title": "Stock aging and unresolved supplier linkage", - "question_ids": [ - "Q13", - "Q14", - "Q15", - "Q19" - ], - "node_ids": [ - "N01_stock_snapshot", - "N03_selected_item_supplier", - "N09_old_purchase_aging", - "N10_unresolved_supplier_link" - ], - "acceptance_canon": { - "root_step_id": "step_01_snapshot_current", - "primary_user_path": [ - "step_01_snapshot_current", - "step_02_selected_item_supplier_small", - "step_03_old_purchase_aging_followup" - ], - "required_paraphrase_families": [ - "canonical", - "followup_date_carryover", - "ui_selected_object_colloquial" - ], - "required_carryover_invariants": [ - "selected_object", - "date_scope", - "warehouse_scope", - "organization_scope", - "answer_shape", - "ordering_semantics" - ] - }, - "steps": [ - { - "step_id": "step_01_snapshot_current", - "question_id": "Q01", - "node_id": "N01_stock_snapshot", - "node_role": "root", - "paraphrase_family": "colloquial", - "title": "Current stock anchor", - "question": "какие остатки на складе на сентябрь 2021", - "analysis_context": { - "as_of_date": "2021-09-30", - "source": "binding_target_date_current" - }, - "expected_capability": "confirmed_inventory_on_hand_as_of_date", - "expected_result_mode": "confirmed_balance" - }, - { - "step_id": "step_02_selected_item_supplier_small", - "question_id": "Q19", - "node_id": "N03_selected_item_supplier", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object_colloquial", - "title": "Supplier for small residual item", - "question": "По выбранному объекту \"{{bindings.focus_item_small_residue}}\": кто это поставил нам", - "depends_on": [ - "step_01_snapshot_current" - ] - }, - { - "step_id": "step_03_old_purchase_aging_followup", - "question_id": "Q13", - "node_id": "N09_old_purchase_aging", - "node_role": "critical_child", - "paraphrase_family": "followup_date_carryover", - "title": "Old purchase aging on the same date", - "question": "Какие остатки по товарам на эту дату относятся к старым закупкам", - "depends_on": [ - "step_01_snapshot_current", - "step_02_selected_item_supplier_small" - ], - "required_carryover_invariants": [ - "date_scope", - "warehouse_scope", - "organization_scope" - ], - "ordering_rule": "oldest_first" - }, - { - "step_id": "step_04_very_old_stock", - "question_id": "Q15", - "node_id": "N09_old_purchase_aging", - "node_role": "supporting_child", - "paraphrase_family": "canonical", - "title": "Very old stock", - "question": "Есть ли остатки товара, которые закупались очень давно", - "depends_on": [ - "step_01_snapshot_current", - "step_03_old_purchase_aging_followup" - ], - "ordering_rule": "oldest_first" - }, - { - "step_id": "step_05_unresolved_supplier_link", - "question_id": "Q14", - "analysis_context": { - "as_of_date": "2021-09-30", - "source": "binding_target_date_current" - }, - "required_filters": { - "as_of_date": "2021-09-30", - "period_from": "2021-09-01", - "period_to": "2021-09-30" - }, - "required_carryover_invariants": [ - "date_scope" - ], - "invariant_severity": { - "wrong_as_of_date": "P0", - "wrong_period_from": "P0", - "wrong_period_to": "P0" - }, - "node_id": "N10_unresolved_supplier_link", - "node_role": "supporting_child", - "paraphrase_family": "canonical", - "title": "Unresolved supplier linkage", - "question": "Какие товары сейчас висят в остатке без понятной привязки к поставщику", - "depends_on": [ - "step_01_snapshot_current" - ] - } - ] - }, - { - "scenario_id": "inventory_sale_trace", - "title": "Sale trace and purchase-to-sale chain", - "question_ids": [ - "Q04", - "Q16", - "Q17", - "Q18", - "Q24", - "Q25" - ], - "node_ids": [ - "N02_account_41_snapshot", - "N11_selected_item_buyer", - "N12_purchase_to_sale_chain", - "N13_supplier_to_buyer_overlap" - ], - "acceptance_canon": { - "root_step_id": "step_01_account_41_historical", - "primary_user_path": [ - "step_01_account_41_historical", - "step_02_selected_item_buyer_ui", - "step_04_purchase_to_sale_chain_ui" - ], - "required_paraphrase_families": [ - "canonical", - "colloquial", - "ui_selected_object" - ], - "required_carryover_invariants": [ - "selected_object", - "date_scope", - "answer_shape" - ] - }, - "bindings": { - "target_date_historical": "2020-03-31", - "focus_item_historical": "Шкаф картотечный 1000*400*2100", - "observed_supplier_candidate": "Гамма-мебель, ООО", - "observed_customer_candidate": "Департамент капитального ремонта города Москвы" - }, - "steps": [ - { - "step_id": "step_01_account_41_historical", - "question_id": "Q04", - "node_id": "N02_account_41_snapshot", - "node_role": "root_variant", - "paraphrase_family": "colloquial", - "title": "Historical account 41 anchor", - "question": "Какие товары числятся на 41 счете на дату {{bindings.target_date_historical}}", - "analysis_context": { - "as_of_date": "2020-03-31", - "source": "binding_target_date_historical" - }, - "expected_capability": "confirmed_inventory_on_hand_as_of_date", - "expected_result_mode": "confirmed_balance" - }, - { - "step_id": "step_02_selected_item_buyer_ui", - "question_id": "Q24", - "node_id": "N11_selected_item_buyer", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object", - "title": "Buyer for historical selected item UI", - "question": "По выбранному объекту \"{{bindings.focus_item_historical}}\": кому был продан товар", - "depends_on": [ - "step_01_account_41_historical" - ] - }, - { - "step_id": "step_03_selected_item_buyer_canonical", - "question_id": "Q16", - "node_id": "N11_selected_item_buyer", - "node_role": "critical_child", - "paraphrase_family": "canonical", - "title": "Buyer for historical selected item canonical", - "question": "Кому был продан товар {{bindings.focus_item_historical}}", - "depends_on": [ - "step_01_account_41_historical" - ] - }, - { - "step_id": "step_04_purchase_to_sale_chain_ui", - "question_id": "Q25", - "node_id": "N12_purchase_to_sale_chain", - "node_role": "critical_child", - "paraphrase_family": "ui_selected_object", - "title": "Purchase to sale document chain UI", - "question": "По выбранному объекту \"{{bindings.focus_item_historical}}\": через какие документы прошел путь товара: закупка -> склад -> продажа", - "depends_on": [ - "step_01_account_41_historical", - "step_02_selected_item_buyer_ui" - ] - }, - { - "step_id": "step_05_purchase_to_sale_chain_canonical", - "question_id": "Q17", - "node_id": "N12_purchase_to_sale_chain", - "node_role": "critical_child", - "paraphrase_family": "canonical", - "title": "Purchase to sale document chain", - "question": "Через какие документы прошел путь товара {{bindings.focus_item_historical}}: закупка -> склад -> продажа", - "depends_on": [ - "step_01_account_41_historical", - "step_02_selected_item_buyer_ui" - ] - }, - { - "step_id": "step_06_supplier_to_buyer_overlap", - "question_id": "Q18", - "node_id": "N13_supplier_to_buyer_overlap", - "node_role": "supporting_child", - "paraphrase_family": "canonical", - "title": "Supplier to buyer overlap", - "question": "Есть ли документально подтвержденная цепочка: поставщик {{bindings.observed_supplier_candidate}} -> товар {{bindings.focus_item_historical}} -> покупатель {{bindings.observed_customer_candidate}}", - "depends_on": [ - "step_01_account_41_historical", - "step_05_purchase_to_sale_chain_canonical" - ] - } - ] - } - ] - }, "agent_audit_expectations": { + "semantic_answer_review_first": true, "direct_answer_first": true, - "action_first_followup_required": true, - "answer_layering_required": true, - "compact_micro_action_answers_required": true, "business_utility_required": true, - "state_continuity_required": true, - "selected_object_memory_required": true, - "focus_object_required": true, - "answer_object_required": true, - "pronoun_followup_resolution_required": true, - "followup_action_resolution_required": true, - "bundle_reuse_required": true, - "temporal_honesty_required": true, - "field_truth_required": true, - "field_truth_checks": [ - "supplier_vs_organization", - "buyer_vs_organization" - ], - "forbidden_top_level_noise": [ - "status headings", - "what was considered headings", - "row counts before the business answer", - "trace-first narration", - "numbered block headings", - "exact contour jargon", - "supplier is not materialized as a separate field" - ], - "required_answer_layers": [ - "direct_answer", - "evidence", - "service_notes" - ], - "required_followup_micro_actions": [ - "who", - "when", - "document_list", - "source_document", - "amount", - "all_receipts", - "buyer", - "sale_documents", - "sale_chain" - ], - "required_state_objects": [ - "focus_object", - "answer_object", - "provenance_bundle" - ], - "reusable_answer_object_expectations": [ - "current_item", - "current_as_of_date", - "answer_object", - "current_provenance_trace", - "current_sale_trace" - ], - "answer_object_fields": [ - "supplier_if_known", - "purchase_date_if_known", - "purchase_doc_if_known", - "purchase_amount_if_known", - "provenance_docs", - "buyer_if_known", - "sale_date_if_known", - "sale_doc_if_known", - "sale_docs" - ], - "temporal_honesty_rule": [ - "distinguish requested window from nearest available evidence", - "do not merge out-of-window evidence into the direct answer silently" + "counterparty_scope_required": true, + "company_profit_boundary_required": true, + "bank_role_boundary_required": true, + "technical_garbage_forbidden": true, + "do_not_accept_if": [ + "counterparty received/paid/net-flow question is answered as company profit", + "90/91/99 accounts are used as the direct answer for counterparty money flow", + "bank or financial counterparty is presented as an ordinary customer/supplier without role caveat", + "selected counterparty follow-up switches back to the organization", + "documents follow-up loses the active counterparty", + "final answer leaks route ids, capability ids, runtime enums, or debug payload" ] }, - "acceptance_contract": { - "acceptance_unit": "scenario_tree", - "do_not_accept_if": [ - "работает только root snapshot, но ломается critical selected-object edge", - "работает только canonical wording, но ломается colloquial или ui_selected_object wording", - "работает только quoted selected-object wording, но ломается короткий местоименный follow-up по активной позиции", - "теряется date_scope на follow-up с `на эту дату` или `на ту дату`", - "ответ меняет business object, например вместо item-level ответа отдаёт dump документов", - "нарушается ordering semantics, например `старые закупки` идут не oldest-first", - "the answer is trace-first instead of action-first on a selected-object follow-up", - "the top block contains system scaffolding instead of the direct business answer", - "the answer silently uses nearest out-of-window evidence without saying so", - "supplier or buyer is surfaced through a mislabeled organization field" - ], - "required_green_for_acceptance": [ - "root nodes", - "critical edges on primary_user_paths", - "canonical coverage on critical nodes", - "colloquial coverage on critical nodes", - "ui_selected_object coverage where UI supports object selection", - "pronoun_followup coverage where the UX already established an active selected object" - ], - "required_defect_classes": [ - "semantic_understanding_gap", - "edge_carryover_gap", - "followup_action_resolution_gap", - "bundle_reuse_gap", - "field_mapping_gap", - "temporal_honesty_gap", - "answer_shape_mismatch", - "ordering_semantics_mismatch", - "runtime_capability_gap", - "business_utility_gap", - "loop_coverage_gap" - ], - "minimum_score_rule": "accepted only if analyst score >= 80 and no unresolved P0 remains" - }, - "known_failure_patterns_to_watch": [ + "scenarios": [ { - "pattern_id": "F01_selected_object_colloquial_supplier", - "symptom": "selected-object follow-up `кто это поставил нам` падает в clarification instead of provenance exact route", - "defect_class": "semantic_understanding_gap" + "scenario_id": "company_business_overview_scope", + "title": "Company overview, money flow, and scope boundary", + "description": "Establish company-level context and verify that later counterparty questions do not inherit company-profit semantics.", + "steps": [ + { + "step_id": "s01_business_overview_2020", + "title": "Direct business overview for the company and year", + "node_role": "root", + "question": "Дай взрослый бизнес-обзор {{bindings.main_organization}} за {{bindings.control_year}} год по данным 1С: обороты, входящие и исходящие деньги, нетто, НДС, долги, склад, клиенты, поставщики и что пока нельзя утверждать.", + "expected_intents": ["business_overview"], + "semantic_tags": ["business_overview", "money", "vat", "debt", "inventory", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["Альтернатива", "2020"], + "forbidden_answer_patterns": [ + "(?i)capability_id", + "(?i)selected_chain_id", + "(?i)runtime_", + "(?i)business_overview_route_template_v1", + "(?i)mcp_discovery" + ] + }, + { + "step_id": "s02_money_followup", + "title": "Company money follow-up with counterparty highlights", + "question": "Раскрой деньги подробнее: сколько получили, сколько заплатили, какой чистый денежный поток, кто главный клиент и главный поставщик в {{bindings.control_year}}.", + "depends_on": ["s01_business_overview_2020"], + "semantic_tags": ["money", "counterparty", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["2020"], + "forbidden_answer_patterns": ["(?i)capability_id", "(?i)runtime_"] + } + ] }, { - "pattern_id": "F02_numeric_tail_account_poisoning", - "symptom": "numeric fragments inside item names such as `600*3050*26` contaminate account/domain inference", - "defect_class": "semantic_understanding_gap" + "scenario_id": "svk_counterparty_documents_scope", + "title": "Explicit SVK counterparty money and document scope", + "description": "Validate that an explicit counterparty request is not confused with company overview context and that document evidence is separated from business conclusions.", + "steps": [ + { + "step_id": "s01_svk_money_documents", + "title": "Explicit counterparty money flow and evidence", + "node_role": "root", + "question": "Отдельно по контрагенту {{bindings.svk_counterparty}}, без опоры на прошлый диалог: сколько денег прошло, что входящее, что исходящее и есть ли документы или движения, на которых это основано?", + "expected_intents": ["value_flow"], + "semantic_tags": ["counterparty", "money", "documents", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["СВК"], + "forbidden_answer_patterns": [ + "(?i)capability_id", + "(?i)selected_chain_id", + "(?i)runtime_", + "(?i)mcp_discovery", + "(?i)Альтернатива Плюс.*контрагент", + "(?i)(90[\\./](01|02|09)|91[\\./]|99\\b)" + ] + }, + { + "step_id": "s02_svk_documents_followup", + "title": "Counterparty documents follow-up", + "question": "Покажи документы по этой цепочке и не смешивай {{bindings.svk_counterparty}} с организацией {{bindings.main_organization}}.", + "depends_on": ["s01_svk_money_documents"], + "semantic_tags": ["counterparty", "documents", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["СВК"], + "forbidden_answer_patterns": [ + "(?i)capability_id", + "(?i)runtime_", + "(?i)Альтернатива Плюс.*как контрагент" + ] + }, + { + "step_id": "s03_company_vs_svk_summary", + "title": "Separate company and counterparty conclusions", + "question": "Собери короткий итог: что мы подтвердили по компании, что отдельно по {{bindings.svk_counterparty}}, какие выводы можно делать и какие нельзя.", + "depends_on": ["s01_svk_money_documents", "s02_svk_documents_followup"], + "semantic_tags": ["business_overview", "counterparty", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["СВК", "компан"], + "forbidden_answer_patterns": ["(?i)capability_id", "(?i)runtime_"] + } + ] }, { - "pattern_id": "F03_date_carryover_loss", - "symptom": "follow-up `на эту дату` loses the originating date and silently switches to current date", - "defect_class": "edge_carryover_gap" + "scenario_id": "company_business_overview_colloquial_scope", + "title": "Colloquial company overview and bank-role money follow-up", + "description": "Validate colloquial wording and the bank-role caveat when the user explicitly warns not to call a bank an ordinary customer.", + "steps": [ + { + "step_id": "s01_colloquial_company_overview_2020", + "title": "Colloquial business overview for the company", + "node_role": "root", + "question": "По-человечески посмотри {{bindings.main_organization}} за {{bindings.control_year}}: что по бизнесу видно в 1С по деньгам, НДС, долгам, складу, клиентам/поставщикам и где пока нельзя делать вывод?", + "expected_intents": ["business_overview"], + "semantic_tags": ["business_overview", "colloquial", "money", "vat", "debt", "inventory", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["2020", "НДС"], + "forbidden_answer_patterns": ["(?i)capability_id", "(?i)selected_chain_id", "(?i)runtime_", "(?i)mcp_discovery"] + }, + { + "step_id": "s02_colloquial_money_leaders", + "title": "Colloquial money leaders with bank-role boundary", + "question": "А по деньгам в этом же году кто больше всего занес и кому больше всего ушло? Банк не называй обычным клиентом без оговорки.", + "depends_on": ["s01_colloquial_company_overview_2020"], + "semantic_tags": ["money", "counterparty", "bank_role_boundary", "colloquial", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["2020"], + "forbidden_answer_patterns": ["(?i)capability_id", "(?i)runtime_"] + } + ] }, { - "pattern_id": "F04_answer_shape_downgrade", - "symptom": "user asks for item-level stock aging but runtime returns raw purchase documents", - "defect_class": "answer_shape_mismatch" - }, - { - "pattern_id": "F05_oldest_first_violation", - "symptom": "`старые закупки` are listed newest-first or in another non-business order", - "defect_class": "ordering_semantics_mismatch" - }, - { - "pattern_id": "F06_pronoun_item_documents_misroute", - "symptom": "short follow-up like `покажи документы по этой позиции` drifts into `documents_by_counterparty` instead of selected-item purchase documents", - "defect_class": "followup_action_resolution_gap" - }, - { - "pattern_id": "F07_provenance_bundle_not_reused", - "symptom": "supplier/date/document lookup was already resolved for the selected item but adjacent follow-up recomputes broadly or loses the reusable bundle", - "defect_class": "bundle_reuse_gap" - }, - { - "pattern_id": "F08_short_when_replays_full_trace", - "symptom": "short follow-up such as `когда` replays the full provenance packet instead of returning the compact purchase date first", - "defect_class": "bundle_reuse_gap" - }, - { - "pattern_id": "F09_temporal_auto_broaden_not_honest", - "symptom": "the answer quietly broadens from the requested date window to nearest available evidence without separating those two facts", - "defect_class": "temporal_honesty_gap" - }, - { - "pattern_id": "F10_supplier_field_truth_blur", - "symptom": "the answer names a supplier but the surfaced business field is actually organization or another unlabeled technical field", - "defect_class": "field_mapping_gap" - }, - { - "pattern_id": "F11_trace_first_top_block_noise", - "symptom": "the answer starts with system scaffolding, trace narration, or contour jargon instead of the direct business answer", - "defect_class": "business_utility_gap" - }, - { - "pattern_id": "F12_numbered_block_scaffolding", - "symptom": "a narrow business follow-up opens with `Блок 1/2/3` scaffolding instead of a compact direct answer", - "defect_class": "business_utility_gap" - }, - { - "pattern_id": "F12b_selected_object_supplier_u_kogo_kupili_misroute", - "symptom": "selected-object follow-up such as `у кого купили` or `у кого куплено` stays on the root stock snapshot instead of switching to selected-item provenance", - "defect_class": "followup_action_resolution_gap" - }, - { - "pattern_id": "F12c_root_named_month_prepositional_misparsed", - "symptom": "root stock snapshot wording such as `что лежит у нас на складе в мае 2016` collapses to the full year boundary instead of the requested month window", - "defect_class": "temporal_honesty_gap" - }, - { - "pattern_id": "F12d_selected_object_supplier_gde_kupili_misroute", - "symptom": "selected-object follow-up such as `где мы купили это` or `где куплено!!` stays on the root stock snapshot or drifts into generic documents instead of switching to selected-item provenance", - "defect_class": "followup_action_resolution_gap" - }, - { - "pattern_id": "F13_selected_item_sale_followup_misroute", - "symptom": "selected-item follow-up such as `кому в итоге мы продали этот товар` drifts back into purchase provenance and answers about supplier instead of buyer", - "defect_class": "followup_action_resolution_gap" - }, - { - "pattern_id": "F14_root_snapshot_service_heavy_top", - "symptom": "root stock snapshot opens with service scaffolding and counters instead of a concise business summary plus top positions", - "defect_class": "business_utility_gap" + "scenario_id": "svk_selected_object_followup_scope", + "title": "Selected-object style SVK counterparty follow-up", + "description": "Validate selected-object and pronoun coverage for the SVK counterparty edge: root counterparty money fact, documents by selected object, and separated summary.", + "steps": [ + { + "step_id": "s01_select_svk_counterparty_money", + "title": "Select SVK as the active counterparty", + "node_role": "root", + "question": "Возьми как выбранного контрагента {{bindings.svk_counterparty}}: сколько по нему входящих денег, исходящих денег и расчетное нетто, на чем это основано?", + "expected_intents": ["value_flow"], + "semantic_tags": ["counterparty", "money", "selected_object", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["СВК"], + "forbidden_answer_patterns": [ + "(?i)capability_id", + "(?i)selected_chain_id", + "(?i)runtime_", + "(?i)mcp_discovery", + "(?i)(90[\\./](01|02|09)|91[\\./]|99\\b)" + ] + }, + { + "step_id": "s02_pronoun_documents_for_selected_svk", + "title": "Documents for the selected counterparty by pronoun", + "question": "Теперь по нему покажи документы и не переключайся на организацию.", + "depends_on": ["s01_select_svk_counterparty_money"], + "semantic_tags": ["counterparty", "documents", "selected_object", "pronoun_followup", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["СВК"], + "forbidden_answer_patterns": [ + "(?i)capability_id", + "(?i)runtime_", + "(?i)Альтернатива Плюс.*как контрагент" + ] + }, + { + "step_id": "s03_selected_svk_company_boundary_summary", + "title": "Boundary summary after selected-object document follow-up", + "question": "И коротко сравни: что у нас подтверждено по компании, что отдельно по выбранному контрагенту, и какие выводы нельзя делать?", + "depends_on": ["s01_select_svk_counterparty_money", "s02_pronoun_documents_for_selected_svk"], + "semantic_tags": ["business_overview", "counterparty", "selected_object", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["СВК", "компан"], + "forbidden_answer_patterns": ["(?i)capability_id", "(?i)runtime_"] + }, + { + "step_id": "s04_org_clarification_boundary_summary", + "title": "Organization clarification after selected counterparty branch", + "question": "{{bindings.main_organization}}", + "depends_on": ["s03_selected_svk_company_boundary_summary"], + "semantic_tags": ["clarification", "organization", "scope_guard"], + "required_answer_shape": "direct_answer_first", + "required_answer_patterns_all": ["Альтернатива"], + "forbidden_answer_patterns": ["(?i)capability_id", "(?i)runtime_"] + } + ] } - ], - "legacy_references": [ - "docs/orchestration/domain_inventory_stock_supplier_trace.md", - "docs/orchestration/domain_inventory_stock_supplier_trace_pool.md", - "docs/orchestration/domain_inventory_stock_supplier_trace_pack.json" ] } diff --git a/docs/orchestration/detector_registry.json b/docs/orchestration/detector_registry.json index 2a2bcc2..003bbc4 100644 --- a/docs/orchestration/detector_registry.json +++ b/docs/orchestration/detector_registry.json @@ -208,6 +208,41 @@ "forbidden_patterns": ["(?im)^\\s*(Что проверили|Опорные документы):"] } }, + "counterparty_value_flow_required_surface": { + "kind": "answer_text_required_any", + "automation_level": "automatic", + "description": "Counterparty value-flow answers must surface counterparty scope, incoming amount, outgoing amount, and net/saldo wording.", + "issue_codes": ["counterparty_value_flow_misrouted_to_company_profit"], + "inputs": ["steps//output.md"], + "check": { + "artifact_path_include_patterns": ["(?i)(s01_svk_money_documents|s01_select_svk_counterparty_money)"], + "required_patterns_any": [ + "(?is)(?=.*(СВК|Группа\\s+СВК))(?=.*(входящ|получил|получено|получили))(?=.*(исходящ|заплатил|заплачено|заплатили|ушло))(?=.*(нетто|сальдо|разниц|чистый\\s+денежный))" + ] + } + }, + "counterparty_value_flow_profit_accounts_forbidden": { + "kind": "answer_text_regex_forbidden", + "automation_level": "automatic", + "description": "Counterparty received/paid/net-flow answers must not use company profit or 90/91/99 accounts as the direct answer.", + "issue_codes": ["counterparty_value_flow_misrouted_to_company_profit"], + "inputs": ["steps//output.md"], + "check": { + "artifact_path_include_patterns": ["(?i)(s01_svk_money_documents|s01_select_svk_counterparty_money)"], + "forbidden_patterns": ["(?i)(сч[её]т\\s*(90|91|99)|90[\\./](01|02|09)|91[\\./]|99\\b|company[- ]level\\s+profit)"] + } + }, + "bank_counterparty_boundary_required": { + "kind": "answer_text_required_any", + "automation_level": "semi_automatic", + "description": "Money-leader answers that explicitly mention bank role must include a boundary instead of presenting a bank as an ordinary customer/supplier.", + "issue_codes": ["bank_counterparty_misclassified_as_business_partner"], + "inputs": ["steps//output.md"], + "check": { + "artifact_path_include_patterns": ["(?i)s02_colloquial_money_leaders"], + "required_patterns_any": ["(?is)(банк|банковск|финансов|не\\s+обычн|роль|назначени[ея]|плат[её]ж)"] + } + }, "route_candidate_needs_enablement": { "kind": "stage_review_signal", "automation_level": "semi_automatic", diff --git a/docs/orchestration/issue_catalog.json b/docs/orchestration/issue_catalog.json index be26a1d..a16da2d 100644 --- a/docs/orchestration/issue_catalog.json +++ b/docs/orchestration/issue_catalog.json @@ -411,7 +411,11 @@ "severity": "P0", "business_meaning": "Контрагентский вопрос про получили/заплатили/нетто отвечен company-level прибылью или 90/91/99 вместо контрагентского денежного потока.", "root_layers": ["followup_action_resolution_gap", "answer_shape_mismatch", "domain_scope_leak"], - "detectors": ["counterparty_value_flow_profit_mismatch"], + "detectors": [ + "counterparty_value_flow_required_surface", + "counterparty_value_flow_profit_accounts_forbidden", + "counterparty_value_flow_profit_mismatch" + ], "allowed_patch_targets": [ "llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts", "llm_normalizer/backend/src/services/assistantMcpDiscoveryRuntimeBridge.ts", @@ -489,7 +493,10 @@ "severity": "P1", "business_meaning": "Банк/финансовая организация ошибочно подана как обычный клиент/поставщик без роли, назначения платежа и договорного контекста.", "root_layers": ["business_semantic_role_gap", "domain_scope"], - "detectors": ["bank_counterparty_role_boundary_missing"], + "detectors": [ + "bank_counterparty_boundary_required", + "bank_counterparty_role_boundary_missing" + ], "allowed_patch_targets": [ "llm_normalizer/backend/src/services/address_runtime/composeStage.ts", "llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts", diff --git a/scripts/agent_detector_runner.py b/scripts/agent_detector_runner.py index 104f478..4165a74 100644 --- a/scripts/agent_detector_runner.py +++ b/scripts/agent_detector_runner.py @@ -13,6 +13,9 @@ DETECTOR_REGISTRY_PATH = REPO_ROOT / "docs" / "orchestration" / "detector_regist ISSUE_CATALOG_PATH = REPO_ROOT / "docs" / "orchestration" / "issue_catalog.json" DEFAULT_GLOBAL_DETECTORS = ["missing_effective_runtime_json"] DETECTOR_RESULTS_SCHEMA_VERSION = "agent_detector_results_v1" +DEFAULT_LIMITED_NEXT_ACTION_EXTRA_PATTERNS = [ + r"(?i)(что\s+проверить\s+дальше|для\s+ответа.{0,120}нужно|нужно\s+отдельно\s+(?:считать|посчитать|проверить)|проверить\s+дальше)" +] def read_json(path: Path) -> Any: @@ -166,6 +169,43 @@ def read_text_or_empty(path: Path) -> str: return "" +def decode_win1251_utf8_mojibake(value: str) -> str | None: + try: + return value.encode("cp1251").decode("utf-8") + except UnicodeError: + return None + + +def decode_latin1_utf8_mojibake(value: str) -> str | None: + try: + return value.encode("latin1").decode("utf-8") + except UnicodeError: + return None + + +def text_match_variants(value: str) -> list[str]: + variants: list[str] = [] + queue = [str(value or "")] + while queue: + current = queue.pop(0) + if current in variants: + continue + variants.append(current) + for decoded in (decode_win1251_utf8_mojibake(current), decode_latin1_utf8_mojibake(current)): + if decoded and decoded not in variants and decoded not in queue: + queue.append(decoded) + return variants + + +def first_pattern_search(patterns: list[re.Pattern[str]], text: str) -> re.Match[str] | None: + for variant in text_match_variants(text): + for pattern in patterns: + match = pattern.search(variant) + if match: + return match + return None + + def assistant_text_from_turn_path(path: Path | None) -> str: if path is None or not path.exists(): return "" @@ -384,7 +424,7 @@ def evaluate_forbidden_regex( lines = [line for line in text.splitlines() if line.strip()] text = "\n".join(lines[:prefix_line_count]) for pattern in patterns: - match = pattern.search(text) + match = first_pattern_search([pattern], text) if match: evidence.append( { @@ -411,7 +451,7 @@ def evaluate_required_any( matched: list[dict[str, Any]] = [] for output in outputs: text = current_answer_text_for_output(output) - match = next((pattern.search(text) for pattern in patterns if pattern.search(text)), None) + match = first_pattern_search(patterns, text) if match: matched.append({"path": output["repo_path"], "match": match.group(0)[:240]}) else: @@ -428,18 +468,24 @@ def evaluate_limited_next_action( ) -> dict[str, Any]: check = detector.get("check") if isinstance(detector.get("check"), dict) else {} limited_patterns = compile_patterns(normalize_string_list(check.get("limited_patterns"))) - next_action_patterns = compile_patterns(normalize_string_list(check.get("required_next_action_patterns_any"))) + next_action_pattern_sources = [ + *normalize_string_list(check.get("required_next_action_patterns_any")), + *normalize_string_list(check.get("required_next_action_patterns_extra_any")), + ] + if detector_name == "limited_answer_without_next_action": + next_action_pattern_sources.extend(DEFAULT_LIMITED_NEXT_ACTION_EXTRA_PATTERNS) + next_action_patterns = compile_patterns(next_action_pattern_sources) if not outputs: return build_result(detector_name, detector, "skipped", "no output.md-style artifacts matched detector scope") failures: list[dict[str, Any]] = [] limited_outputs = 0 for output in outputs: text = current_answer_text_for_output(output) - limited_match = next((pattern.search(text) for pattern in limited_patterns if pattern.search(text)), None) + limited_match = first_pattern_search(limited_patterns, text) if not limited_match: continue limited_outputs += 1 - if not any(pattern.search(text) for pattern in next_action_patterns): + if not first_pattern_search(next_action_patterns, text): failures.append({"path": output["repo_path"], "limited_match": limited_match.group(0)[:240]}) status = "fail" if failures else "pass" if failures: diff --git a/scripts/domain_case_loop.py b/scripts/domain_case_loop.py index 473f98f..f4519f2 100644 --- a/scripts/domain_case_loop.py +++ b/scripts/domain_case_loop.py @@ -6225,7 +6225,22 @@ def summarize_detector_results(detector_results: dict[str, Any] | None, *, limit return names[:limit] status = str(summary.get("status") or "skipped") - declared_detector_count = len(normalize_string_list(detector_results.get("declared_detectors_under_test"))) + declared_detectors = normalize_string_list(detector_results.get("declared_detectors_under_test")) + declared_detector_count = len(declared_detectors) + + def _declared_with_status(status: str) -> list[str]: + names: list[str] = [] + declared = set(declared_detectors) + if not declared: + return names + for item in results: + if not isinstance(item, dict) or str(item.get("status") or "") != status: + continue + detector_name = str(item.get("detector") or "").strip() + if detector_name in declared and detector_name not in names: + names.append(detector_name) + return names[:limit] + return { "status": status, "detector_count": int(summary.get("detector_count") or len(results)), @@ -6237,6 +6252,7 @@ def summarize_detector_results(detector_results: dict[str, Any] | None, *, limit "failed_detectors": _detectors_with_status("fail"), "review_detectors": _detectors_with_status("review"), "skipped_detectors": _detectors_with_status("skipped"), + "declared_skipped_detectors": _declared_with_status("skipped"), "signal_ok_for_auto_coder": status in {"fail", "review"}, } @@ -6247,6 +6263,9 @@ def evaluate_detector_results_gate(detector_results_summary: dict[str, Any]) -> detector_count = int(detector_results_summary.get("detector_count") or 0) if declared_detector_count > 0 and detector_count == 0: return False, "declared_detector_results_missing" + declared_skipped = normalize_string_list(detector_results_summary.get("declared_skipped_detectors")) + if declared_skipped: + return False, f"declared_detector_results_skipped:{','.join(declared_skipped)}" if declared_detector_count > 0 and status == "skipped": return False, "declared_detector_results_skipped" if status == "fail": diff --git a/scripts/test_agent_detector_runner.py b/scripts/test_agent_detector_runner.py index 0165717..b21c785 100644 --- a/scripts/test_agent_detector_runner.py +++ b/scripts/test_agent_detector_runner.py @@ -22,6 +22,10 @@ def write_text(path: Path, text: str) -> None: path.write_text(text, encoding="utf-8") +def to_win1251_mojibake(value: str) -> str: + return value.encode("utf-8").decode("cp1251") + + class AgentDetectorRunnerTests(unittest.TestCase): def test_default_runner_fails_missing_effective_runtime(self) -> None: with tempfile.TemporaryDirectory() as tmp: @@ -102,6 +106,59 @@ class AgentDetectorRunnerTests(unittest.TestCase): self.assertEqual(results["summary"]["status"], "pass") self.assertEqual(results["results"][0]["status"], "pass") + def test_limited_next_action_accepts_mojibake_what_to_check_next(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + artifact_dir = root / "run" + answer = ( + "Это не подтвержденная чистая прибыль.\n" + "Что проверить дальше: чистую прибыль через счета 90/91/99 и закрытие периода." + ) + write_text(artifact_dir / "steps" / "s01" / "output.md", to_win1251_mojibake(answer)) + registry_path = root / "detector_registry.json" + issue_catalog_path = root / "issue_catalog.json" + write_json( + registry_path, + { + "schema_version": "agent_detector_registry_v1", + "detectors": { + "limited_answer_without_next_action": { + "kind": "answer_text_required_when_limited", + "automation_level": "semi_automatic", + "description": "Limited answers need a next action.", + "issue_codes": ["business_next_step_missing"], + "inputs": ["output.md"], + "check": { + "limited_patterns": ["(?i)не подтвержден"], + "required_next_action_patterns_any": ["(?i)(можно|проверь)"], + }, + } + }, + }, + ) + write_json( + issue_catalog_path, + { + "schema_version": "agent_issue_catalog_v1", + "issues": { + "business_next_step_missing": { + "detectors": ["limited_answer_without_next_action"], + } + }, + }, + ) + + results = runner.build_detector_results( + artifact_dir, + issue_codes=["business_next_step_missing"], + registry_path=registry_path, + issue_catalog_path=issue_catalog_path, + include_default_global=False, + ) + + self.assertEqual(results["summary"]["status"], "pass") + self.assertEqual(results["results"][0]["message"], "limited answer includes a next action") + def test_candidate_forbidden_regex_is_limited_to_evidence_path(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) diff --git a/scripts/test_domain_case_loop_lead_handoff.py b/scripts/test_domain_case_loop_lead_handoff.py index 2d1fdb5..801799e 100644 --- a/scripts/test_domain_case_loop_lead_handoff.py +++ b/scripts/test_domain_case_loop_lead_handoff.py @@ -404,6 +404,24 @@ class DomainCaseLoopLeadHandoffTests(unittest.TestCase): self.assertEqual(summary["declared_detector_count"], 1) self.assertEqual(reason, "declared_detector_results_missing") + def test_detector_gate_blocks_declared_detector_skipped_inside_mixed_pass_run(self) -> None: + summary = dcl.summarize_detector_results( + { + "summary": {"status": "pass", "detector_count": 2, "pass": 1, "fail": 0, "review": 0, "skipped": 1}, + "results": [ + {"detector": "runtime_tokens_in_user_answer", "status": "pass"}, + {"detector": "counterparty_value_flow_profit_mismatch", "status": "skipped"}, + ], + "declared_detectors_under_test": ["counterparty_value_flow_profit_mismatch"], + } + ) + + ok, reason = dcl.evaluate_detector_results_gate(summary) + + self.assertFalse(ok) + self.assertEqual(summary["declared_skipped_detectors"], ["counterparty_value_flow_profit_mismatch"]) + self.assertEqual(reason, "declared_detector_results_skipped:counterparty_value_flow_profit_mismatch") + def test_auto_coder_gate_allows_when_detector_results_confirm_failure(self) -> None: repair_targets = { "targets": [