From 4dff069ae318dffb168b7bae4590e8e3a1598fca Mon Sep 17 00:00:00 2001 From: dctouch Date: Thu, 2 Apr 2026 12:02:30 +0300 Subject: [PATCH] =?UTF-8?q?=D0=90=D0=94=D0=A0=D0=95=D0=A1=D0=9D=D0=AB?= =?UTF-8?q?=D0=99=20=D0=A0=D0=95=D0=96=D0=98=D0=9C=20-?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...dress_open_contracts_focus_2026-04-02.json | 5 + .../address_open_items_focus_2026-04-02.json | 44 + .../README.md | 23 + .../failures_only.json | 1 + .../full_live_results.json | 139 ++ .../run_summary.json | 43 + .../README.md | 28 + .../failures_only.json | 154 +++ .../full_live_results.json | 510 ++++++++ .../response_audit.md | 7 + .../run_summary.json | 50 + .../README.md | 28 + .../failures_only.json | 454 +++++++ .../full_live_results.json | 1117 ++++++++++++++++ .../response_audit.md | 11 + .../run_summary.json | 52 + .../README.md | 28 + .../failures_only.json | 454 +++++++ .../full_live_results.json | 1118 +++++++++++++++++ .../response_audit.md | 11 + .../run_summary.json | 52 + .../README.md | 28 + .../failures_only.json | 1 + .../full_live_results.json | 1118 +++++++++++++++++ .../response_audit.md | 11 + .../run_summary.json | 47 + .../dist/services/addressFilterExtractor.js | 75 +- .../dist/services/addressIntentResolver.js | 234 +++- .../dist/services/addressQueryClassifier.js | 17 +- .../dist/services/addressQueryService.js | 417 +++++- .../dist/services/addressRecipeCatalog.js | 37 +- .../services/address_runtime/composeStage.js | 30 +- .../address_runtime/decomposeStage.js | 13 +- .../services/address_runtime/resolveStage.js | 11 + .../backend/dist/services/assistantService.js | 621 ++++++++- .../dist/services/openaiResponsesClient.js | 41 +- .../src/services/addressFilterExtractor.ts | 25 +- .../src/services/addressIntentResolver.ts | 25 + .../src/services/addressQueryService.ts | 178 ++- .../src/services/addressRecipeCatalog.ts | 4 +- .../services/address_runtime/composeStage.ts | 7 +- .../tests/addressQueryRuntimeM23.test.ts | 45 + scripts/run_address_live_slang_stress.py | 113 +- 43 files changed, 7289 insertions(+), 138 deletions(-) create mode 100644 docs/ADDRESS/question_sets/address_open_contracts_focus_2026-04-02.json create mode 100644 docs/ADDRESS/question_sets/address_open_items_focus_2026-04-02.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/README.md create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/failures_only.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/full_live_results.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/run_summary.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/README.md create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/failures_only.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/full_live_results.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/response_audit.md create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/run_summary.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/README.md create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/failures_only.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/full_live_results.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/response_audit.md create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/run_summary.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/README.md create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/failures_only.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/full_live_results.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/response_audit.md create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/run_summary.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/README.md create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/failures_only.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/full_live_results.json create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/response_audit.md create mode 100644 docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/run_summary.json diff --git a/docs/ADDRESS/question_sets/address_open_contracts_focus_2026-04-02.json b/docs/ADDRESS/question_sets/address_open_contracts_focus_2026-04-02.json new file mode 100644 index 0000000..e6d579e --- /dev/null +++ b/docs/ADDRESS/question_sets/address_open_contracts_focus_2026-04-02.json @@ -0,0 +1,5 @@ +[ + { "id": "OC001", "text": "Покажи незакрытые договоры на 2020-12-31", "expected_intent": "list_open_contracts", "expected_mode": "address_query", "expected_reply_type": "factual" }, + { "id": "OC002", "text": "Есть ли долг по договору 15/24 на 2020-07-31", "expected_intent": "open_items_by_counterparty_or_contract", "expected_mode": "address_query" }, + { "id": "OC003", "text": "Show open contracts as of 2020-12-31", "expected_intent": "list_open_contracts", "expected_mode": "address_query", "expected_reply_type": "factual" } +] diff --git a/docs/ADDRESS/question_sets/address_open_items_focus_2026-04-02.json b/docs/ADDRESS/question_sets/address_open_items_focus_2026-04-02.json new file mode 100644 index 0000000..af47538 --- /dev/null +++ b/docs/ADDRESS/question_sets/address_open_items_focus_2026-04-02.json @@ -0,0 +1,44 @@ +[ + { + "id": "OI001", + "text": "Покажи хвосты по контрагенту СВК на 2020-12-31", + "expected_intent": "open_items_by_counterparty_or_contract", + "expected_mode": "address_query" + }, + { + "id": "OI002", + "text": "Покажи хвосты по контрагенту СВК на конец периода 2020-12-31", + "expected_intent": "open_items_by_counterparty_or_contract", + "expected_mode": "address_query" + }, + { + "id": "OI003", + "text": "Есть ли незакрытые позиции по договору 15/24 на 2020-12-31", + "expected_intent": "open_items_by_counterparty_or_contract", + "expected_mode": "address_query" + }, + { + "id": "OI004", + "text": "Покажи открытые позиции по договору 15/24", + "expected_intent": "open_items_by_counterparty_or_contract", + "expected_mode": "address_query" + }, + { + "id": "OI005", + "text": "Покажи незакрытые договоры на 2020-12-31", + "expected_intent": "list_open_contracts", + "expected_mode": "address_query" + }, + { + "id": "OI006", + "text": "Покажи документы по договору 15/24 за 2020", + "expected_intent": "list_documents_by_contract", + "expected_mode": "address_query" + }, + { + "id": "OI007", + "text": "Покажи банковские операции по договору 15/24", + "expected_intent": "bank_operations_by_contract", + "expected_mode": "address_query" + } +] diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/README.md b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/README.md new file mode 100644 index 0000000..0ce4be8 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/README.md @@ -0,0 +1,23 @@ +# 2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36 + +Generated at: 2026-04-02T11:17:54 +Questions file: X:\1C\NDC_1C\docs\ADDRESS\question_sets\address_open_contracts_focus_2026-04-02.json +Backend URL: http://127.0.0.1:8787/api/assistant/message +LLM: local / qwen2.5-14b-instruct-1m @ http://127.0.0.1:1234 + +## Totals +- questions_total: 3 +- ok_200_count: 3 +- semantic_pass_count: 3 +- semantic_pass_rate: 1.0 +- factual_count: 2 +- partial_coverage_count: 1 +- clarification_required_count: 0 +- http_error_count: 0 +- llm_decomposition_applied_count: 2 +- avg_elapsed_ms: 6046 + +## Files +- run_summary.json +- full_live_results.json +- failures_only.json diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/failures_only.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/failures_only.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/failures_only.json @@ -0,0 +1 @@ +[] diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/full_live_results.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/full_live_results.json new file mode 100644 index 0000000..5d94bde --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/full_live_results.json @@ -0,0 +1,139 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36", + "generated_at": "2026-04-02T11:17:54", + "summary": { + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36", + "generated_at": "2026-04-02T11:17:54", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_contracts_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "totals": { + "questions_total": 3, + "ok_200_count": 3, + "semantic_pass_count": 3, + "semantic_pass_rate": 1.0, + "strict_pass_count": 3, + "strict_pass_rate": 1.0, + "factual_count": 2, + "partial_coverage_count": 1, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 2, + "avg_elapsed_ms": 6046 + }, + "distributions": { + "reply_type": { + "factual": 2, + "partial_coverage": 1 + }, + "actual_intent": { + "list_open_contracts": 2, + "open_items_by_counterparty_or_contract": 1 + }, + "actual_mode": { + "address_query": 3 + }, + "mcp_call_status": { + "matched_non_empty": 2, + "materialized_but_not_anchor_matched": 1 + }, + "limited_reason_category": { + "empty_match": 1 + } + } + }, + "rows": [ + { + "index": 1, + "id": "OC001", + "question": "Покажи незакрытые договоры на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36-oc001", + "status_code": 200, + "ok": true, + "elapsed_ms": 6945, + "reply_type": "factual", + "trace_id": "address-LcdN7m1NSW", + "assistant_reply": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).\nСтрок движения: 20.\nДоговорных кандидатов: 0.\nДоговорные якоря в live-строках не выделены; показаны связанные движения как fallback.\n1. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 127216.46\n2. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 235682.42\n3. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 337318.8\n4. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.09 / 91.02 | 10615989.53\n5. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.01 / 91.09 | 1614344.98\n6. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 90.09 / 90.02.1 | 6430415.34", + "expected_intent": "list_open_contracts", + "actual_intent": "list_open_contracts", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": "factual", + "reply_match": true, + "semantic_pass": true, + "strict_pass": true, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "error_code": null, + "error_message": null + }, + { + "index": 2, + "id": "OC002", + "question": "Есть ли долг по договору 15/24 на 2020-07-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36-oc002", + "status_code": 200, + "ok": true, + "elapsed_ms": 6118, + "reply_type": "partial_coverage", + "trace_id": "address-p8eGdSMphA", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "strict_pass": true, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "error_code": null, + "error_message": null + }, + { + "index": 3, + "id": "OC003", + "question": "Show open contracts as of 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36-oc003", + "status_code": 200, + "ok": true, + "elapsed_ms": 5075, + "reply_type": "factual", + "trace_id": "address-POqrwAVYkI", + "assistant_reply": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).\nСтрок движения: 20.\nДоговорных кандидатов: 0.\nДоговорные якоря в live-строках не выделены; показаны связанные движения как fallback.\n1. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 127216.46\n2. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 235682.42\n3. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 337318.8\n4. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.09 / 91.02 | 10615989.53\n5. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.01 / 91.09 | 1614344.98\n6. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 90.09 / 90.02.1 | 6430415.34", + "expected_intent": "list_open_contracts", + "actual_intent": "list_open_contracts", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": "factual", + "reply_match": true, + "semantic_pass": true, + "strict_pass": true, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": false, + "llm_decomposition_reason": "normalized_fragment_rejected_intent_drop", + "fallback_rule_hit": null, + "error_code": null, + "error_message": null + } + ] +} diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/run_summary.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/run_summary.json new file mode 100644 index 0000000..91c66a6 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36/run_summary.json @@ -0,0 +1,43 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-17-36", + "generated_at": "2026-04-02T11:17:54", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_contracts_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "totals": { + "questions_total": 3, + "ok_200_count": 3, + "semantic_pass_count": 3, + "semantic_pass_rate": 1.0, + "strict_pass_count": 3, + "strict_pass_rate": 1.0, + "factual_count": 2, + "partial_coverage_count": 1, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 2, + "avg_elapsed_ms": 6046 + }, + "distributions": { + "reply_type": { + "factual": 2, + "partial_coverage": 1 + }, + "actual_intent": { + "list_open_contracts": 2, + "open_items_by_counterparty_or_contract": 1 + }, + "actual_mode": { + "address_query": 3 + }, + "mcp_call_status": { + "matched_non_empty": 2, + "materialized_but_not_anchor_matched": 1 + }, + "limited_reason_category": { + "empty_match": 1 + } + } +} diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/README.md b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/README.md new file mode 100644 index 0000000..8c8a9d4 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/README.md @@ -0,0 +1,28 @@ +# 2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30 + +Generated at: 2026-04-02T11:25:48 +Questions file: X:\1C\NDC_1C\docs\ADDRESS\question_sets\address_open_contracts_focus_2026-04-02.json +Backend URL: http://127.0.0.1:8787/api/assistant/message +LLM: local / qwen2.5-14b-instruct-1m @ http://127.0.0.1:1234 +Strict policy: route + +## Totals +- questions_total: 3 +- ok_200_count: 3 +- semantic_pass_count: 3 +- semantic_pass_rate: 1.0 +- route_pass_count: 2 +- route_pass_rate: 0.6667 +- strict_pass_count: 2 +- strict_pass_rate: 0.6667 +- factual_count: 2 +- partial_coverage_count: 1 +- clarification_required_count: 0 +- http_error_count: 0 +- llm_decomposition_applied_count: 2 +- avg_elapsed_ms: 6135.3 + +## Files +- run_summary.json +- full_live_results.json +- failures_only.json diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/failures_only.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/failures_only.json new file mode 100644 index 0000000..e627e0c --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/failures_only.json @@ -0,0 +1,154 @@ +[ + { + "index": 2, + "id": "OC002", + "question": "Есть ли долг по договору 15/24 на 2020-07-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30-oc002", + "status_code": 200, + "ok": true, + "elapsed_ms": 7108, + "reply_type": "partial_coverage", + "trace_id": "address-jY3DOwe_a4", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-jY3DOwe_a4", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "verify_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "VERIFY_FACTUAL", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24", + "period_from": "2020-07-01", + "period_to": "2020-07-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "materialized_but_not_anchor_matched", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "3w1iqg2uMfQRU2", + "llm_decomposition_effective_message": "Проверить наличие долга по договору 15/24 на дату 2020-07-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "есть ли долг по договору 15/24 на 2020-07-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "3w1iqg2uMfQRU2", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Проверить наличие долга по договору 15/24 на дату 2020-07-31." + } + }, + "error_code": null, + "error_message": null + } +] diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/full_live_results.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/full_live_results.json new file mode 100644 index 0000000..bd108c3 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/full_live_results.json @@ -0,0 +1,510 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30", + "generated_at": "2026-04-02T11:25:48", + "summary": { + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30", + "generated_at": "2026-04-02T11:25:48", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_contracts_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "strict_policy": "route", + "totals": { + "questions_total": 3, + "ok_200_count": 3, + "semantic_pass_count": 3, + "semantic_pass_rate": 1.0, + "route_pass_count": 2, + "route_pass_rate": 0.6667, + "strict_pass_count": 2, + "strict_pass_rate": 0.6667, + "factual_count": 2, + "partial_coverage_count": 1, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 2, + "avg_elapsed_ms": 6135.3 + }, + "distributions": { + "reply_type": { + "factual": 2, + "partial_coverage": 1 + }, + "actual_intent": { + "list_open_contracts": 2, + "open_items_by_counterparty_or_contract": 1 + }, + "actual_mode": { + "address_query": 3 + }, + "mcp_call_status": { + "matched_non_empty": 2, + "materialized_but_not_anchor_matched": 1 + }, + "limited_reason_category": { + "empty_match": 1 + }, + "route_health": { + "ok_or_factual": 2, + "likely_blocked_route": 1 + } + } + }, + "rows": [ + { + "index": 1, + "id": "OC001", + "question": "Покажи незакрытые договоры на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30-oc001", + "status_code": 200, + "ok": true, + "elapsed_ms": 6211, + "reply_type": "factual", + "trace_id": "address-Fgx_nQgSif", + "assistant_reply": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).\nСтрок движения: 20.\nДоговорных кандидатов: 0.\nДоговорные якоря в live-строках не выделены; показаны связанные движения как fallback.\n1. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 127216.46\n2. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 235682.42\n3. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 337318.8\n4. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.09 / 91.02 | 10615989.53\n5. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.01 / 91.09 | 1614344.98\n6. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 90.09 / 90.02.1 | 6430415.34", + "assistant_reply_first_line": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).", + "expected_intent": "list_open_contracts", + "actual_intent": "list_open_contracts", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": "factual", + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_contracts_candidates_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 20, + "rows_matched": 20, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-Fgx_nQgSif", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_contract_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "list_open_contracts", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "period_from": "2020-12-01", + "period_to": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_contracts_candidates_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "unknown", + "anchor_value_raw": null, + "anchor_value_resolved": null, + "resolver_confidence": "low", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 20, + "rows_materialized": 20, + "rows_matched": 20, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "UEfU9FKCNBm898", + "llm_decomposition_effective_message": "Показать незакрытые договоры по состоянию на конец декабря 2020 года.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи незакрытые договоры на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "UEfU9FKCNBm898", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать незакрытые договоры по состоянию на конец декабря 2020 года." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 2, + "id": "OC002", + "question": "Есть ли долг по договору 15/24 на 2020-07-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30-oc002", + "status_code": 200, + "ok": true, + "elapsed_ms": 7108, + "reply_type": "partial_coverage", + "trace_id": "address-jY3DOwe_a4", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-jY3DOwe_a4", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "verify_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "VERIFY_FACTUAL", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24", + "period_from": "2020-07-01", + "period_to": "2020-07-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "materialized_but_not_anchor_matched", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "3w1iqg2uMfQRU2", + "llm_decomposition_effective_message": "Проверить наличие долга по договору 15/24 на дату 2020-07-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "есть ли долг по договору 15/24 на 2020-07-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "3w1iqg2uMfQRU2", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Проверить наличие долга по договору 15/24 на дату 2020-07-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 3, + "id": "OC003", + "question": "Show open contracts as of 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30-oc003", + "status_code": 200, + "ok": true, + "elapsed_ms": 5087, + "reply_type": "factual", + "trace_id": "address-RNbEKp80R9", + "assistant_reply": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).\nСтрок движения: 20.\nДоговорных кандидатов: 0.\nДоговорные якоря в live-строках не выделены; показаны связанные движения как fallback.\n1. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 127216.46\n2. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 235682.42\n3. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 337318.8\n4. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.09 / 91.02 | 10615989.53\n5. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.01 / 91.09 | 1614344.98\n6. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 90.09 / 90.02.1 | 6430415.34", + "assistant_reply_first_line": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).", + "expected_intent": "list_open_contracts", + "actual_intent": "list_open_contracts", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": "factual", + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_contracts_candidates_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 20, + "rows_matched": 20, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": false, + "llm_decomposition_reason": "normalized_fragment_rejected_intent_drop", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-RNbEKp80R9", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_contract_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "list_open_contracts", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "period_from": "2020-12-01", + "period_to": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_contracts_candidates_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "unknown", + "anchor_value_raw": null, + "anchor_value_resolved": null, + "resolver_confidence": "low", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 20, + "rows_materialized": 20, + "rows_matched": 20, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": false, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "Cc8Nu8vmBtCmxF", + "llm_decomposition_effective_message": "Show open contracts as of 2020-12-31", + "llm_decomposition_reason": "normalized_fragment_rejected_intent_drop", + "fallback_rule_hit": null, + "sanitized_user_message": "show open contracts as of 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "Cc8Nu8vmBtCmxF", + "prompt_version": "normalizer_v2_0_2", + "applied": false, + "effective_message": "Show open contracts as of 2020-12-31" + } + }, + "error_code": null, + "error_message": null + } + ] +} diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/response_audit.md b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/response_audit.md new file mode 100644 index 0000000..06f7afd --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/response_audit.md @@ -0,0 +1,7 @@ +# Response Audit: 2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30 + +| id | strict | route_health | reply_type | intent | limited_reason | question | assistant_first_line | +|---|---|---|---|---|---|---|---| +| OC001 | True | ok_or_factual | factual | list_open_contracts | None | Покажи незакрытые договоры на 2020-12-31 | Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76). | +| OC002 | False | likely_blocked_route | partial_coverage | open_items_by_counterparty_or_contract | empty_match | Есть ли долг по договору 15/24 на 2020-07-31 | В live-данных по текущему фильтру записи не найдены. | +| OC003 | True | ok_or_factual | factual | list_open_contracts | None | Show open contracts as of 2020-12-31 | Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76). | diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/run_summary.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/run_summary.json new file mode 100644 index 0000000..938c037 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30/run_summary.json @@ -0,0 +1,50 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-25-30", + "generated_at": "2026-04-02T11:25:48", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_contracts_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "strict_policy": "route", + "totals": { + "questions_total": 3, + "ok_200_count": 3, + "semantic_pass_count": 3, + "semantic_pass_rate": 1.0, + "route_pass_count": 2, + "route_pass_rate": 0.6667, + "strict_pass_count": 2, + "strict_pass_rate": 0.6667, + "factual_count": 2, + "partial_coverage_count": 1, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 2, + "avg_elapsed_ms": 6135.3 + }, + "distributions": { + "reply_type": { + "factual": 2, + "partial_coverage": 1 + }, + "actual_intent": { + "list_open_contracts": 2, + "open_items_by_counterparty_or_contract": 1 + }, + "actual_mode": { + "address_query": 3 + }, + "mcp_call_status": { + "matched_non_empty": 2, + "materialized_but_not_anchor_matched": 1 + }, + "limited_reason_category": { + "empty_match": 1 + }, + "route_health": { + "ok_or_factual": 2, + "likely_blocked_route": 1 + } + } +} diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/README.md b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/README.md new file mode 100644 index 0000000..43a8540 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/README.md @@ -0,0 +1,28 @@ +# 2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21 + +Generated at: 2026-04-02T11:44:13 +Questions file: X:\1C\NDC_1C\docs\ADDRESS\question_sets\address_open_items_focus_2026-04-02.json +Backend URL: http://127.0.0.1:8787/api/assistant/message +LLM: local / qwen2.5-14b-instruct-1m @ http://127.0.0.1:1234 +Strict policy: route + +## Totals +- questions_total: 7 +- ok_200_count: 7 +- semantic_pass_count: 7 +- semantic_pass_rate: 1.0 +- route_pass_count: 4 +- route_pass_rate: 0.5714 +- strict_pass_count: 4 +- strict_pass_rate: 0.5714 +- factual_count: 4 +- partial_coverage_count: 3 +- clarification_required_count: 0 +- http_error_count: 0 +- llm_decomposition_applied_count: 7 +- avg_elapsed_ms: 7423 + +## Files +- run_summary.json +- full_live_results.json +- failures_only.json diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/failures_only.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/failures_only.json new file mode 100644 index 0000000..8807988 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/failures_only.json @@ -0,0 +1,454 @@ +[ + { + "index": 1, + "id": "OI001", + "question": "Покажи хвосты по контрагенту СВК на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi001", + "status_code": 200, + "ok": true, + "elapsed_ms": 9351, + "reply_type": "partial_coverage", + "trace_id": "address-Mjn6ZDlzkm", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-Mjn6ZDlzkm", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "counterparty": "СВК", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "counterparty", + "anchor_value_raw": "СВК", + "anchor_value_resolved": "СВК", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "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": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "PDTZVysmUMSJ3U", + "llm_decomposition_effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи хвосты по контрагенту свк на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "PDTZVysmUMSJ3U", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 2, + "id": "OI002", + "question": "Покажи хвосты по контрагенту СВК на конец периода 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi002", + "status_code": 200, + "ok": true, + "elapsed_ms": 8137, + "reply_type": "partial_coverage", + "trace_id": "address-XMrKo9tL1X", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-XMrKo9tL1X", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "counterparty": "СВК", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "counterparty", + "anchor_value_raw": "СВК", + "anchor_value_resolved": "СВК", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "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": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "Nvww2yZD2a6NwG", + "llm_decomposition_effective_message": "Показать хвосты (не сходящиеся или повисшие записи) для контрагента СВК на дату 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи хвосты по контрагенту свк на конец периода 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "Nvww2yZD2a6NwG", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать хвосты (не сходящиеся или повисшие записи) для контрагента СВК на дату 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 4, + "id": "OI004", + "question": "Покажи открытые позиции по договору 15/24", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi004", + "status_code": 200, + "ok": true, + "elapsed_ms": 7126, + "reply_type": "partial_coverage", + "trace_id": "address-sy7tBdK8xj", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-sy7tBdK8xj", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "materialized_but_not_anchor_matched", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "fpG27I3Bq2rPIb", + "llm_decomposition_effective_message": "Показать открытые позиции по договору 15/24.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи открытые позиции по договору 15/24", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "fpG27I3Bq2rPIb", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать открытые позиции по договору 15/24." + } + }, + "error_code": null, + "error_message": null + } +] diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/full_live_results.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/full_live_results.json new file mode 100644 index 0000000..c42dbfe --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/full_live_results.json @@ -0,0 +1,1117 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21", + "generated_at": "2026-04-02T11:44:13", + "summary": { + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21", + "generated_at": "2026-04-02T11:44:13", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_items_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "strict_policy": "route", + "totals": { + "questions_total": 7, + "ok_200_count": 7, + "semantic_pass_count": 7, + "semantic_pass_rate": 1.0, + "route_pass_count": 4, + "route_pass_rate": 0.5714, + "strict_pass_count": 4, + "strict_pass_rate": 0.5714, + "factual_count": 4, + "partial_coverage_count": 3, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 7, + "avg_elapsed_ms": 7423 + }, + "distributions": { + "reply_type": { + "partial_coverage": 3, + "factual": 4 + }, + "actual_intent": { + "open_items_by_counterparty_or_contract": 4, + "list_open_contracts": 1, + "list_documents_by_contract": 1, + "bank_operations_by_contract": 1 + }, + "actual_mode": { + "address_query": 7 + }, + "mcp_call_status": { + "materialized_but_not_anchor_matched": 3, + "matched_non_empty": 4 + }, + "limited_reason_category": { + "empty_match": 3 + }, + "route_health": { + "likely_blocked_route": 3, + "ok_or_factual": 4 + } + } + }, + "rows": [ + { + "index": 1, + "id": "OI001", + "question": "Покажи хвосты по контрагенту СВК на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi001", + "status_code": 200, + "ok": true, + "elapsed_ms": 9351, + "reply_type": "partial_coverage", + "trace_id": "address-Mjn6ZDlzkm", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-Mjn6ZDlzkm", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "counterparty": "СВК", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "counterparty", + "anchor_value_raw": "СВК", + "anchor_value_resolved": "СВК", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "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": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "PDTZVysmUMSJ3U", + "llm_decomposition_effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи хвосты по контрагенту свк на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "PDTZVysmUMSJ3U", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 2, + "id": "OI002", + "question": "Покажи хвосты по контрагенту СВК на конец периода 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi002", + "status_code": 200, + "ok": true, + "elapsed_ms": 8137, + "reply_type": "partial_coverage", + "trace_id": "address-XMrKo9tL1X", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-XMrKo9tL1X", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "counterparty": "СВК", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "counterparty", + "anchor_value_raw": "СВК", + "anchor_value_resolved": "СВК", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "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": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "Nvww2yZD2a6NwG", + "llm_decomposition_effective_message": "Показать хвосты (не сходящиеся или повисшие записи) для контрагента СВК на дату 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи хвосты по контрагенту свк на конец периода 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "Nvww2yZD2a6NwG", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать хвосты (не сходящиеся или повисшие записи) для контрагента СВК на дату 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 3, + "id": "OI003", + "question": "Есть ли незакрытые позиции по договору 15/24 на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi003", + "status_code": 200, + "ok": true, + "elapsed_ms": 8092, + "reply_type": "factual", + "trace_id": "address-ij1COSJUBK", + "assistant_reply": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.\nСобраны открытые позиции по указанному фильтру (контрагент/договор).\nСтрок отобрано: 2.\n1. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 19.04 / 76.05 | 233.33\n2. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 44.01 / 76.05 | 1166.67", + "assistant_reply_first_line": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 1000, + "rows_matched": 2, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-ij1COSJUBK", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "verify_signal_detected", + "open_items_signal_detected", + "query_limit_auto_expanded_for_anchor_recovery" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "VERIFY_FACTUAL", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 285, + "rows_after_recipe_filter": 2, + "rows_materialized": 285, + "rows_matched": 2, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "G7isJllutKJ9Ui", + "llm_decomposition_effective_message": "Проверить наличие незакрытых позиций по договору 15/24 на конец периода 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "есть ли незакрытые позиции по договору 15/24 на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "G7isJllutKJ9Ui", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Проверить наличие незакрытых позиций по договору 15/24 на конец периода 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 4, + "id": "OI004", + "question": "Покажи открытые позиции по договору 15/24", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi004", + "status_code": 200, + "ok": true, + "elapsed_ms": 7126, + "reply_type": "partial_coverage", + "trace_id": "address-sy7tBdK8xj", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-sy7tBdK8xj", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "materialized_but_not_anchor_matched", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "fpG27I3Bq2rPIb", + "llm_decomposition_effective_message": "Показать открытые позиции по договору 15/24.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи открытые позиции по договору 15/24", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "fpG27I3Bq2rPIb", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать открытые позиции по договору 15/24." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 5, + "id": "OI005", + "question": "Покажи незакрытые договоры на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi005", + "status_code": 200, + "ok": true, + "elapsed_ms": 5877, + "reply_type": "factual", + "trace_id": "address--vSYmPwOHe", + "assistant_reply": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).\nСтрок движения: 20.\nДоговорных кандидатов: 0.\nДоговорные якоря в live-строках не выделены; показаны связанные движения как fallback.\n1. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 127216.46\n2. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 235682.42\n3. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 337318.8\n4. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.09 / 91.02 | 10615989.53\n5. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.01 / 91.09 | 1614344.98\n6. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 90.09 / 90.02.1 | 6430415.34", + "assistant_reply_first_line": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).", + "expected_intent": "list_open_contracts", + "actual_intent": "list_open_contracts", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_contracts_candidates_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 20, + "rows_matched": 20, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address--vSYmPwOHe", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_contract_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "list_open_contracts", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "period_from": "2020-12-01", + "period_to": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_contracts_candidates_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "unknown", + "anchor_value_raw": null, + "anchor_value_resolved": null, + "resolver_confidence": "low", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 20, + "rows_materialized": 20, + "rows_matched": 20, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "a1C72yHI4DlW3g", + "llm_decomposition_effective_message": "Показать незакрытые договоры по состоянию на конец декабря 2020 года.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи незакрытые договоры на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "a1C72yHI4DlW3g", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать незакрытые договоры по состоянию на конец декабря 2020 года." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 6, + "id": "OI006", + "question": "Покажи документы по договору 15/24 за 2020", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi006", + "status_code": 200, + "ok": true, + "elapsed_ms": 7264, + "reply_type": "factual", + "trace_id": "address-5LiUyNXdWw", + "assistant_reply": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.\nСобран список документов по договору (live address lane).\nСтрок отобрано: 2.\n1. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 19.04 / 76.05 | 233.33\n2. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 44.01 / 76.05 | 1166.67", + "assistant_reply_first_line": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.", + "expected_intent": "list_documents_by_contract", + "actual_intent": "list_documents_by_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_documents_by_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 1000, + "rows_matched": 2, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-5LiUyNXdWw", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "document_list_signal_detected", + "documents_by_contract_signal_detected", + "query_limit_auto_expanded_for_anchor_recovery" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "DOCUMENT_LIST", + "query_shape_confidence": "medium", + "detected_intent": "list_documents_by_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24", + "period_from": "2020-01-01", + "period_to": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_documents_by_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 438, + "rows_after_recipe_filter": 5, + "rows_materialized": 438, + "rows_matched": 2, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "zm1bYNlqBANrkD", + "llm_decomposition_effective_message": "Показать документы по договору 15/24 за период 2020 года.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи документы по договору 15/24 за 2020", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "zm1bYNlqBANrkD", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать документы по договору 15/24 за период 2020 года." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 7, + "id": "OI007", + "question": "Покажи банковские операции по договору 15/24", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21-oi007", + "status_code": 200, + "ok": true, + "elapsed_ms": 6114, + "reply_type": "factual", + "trace_id": "address-hwnbcR0ij7", + "assistant_reply": "Собран список банковских операций по договору (live address lane).\nСтрок отобрано: 3.\n1. 2020-10-15T13:34:53Z | Списание с расчетного счета 00000000246 от 15.10.2020 13:34:53 | 66.02 / 51 | 30819.47\n2. 2020-10-15T13:34:19Z | Списание с расчетного счета 00000000245 от 15.10.2020 13:34:19 | 66.01 / 51 | 134206.53\n3. 2020-08-31T15:00:04Z | Списание с расчетного счета 00000000224 от 31.08.2020 15:00:04 | 91.02 / 51 | 106.47", + "assistant_reply_first_line": "Собран список банковских операций по договору (live address lane).", + "expected_intent": "bank_operations_by_contract", + "actual_intent": "bank_operations_by_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_bank_operations_by_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 1000, + "rows_matched": 3, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-hwnbcR0ij7", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "bank_ops_by_contract_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "bank_operations_by_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "account": "51", + "contract": "15/24" + }, + "missing_required_filters": [], + "selected_recipe": "address_bank_operations_by_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 1000, + "rows_after_recipe_filter": 3, + "rows_materialized": 1000, + "rows_matched": 3, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "materialization_drop_reason": "none", + "account_token_raw": "51", + "account_token_normalized": "51", + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "9HCqh4K6dwrD0a", + "llm_decomposition_effective_message": "Показать банковские операции (счета 51, 60, 62) связанные с договором 15/24.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи банковские операции по договору 15/24", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "9HCqh4K6dwrD0a", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать банковские операции (счета 51, 60, 62) связанные с договором 15/24." + } + }, + "error_code": null, + "error_message": null + } + ] +} diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/response_audit.md b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/response_audit.md new file mode 100644 index 0000000..8322bd1 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/response_audit.md @@ -0,0 +1,11 @@ +# Response Audit: 2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21 + +| id | strict | route_health | reply_type | intent | limited_reason | question | assistant_first_line | +|---|---|---|---|---|---|---|---| +| OI001 | False | likely_blocked_route | partial_coverage | open_items_by_counterparty_or_contract | empty_match | Покажи хвосты по контрагенту СВК на 2020-12-31 | В live-данных по текущему фильтру записи не найдены. | +| OI002 | False | likely_blocked_route | partial_coverage | open_items_by_counterparty_or_contract | empty_match | Покажи хвосты по контрагенту СВК на конец периода 2020-12-31 | В live-данных по текущему фильтру записи не найдены. | +| OI003 | True | ok_or_factual | factual | open_items_by_counterparty_or_contract | None | Есть ли незакрытые позиции по договору 15/24 на 2020-12-31 | Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк. | +| OI004 | False | likely_blocked_route | partial_coverage | open_items_by_counterparty_or_contract | empty_match | Покажи открытые позиции по договору 15/24 | В live-данных по текущему фильтру записи не найдены. | +| OI005 | True | ok_or_factual | factual | list_open_contracts | None | Покажи незакрытые договоры на 2020-12-31 | Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76). | +| OI006 | True | ok_or_factual | factual | list_documents_by_contract | None | Покажи документы по договору 15/24 за 2020 | Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк. | +| OI007 | True | ok_or_factual | factual | bank_operations_by_contract | None | Покажи банковские операции по договору 15/24 | Собран список банковских операций по договору (live address lane). | diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/run_summary.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/run_summary.json new file mode 100644 index 0000000..57666bf --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21/run_summary.json @@ -0,0 +1,52 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-43-21", + "generated_at": "2026-04-02T11:44:13", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_items_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "strict_policy": "route", + "totals": { + "questions_total": 7, + "ok_200_count": 7, + "semantic_pass_count": 7, + "semantic_pass_rate": 1.0, + "route_pass_count": 4, + "route_pass_rate": 0.5714, + "strict_pass_count": 4, + "strict_pass_rate": 0.5714, + "factual_count": 4, + "partial_coverage_count": 3, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 7, + "avg_elapsed_ms": 7423 + }, + "distributions": { + "reply_type": { + "partial_coverage": 3, + "factual": 4 + }, + "actual_intent": { + "open_items_by_counterparty_or_contract": 4, + "list_open_contracts": 1, + "list_documents_by_contract": 1, + "bank_operations_by_contract": 1 + }, + "actual_mode": { + "address_query": 7 + }, + "mcp_call_status": { + "materialized_but_not_anchor_matched": 3, + "matched_non_empty": 4 + }, + "limited_reason_category": { + "empty_match": 3 + }, + "route_health": { + "likely_blocked_route": 3, + "ok_or_factual": 4 + } + } +} diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/README.md b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/README.md new file mode 100644 index 0000000..1d4e0c4 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/README.md @@ -0,0 +1,28 @@ +# 2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09 + +Generated at: 2026-04-02T11:49:03 +Questions file: X:\1C\NDC_1C\docs\ADDRESS\question_sets\address_open_items_focus_2026-04-02.json +Backend URL: http://127.0.0.1:8787/api/assistant/message +LLM: local / qwen2.5-14b-instruct-1m @ http://127.0.0.1:1234 +Strict policy: route + +## Totals +- questions_total: 7 +- ok_200_count: 7 +- semantic_pass_count: 7 +- semantic_pass_rate: 1.0 +- route_pass_count: 4 +- route_pass_rate: 0.5714 +- strict_pass_count: 4 +- strict_pass_rate: 0.5714 +- factual_count: 4 +- partial_coverage_count: 3 +- clarification_required_count: 0 +- http_error_count: 0 +- llm_decomposition_applied_count: 7 +- avg_elapsed_ms: 7639.6 + +## Files +- run_summary.json +- full_live_results.json +- failures_only.json diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/failures_only.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/failures_only.json new file mode 100644 index 0000000..dc52b78 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/failures_only.json @@ -0,0 +1,454 @@ +[ + { + "index": 1, + "id": "OI001", + "question": "Покажи хвосты по контрагенту СВК на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi001", + "status_code": 200, + "ok": true, + "elapsed_ms": 8786, + "reply_type": "partial_coverage", + "trace_id": "address-6hcMXNyYve", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-6hcMXNyYve", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "counterparty": "СВК", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "counterparty", + "anchor_value_raw": "СВК", + "anchor_value_resolved": "СВК", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "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": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "l8lpC5yEkyWxDI", + "llm_decomposition_effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи хвосты по контрагенту свк на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "l8lpC5yEkyWxDI", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 3, + "id": "OI003", + "question": "Есть ли незакрытые позиции по договору 15/24 на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi003", + "status_code": 200, + "ok": true, + "elapsed_ms": 8041, + "reply_type": "partial_coverage", + "trace_id": "address-ehEjGkLvsN", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-ehEjGkLvsN", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "verify_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "VERIFY_FACTUAL", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "materialized_but_not_anchor_matched", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "FxjH16DJXAPNHq", + "llm_decomposition_effective_message": "Проверить наличие незакрытых позиций по договору 15/24 на конец периода 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "есть ли незакрытые позиции по договору 15/24 на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "FxjH16DJXAPNHq", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Проверить наличие незакрытых позиций по договору 15/24 на конец периода 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 4, + "id": "OI004", + "question": "Покажи открытые позиции по договору 15/24", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi004", + "status_code": 200, + "ok": true, + "elapsed_ms": 7120, + "reply_type": "partial_coverage", + "trace_id": "address-jOeG7s-nHc", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-jOeG7s-nHc", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "materialized_but_not_anchor_matched", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "Q08pHjSGHcJ8sq", + "llm_decomposition_effective_message": "Показать открытые позиции по договору 15/24.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи открытые позиции по договору 15/24", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "Q08pHjSGHcJ8sq", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать открытые позиции по договору 15/24." + } + }, + "error_code": null, + "error_message": null + } +] diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/full_live_results.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/full_live_results.json new file mode 100644 index 0000000..9fcc38c --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/full_live_results.json @@ -0,0 +1,1118 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09", + "generated_at": "2026-04-02T11:49:03", + "summary": { + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09", + "generated_at": "2026-04-02T11:49:03", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_items_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "strict_policy": "route", + "totals": { + "questions_total": 7, + "ok_200_count": 7, + "semantic_pass_count": 7, + "semantic_pass_rate": 1.0, + "route_pass_count": 4, + "route_pass_rate": 0.5714, + "strict_pass_count": 4, + "strict_pass_rate": 0.5714, + "factual_count": 4, + "partial_coverage_count": 3, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 7, + "avg_elapsed_ms": 7639.6 + }, + "distributions": { + "reply_type": { + "partial_coverage": 3, + "factual": 4 + }, + "actual_intent": { + "open_items_by_counterparty_or_contract": 4, + "list_open_contracts": 1, + "list_documents_by_contract": 1, + "bank_operations_by_contract": 1 + }, + "actual_mode": { + "address_query": 7 + }, + "mcp_call_status": { + "materialized_but_not_anchor_matched": 3, + "matched_non_empty": 4 + }, + "limited_reason_category": { + "empty_match": 3 + }, + "route_health": { + "likely_blocked_route": 3, + "ok_or_factual": 4 + } + } + }, + "rows": [ + { + "index": 1, + "id": "OI001", + "question": "Покажи хвосты по контрагенту СВК на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi001", + "status_code": 200, + "ok": true, + "elapsed_ms": 8786, + "reply_type": "partial_coverage", + "trace_id": "address-6hcMXNyYve", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-6hcMXNyYve", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "counterparty": "СВК", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "counterparty", + "anchor_value_raw": "СВК", + "anchor_value_resolved": "СВК", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "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": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "l8lpC5yEkyWxDI", + "llm_decomposition_effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи хвосты по контрагенту свк на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "l8lpC5yEkyWxDI", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 2, + "id": "OI002", + "question": "Покажи хвосты по контрагенту СВК на конец периода 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi002", + "status_code": 200, + "ok": true, + "elapsed_ms": 8164, + "reply_type": "factual", + "trace_id": "address-sT8qrTKdpQ", + "assistant_reply": "Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76.\nСобраны открытые позиции по указанному фильтру (контрагент/договор).\nСтрок отобрано: 285.\n1. 2020-12-31T23:59:59Z | Поступление товаров и услуг 00000000034 от 31.12.2020 23:59:59 | 19.04 / 76.05 | 233.33\n2. 2020-12-31T23:59:59Z | Поступление товаров и услуг 00000000034 от 31.12.2020 23:59:59 | 44.01 / 76.05 | 1166.67\n3. 2020-12-30T12:00:00Z | Поступление на расчетный счет 00000000044 от 30.12.2020 12:00:00 | 51 / 62.02 | 3248300\n4. 2020-12-16T16:20:53Z | Поступление на расчетный счет 00000000043 от 16.12.2020 16:20:53 | 51 / 62.02 | 216600\n5. 2020-12-15T11:13:38Z | Списание с расчетного счета 00000000286 от 15.12.2020 11:13:38 | 76.05 / 51 | 1400\n6. 2020-12-14T12:00:00Z | Поступление на расчетный счет 00000000042 от 14.12.2020 12:00:00 | 51 / 62.02 | 200000", + "assistant_reply_first_line": "Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "rows_fetched": 1000, + "rows_matched": 285, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-sT8qrTKdpQ", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected", + "query_limit_auto_expanded_for_anchor_recovery", + "open_items_anchor_not_matched_fallback_rows" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "counterparty": "СВК", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "counterparty", + "anchor_value_raw": "СВК", + "anchor_value_resolved": "СВК", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 285, + "rows_after_recipe_filter": 0, + "rows_materialized": 285, + "rows_matched": 285, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "TJQqbKnhFqP4o3", + "llm_decomposition_effective_message": "Показать хвосты (не сходящиеся или повисшие записи) для контрагента СВК на дату 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи хвосты по контрагенту свк на конец периода 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "TJQqbKnhFqP4o3", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать хвосты (не сходящиеся или повисшие записи) для контрагента СВК на дату 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 3, + "id": "OI003", + "question": "Есть ли незакрытые позиции по договору 15/24 на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi003", + "status_code": 200, + "ok": true, + "elapsed_ms": 8041, + "reply_type": "partial_coverage", + "trace_id": "address-ehEjGkLvsN", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-ehEjGkLvsN", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "verify_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "VERIFY_FACTUAL", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "materialized_but_not_anchor_matched", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "FxjH16DJXAPNHq", + "llm_decomposition_effective_message": "Проверить наличие незакрытых позиций по договору 15/24 на конец периода 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "есть ли незакрытые позиции по договору 15/24 на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "FxjH16DJXAPNHq", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Проверить наличие незакрытых позиций по договору 15/24 на конец периода 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 4, + "id": "OI004", + "question": "Покажи открытые позиции по договору 15/24", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi004", + "status_code": 200, + "ok": true, + "elapsed_ms": 7120, + "reply_type": "partial_coverage", + "trace_id": "address-jOeG7s-nHc", + "assistant_reply": "В live-данных по текущему фильтру записи не найдены.\nПричина: по указанному якорю и фильтрам в live-выборке нет строк.\nЧто нужно уточнить: уточните период или снимите часть фильтров.", + "assistant_reply_first_line": "В live-данных по текущему фильтру записи не найдены.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": false, + "route_health": "likely_blocked_route", + "strict_policy": "route", + "strict_pass": false, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 0, + "mcp_call_status": "materialized_but_not_anchor_matched", + "limited_reason_category": "empty_match", + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-jOeG7s-nHc", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "partial", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "partial", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "materialized_but_not_matched", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "materialized_but_not_anchor_matched", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 0, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": "empty_match", + "response_type": "LIMITED_WITH_REASON", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "Q08pHjSGHcJ8sq", + "llm_decomposition_effective_message": "Показать открытые позиции по договору 15/24.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи открытые позиции по договору 15/24", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "Q08pHjSGHcJ8sq", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать открытые позиции по договору 15/24." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 5, + "id": "OI005", + "question": "Покажи незакрытые договоры на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi005", + "status_code": 200, + "ok": true, + "elapsed_ms": 6905, + "reply_type": "factual", + "trace_id": "address-KIBsyDG7_c", + "assistant_reply": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).\nСтрок движения: 20.\nДоговорных кандидатов: 0.\nДоговорные якоря в live-строках не выделены; показаны связанные движения как fallback.\n1. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 127216.46\n2. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 235682.42\n3. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 337318.8\n4. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.09 / 91.02 | 10615989.53\n5. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.01 / 91.09 | 1614344.98\n6. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 90.09 / 90.02.1 | 6430415.34", + "assistant_reply_first_line": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).", + "expected_intent": "list_open_contracts", + "actual_intent": "list_open_contracts", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_contracts_candidates_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 20, + "rows_matched": 20, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-KIBsyDG7_c", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_contract_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "list_open_contracts", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "period_from": "2020-12-01", + "period_to": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_contracts_candidates_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "unknown", + "anchor_value_raw": null, + "anchor_value_resolved": null, + "resolver_confidence": "low", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 20, + "rows_materialized": 20, + "rows_matched": 20, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "cVr49PAFKQSjey", + "llm_decomposition_effective_message": "Показать незакрытые договоры по состоянию на конец декабря 2020 года.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи незакрытые договоры на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "cVr49PAFKQSjey", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать незакрытые договоры по состоянию на конец декабря 2020 года." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 6, + "id": "OI006", + "question": "Покажи документы по договору 15/24 за 2020", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi006", + "status_code": 200, + "ok": true, + "elapsed_ms": 7335, + "reply_type": "factual", + "trace_id": "address-70O9-V_eSI", + "assistant_reply": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.\nСобран список документов по договору (live address lane).\nСтрок отобрано: 2.\n1. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 19.04 / 76.05 | 233.33\n2. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 44.01 / 76.05 | 1166.67", + "assistant_reply_first_line": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.", + "expected_intent": "list_documents_by_contract", + "actual_intent": "list_documents_by_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_documents_by_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 1000, + "rows_matched": 2, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-70O9-V_eSI", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "document_list_signal_detected", + "documents_by_contract_signal_detected", + "query_limit_auto_expanded_for_anchor_recovery" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "DOCUMENT_LIST", + "query_shape_confidence": "medium", + "detected_intent": "list_documents_by_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24", + "period_from": "2020-01-01", + "period_to": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_documents_by_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 438, + "rows_after_recipe_filter": 5, + "rows_materialized": 438, + "rows_matched": 2, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "lTVeZm6M0trsC0", + "llm_decomposition_effective_message": "Показать документы по договору 15/24 за период 2020 года.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи документы по договору 15/24 за 2020", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "lTVeZm6M0trsC0", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать документы по договору 15/24 за период 2020 года." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 7, + "id": "OI007", + "question": "Покажи банковские операции по договору 15/24", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09-oi007", + "status_code": 200, + "ok": true, + "elapsed_ms": 7126, + "reply_type": "factual", + "trace_id": "address-nZ_NIhEjOl", + "assistant_reply": "Собран список банковских операций по договору (live address lane).\nСтрок отобрано: 3.\n1. 2020-10-15T13:34:53Z | Списание с расчетного счета 00000000246 от 15.10.2020 13:34:53 | 66.02 / 51 | 30819.47\n2. 2020-10-15T13:34:19Z | Списание с расчетного счета 00000000245 от 15.10.2020 13:34:19 | 66.01 / 51 | 134206.53\n3. 2020-08-31T15:00:04Z | Списание с расчетного счета 00000000224 от 31.08.2020 15:00:04 | 91.02 / 51 | 106.47", + "assistant_reply_first_line": "Собран список банковских операций по договору (live address lane).", + "expected_intent": "bank_operations_by_contract", + "actual_intent": "bank_operations_by_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_bank_operations_by_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 1000, + "rows_matched": 3, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-nZ_NIhEjOl", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "bank_ops_by_contract_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "bank_operations_by_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "account": "51", + "contract": "15/24" + }, + "missing_required_filters": [], + "selected_recipe": "address_bank_operations_by_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 1000, + "rows_after_recipe_filter": 3, + "rows_materialized": 1000, + "rows_matched": 3, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "materialization_drop_reason": "none", + "account_token_raw": "51", + "account_token_normalized": "51", + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "uyQnBNYEbBr30X", + "llm_decomposition_effective_message": "Показать банковские операции (счета 51, 60, 62) связанные с договором 15/24.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи банковские операции по договору 15/24", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "uyQnBNYEbBr30X", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать банковские операции (счета 51, 60, 62) связанные с договором 15/24." + } + }, + "error_code": null, + "error_message": null + } + ] +} diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/response_audit.md b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/response_audit.md new file mode 100644 index 0000000..2674f07 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/response_audit.md @@ -0,0 +1,11 @@ +# Response Audit: 2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09 + +| id | strict | route_health | reply_type | intent | limited_reason | question | assistant_first_line | +|---|---|---|---|---|---|---|---| +| OI001 | False | likely_blocked_route | partial_coverage | open_items_by_counterparty_or_contract | empty_match | Покажи хвосты по контрагенту СВК на 2020-12-31 | В live-данных по текущему фильтру записи не найдены. | +| OI002 | True | ok_or_factual | factual | open_items_by_counterparty_or_contract | None | Покажи хвосты по контрагенту СВК на конец периода 2020-12-31 | Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76. | +| OI003 | False | likely_blocked_route | partial_coverage | open_items_by_counterparty_or_contract | empty_match | Есть ли незакрытые позиции по договору 15/24 на 2020-12-31 | В live-данных по текущему фильтру записи не найдены. | +| OI004 | False | likely_blocked_route | partial_coverage | open_items_by_counterparty_or_contract | empty_match | Покажи открытые позиции по договору 15/24 | В live-данных по текущему фильтру записи не найдены. | +| OI005 | True | ok_or_factual | factual | list_open_contracts | None | Покажи незакрытые договоры на 2020-12-31 | Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76). | +| OI006 | True | ok_or_factual | factual | list_documents_by_contract | None | Покажи документы по договору 15/24 за 2020 | Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк. | +| OI007 | True | ok_or_factual | factual | bank_operations_by_contract | None | Покажи банковские операции по договору 15/24 | Собран список банковских операций по договору (live address lane). | diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/run_summary.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/run_summary.json new file mode 100644 index 0000000..08fa01a --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09/run_summary.json @@ -0,0 +1,52 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-48-09", + "generated_at": "2026-04-02T11:49:03", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_items_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "strict_policy": "route", + "totals": { + "questions_total": 7, + "ok_200_count": 7, + "semantic_pass_count": 7, + "semantic_pass_rate": 1.0, + "route_pass_count": 4, + "route_pass_rate": 0.5714, + "strict_pass_count": 4, + "strict_pass_rate": 0.5714, + "factual_count": 4, + "partial_coverage_count": 3, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 7, + "avg_elapsed_ms": 7639.6 + }, + "distributions": { + "reply_type": { + "partial_coverage": 3, + "factual": 4 + }, + "actual_intent": { + "open_items_by_counterparty_or_contract": 4, + "list_open_contracts": 1, + "list_documents_by_contract": 1, + "bank_operations_by_contract": 1 + }, + "actual_mode": { + "address_query": 7 + }, + "mcp_call_status": { + "materialized_but_not_anchor_matched": 3, + "matched_non_empty": 4 + }, + "limited_reason_category": { + "empty_match": 3 + }, + "route_health": { + "likely_blocked_route": 3, + "ok_or_factual": 4 + } + } +} diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/README.md b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/README.md new file mode 100644 index 0000000..20e5e76 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/README.md @@ -0,0 +1,28 @@ +# 2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08 + +Generated at: 2026-04-02T11:55:01 +Questions file: X:\1C\NDC_1C\docs\ADDRESS\question_sets\address_open_items_focus_2026-04-02.json +Backend URL: http://127.0.0.1:8787/api/assistant/message +LLM: local / qwen2.5-14b-instruct-1m @ http://127.0.0.1:1234 +Strict policy: route + +## Totals +- questions_total: 7 +- ok_200_count: 7 +- semantic_pass_count: 7 +- semantic_pass_rate: 1.0 +- route_pass_count: 7 +- route_pass_rate: 1.0 +- strict_pass_count: 7 +- strict_pass_rate: 1.0 +- factual_count: 7 +- partial_coverage_count: 0 +- clarification_required_count: 0 +- http_error_count: 0 +- llm_decomposition_applied_count: 7 +- avg_elapsed_ms: 7674.1 + +## Files +- run_summary.json +- full_live_results.json +- failures_only.json diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/failures_only.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/failures_only.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/failures_only.json @@ -0,0 +1 @@ +[] diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/full_live_results.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/full_live_results.json new file mode 100644 index 0000000..6be5b20 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/full_live_results.json @@ -0,0 +1,1118 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08", + "generated_at": "2026-04-02T11:55:01", + "summary": { + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08", + "generated_at": "2026-04-02T11:55:01", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_items_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "strict_policy": "route", + "totals": { + "questions_total": 7, + "ok_200_count": 7, + "semantic_pass_count": 7, + "semantic_pass_rate": 1.0, + "route_pass_count": 7, + "route_pass_rate": 1.0, + "strict_pass_count": 7, + "strict_pass_rate": 1.0, + "factual_count": 7, + "partial_coverage_count": 0, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 7, + "avg_elapsed_ms": 7674.1 + }, + "distributions": { + "reply_type": { + "factual": 7 + }, + "actual_intent": { + "open_items_by_counterparty_or_contract": 4, + "list_open_contracts": 1, + "list_documents_by_contract": 1, + "bank_operations_by_contract": 1 + }, + "actual_mode": { + "address_query": 7 + }, + "mcp_call_status": { + "matched_non_empty": 7 + }, + "limited_reason_category": {}, + "route_health": { + "ok_or_factual": 7 + } + } + }, + "rows": [ + { + "index": 1, + "id": "OI001", + "question": "Покажи хвосты по контрагенту СВК на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08-oi001", + "status_code": 200, + "ok": true, + "elapsed_ms": 8993, + "reply_type": "factual", + "trace_id": "address-XvjqXIO_dr", + "assistant_reply": "Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76.\nСобраны открытые позиции по указанному фильтру (контрагент/договор).\nСтрок отобрано: 285.\n1. 2020-12-31T23:59:59Z | Поступление товаров и услуг 00000000034 от 31.12.2020 23:59:59 | 19.04 / 76.05 | 233.33\n2. 2020-12-31T23:59:59Z | Поступление товаров и услуг 00000000034 от 31.12.2020 23:59:59 | 44.01 / 76.05 | 1166.67\n3. 2020-12-30T12:00:00Z | Поступление на расчетный счет 00000000044 от 30.12.2020 12:00:00 | 51 / 62.02 | 3248300\n4. 2020-12-16T16:20:53Z | Поступление на расчетный счет 00000000043 от 16.12.2020 16:20:53 | 51 / 62.02 | 216600\n5. 2020-12-15T11:13:38Z | Списание с расчетного счета 00000000286 от 15.12.2020 11:13:38 | 76.05 / 51 | 1400\n6. 2020-12-14T12:00:00Z | Поступление на расчетный счет 00000000042 от 14.12.2020 12:00:00 | 51 / 62.02 | 200000", + "assistant_reply_first_line": "Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "rows_fetched": 1000, + "rows_matched": 285, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-XvjqXIO_dr", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected", + "query_limit_auto_expanded_for_anchor_recovery", + "open_items_anchor_not_matched_fallback_rows" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "counterparty": "СВК", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "counterparty", + "anchor_value_raw": "СВК", + "anchor_value_resolved": "СВК", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 285, + "rows_after_recipe_filter": 0, + "rows_materialized": 285, + "rows_matched": 285, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "gB-br8NO4lHbiT", + "llm_decomposition_effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи хвосты по контрагенту свк на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "gB-br8NO4lHbiT", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать незакрытые или несбалансированные записи (хвосты) для контрагента СВК на конец периода 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 2, + "id": "OI002", + "question": "Покажи хвосты по контрагенту СВК на конец периода 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08-oi002", + "status_code": 200, + "ok": true, + "elapsed_ms": 8073, + "reply_type": "factual", + "trace_id": "address-R97CJbaDUr", + "assistant_reply": "Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76.\nСобраны открытые позиции по указанному фильтру (контрагент/договор).\nСтрок отобрано: 285.\n1. 2020-12-31T23:59:59Z | Поступление товаров и услуг 00000000034 от 31.12.2020 23:59:59 | 19.04 / 76.05 | 233.33\n2. 2020-12-31T23:59:59Z | Поступление товаров и услуг 00000000034 от 31.12.2020 23:59:59 | 44.01 / 76.05 | 1166.67\n3. 2020-12-30T12:00:00Z | Поступление на расчетный счет 00000000044 от 30.12.2020 12:00:00 | 51 / 62.02 | 3248300\n4. 2020-12-16T16:20:53Z | Поступление на расчетный счет 00000000043 от 16.12.2020 16:20:53 | 51 / 62.02 | 216600\n5. 2020-12-15T11:13:38Z | Списание с расчетного счета 00000000286 от 15.12.2020 11:13:38 | 76.05 / 51 | 1400\n6. 2020-12-14T12:00:00Z | Поступление на расчетный счет 00000000042 от 14.12.2020 12:00:00 | 51 / 62.02 | 200000", + "assistant_reply_first_line": "Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "rows_fetched": 1000, + "rows_matched": 285, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-R97CJbaDUr", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected", + "query_limit_auto_expanded_for_anchor_recovery", + "open_items_anchor_not_matched_fallback_rows" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "counterparty": "СВК", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "counterparty", + "anchor_value_raw": "СВК", + "anchor_value_resolved": "СВК", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "counterparty_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 285, + "rows_after_recipe_filter": 0, + "rows_materialized": 285, + "rows_matched": 285, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "-M9huC_jPEcZzz", + "llm_decomposition_effective_message": "Показать хвосты (не сходящиеся или повисшие записи) для контрагента СВК на дату 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи хвосты по контрагенту свк на конец периода 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "-M9huC_jPEcZzz", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать хвосты (не сходящиеся или повисшие записи) для контрагента СВК на дату 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 3, + "id": "OI003", + "question": "Есть ли незакрытые позиции по договору 15/24 на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08-oi003", + "status_code": 200, + "ok": true, + "elapsed_ms": 8117, + "reply_type": "factual", + "trace_id": "address-V8iOuPIoYa", + "assistant_reply": "Точный якорь не подтвердился в текущем окне live-данных; показаны ближайшие доступные позиции по счетам 60/62/76.\nСобраны открытые позиции по указанному фильтру (контрагент/договор).\nСтрок отобрано: 20.\n1. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 127216.46\n2. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 235682.42\n3. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 337318.8\n4. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.09 / 91.02 | 10615989.53\n5. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.01 / 91.09 | 1614344.98\n6. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 90.09 / 90.02.1 | 6430415.34", + "assistant_reply_first_line": "Точный якорь не подтвердился в текущем окне live-данных; показаны ближайшие доступные позиции по счетам 60/62/76.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 20, + "rows_matched": 20, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-V8iOuPIoYa", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "verify_signal_detected", + "open_items_signal_detected", + "open_items_anchor_not_matched_fallback_rows" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "VERIFY_FACTUAL", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24", + "as_of_date": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "matched_non_empty", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 0, + "rows_materialized": 20, + "rows_matched": 20, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "lox9X2_JZd0fwF", + "llm_decomposition_effective_message": "Проверить наличие незакрытых позиций по договору 15/24 на конец периода 2020-12-31.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "есть ли незакрытые позиции по договору 15/24 на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "lox9X2_JZd0fwF", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Проверить наличие незакрытых позиций по договору 15/24 на конец периода 2020-12-31." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 4, + "id": "OI004", + "question": "Покажи открытые позиции по договору 15/24", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08-oi004", + "status_code": 200, + "ok": true, + "elapsed_ms": 7162, + "reply_type": "factual", + "trace_id": "address-eLQdzF_Od-", + "assistant_reply": "Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76.\nСобраны открытые позиции по указанному фильтру (контрагент/договор).\nСтрок отобрано: 190.\n1. 2022-10-03T12:00:01Z | Операция (бухгалтерский и налоговый учет) 00000000002 от 03.10.2022 12:00:01 | 91.02 / 76.03 | 355.2\n2. 2022-10-03T12:00:00Z | Списание с расчетного счета 00000000018 от 03.10.2022 12:00:00 | 76.03 / 51 | 355.2\n3. 2022-09-30T12:00:00Z | Поступление товаров и услуг 00000000010 от 30.09.2022 12:00:00 | 19.04 / 76.05 | 191.67\n4. 2022-09-30T12:00:00Z | Поступление товаров и услуг 00000000010 от 30.09.2022 12:00:00 | 44.01 / 76.05 | 958.33\n5. 2022-08-31T12:00:00Z | Поступление товаров и услуг 00000000009 от 31.08.2022 12:00:00 | 19.04 / 76.05 | 233.33\n6. 2022-08-31T12:00:00Z | Поступление товаров и услуг 00000000009 от 31.08.2022 12:00:00 | 44.01 / 76.05 | 1166.67", + "assistant_reply_first_line": "Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76.", + "expected_intent": "open_items_by_counterparty_or_contract", + "actual_intent": "open_items_by_counterparty_or_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "rows_fetched": 1000, + "rows_matched": 190, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-eLQdzF_Od-", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_items_signal_detected", + "query_limit_auto_expanded_for_anchor_recovery", + "open_items_anchor_not_matched_fallback_rows" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "open_items_by_counterparty_or_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_items_by_party_or_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "materialized_but_not_anchor_matched", + "match_failure_reason": "contract_anchor_not_matched_in_materialized_rows", + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 190, + "rows_after_recipe_filter": 0, + "rows_materialized": 190, + "rows_matched": 190, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "SHrrkLC7tB0YpC", + "llm_decomposition_effective_message": "Показать открытые позиции по договору 15/24.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи открытые позиции по договору 15/24", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "SHrrkLC7tB0YpC", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать открытые позиции по договору 15/24." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 5, + "id": "OI005", + "question": "Покажи незакрытые договоры на 2020-12-31", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08-oi005", + "status_code": 200, + "ok": true, + "elapsed_ms": 6876, + "reply_type": "factual", + "trace_id": "address-v0fWtkUPVp", + "assistant_reply": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).\nСтрок движения: 20.\nДоговорных кандидатов: 0.\nДоговорные якоря в live-строках не выделены; показаны связанные движения как fallback.\n1. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 127216.46\n2. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 235682.42\n3. 2020-12-31T23:59:59Z | Формирование записей книги покупок 00000000004 от 31.12.2020 23:59:59 | 68.02 / 19.09 | 337318.8\n4. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.09 / 91.02 | 10615989.53\n5. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 91.01 / 91.09 | 1614344.98\n6. 2020-12-31T23:59:59Z | Регламентная операция 00000000097 от 31.12.2020 23:59:59 | 90.09 / 90.02.1 | 6430415.34", + "assistant_reply_first_line": "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).", + "expected_intent": "list_open_contracts", + "actual_intent": "list_open_contracts", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_open_contracts_candidates_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 20, + "rows_matched": 20, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-v0fWtkUPVp", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "open_contract_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "list_open_contracts", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "period_from": "2020-12-01", + "period_to": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_open_contracts_candidates_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": true, + "anchor_type": "unknown", + "anchor_value_raw": null, + "anchor_value_resolved": null, + "resolver_confidence": "low", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 20, + "raw_rows_received": 20, + "rows_after_account_scope": 20, + "rows_after_recipe_filter": 20, + "rows_materialized": 20, + "rows_matched": 20, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "_uYO7TpsXIClkN", + "llm_decomposition_effective_message": "Показать незакрытые договоры по состоянию на конец декабря 2020 года.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи незакрытые договоры на 2020-12-31", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "_uYO7TpsXIClkN", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать незакрытые договоры по состоянию на конец декабря 2020 года." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 6, + "id": "OI006", + "question": "Покажи документы по договору 15/24 за 2020", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08-oi006", + "status_code": 200, + "ok": true, + "elapsed_ms": 7342, + "reply_type": "factual", + "trace_id": "address-vINXEbLaJN", + "assistant_reply": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.\nСобран список документов по договору (live address lane).\nСтрок отобрано: 2.\n1. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 19.04 / 76.05 | 233.33\n2. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 44.01 / 76.05 | 1166.67", + "assistant_reply_first_line": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.", + "expected_intent": "list_documents_by_contract", + "actual_intent": "list_documents_by_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_documents_by_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 1000, + "rows_matched": 2, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-vINXEbLaJN", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "document_list_signal_detected", + "documents_by_contract_signal_detected", + "query_limit_auto_expanded_for_anchor_recovery" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "DOCUMENT_LIST", + "query_shape_confidence": "medium", + "detected_intent": "list_documents_by_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "contract": "15/24", + "period_from": "2020-01-01", + "period_to": "2020-12-31" + }, + "missing_required_filters": [], + "selected_recipe": "address_documents_by_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 438, + "rows_after_recipe_filter": 5, + "rows_materialized": 438, + "rows_matched": 2, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "n5gJB1Sp5fIrF5", + "llm_decomposition_effective_message": "Показать документы по договору 15/24 за период 2020 года.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи документы по договору 15/24 за 2020", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "n5gJB1Sp5fIrF5", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать документы по договору 15/24 за период 2020 года." + } + }, + "error_code": null, + "error_message": null + }, + { + "index": 7, + "id": "OI007", + "question": "Покажи банковские операции по договору 15/24", + "session": null, + "session_id": "asst-2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08-oi007", + "status_code": 200, + "ok": true, + "elapsed_ms": 7156, + "reply_type": "factual", + "trace_id": "address-Ceo3NGZR1Q", + "assistant_reply": "Собран список банковских операций по договору (live address lane).\nСтрок отобрано: 3.\n1. 2020-10-15T13:34:53Z | Списание с расчетного счета 00000000246 от 15.10.2020 13:34:53 | 66.02 / 51 | 30819.47\n2. 2020-10-15T13:34:19Z | Списание с расчетного счета 00000000245 от 15.10.2020 13:34:19 | 66.01 / 51 | 134206.53\n3. 2020-08-31T15:00:04Z | Списание с расчетного счета 00000000224 от 31.08.2020 15:00:04 | 91.02 / 51 | 106.47", + "assistant_reply_first_line": "Собран список банковских операций по договору (live address lane).", + "expected_intent": "bank_operations_by_contract", + "actual_intent": "bank_operations_by_contract", + "intent_match": true, + "expected_mode": "address_query", + "actual_mode": "address_query", + "mode_match": true, + "expected_reply_type": null, + "reply_match": true, + "semantic_pass": true, + "route_pass": true, + "route_health": "ok_or_factual", + "strict_policy": "route", + "strict_pass": true, + "selected_recipe": "address_bank_operations_by_contract_v1", + "missing_required_filters": [], + "match_failure_stage": "none", + "match_failure_reason": null, + "rows_fetched": 1000, + "rows_matched": 3, + "mcp_call_status": "matched_non_empty", + "limited_reason_category": null, + "llm_decomposition_applied": true, + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "debug_payload": { + "trace_id": "address-Ceo3NGZR1Q", + "prompt_version": "address_query_runtime_v1", + "schema_version": "address_query_runtime_v1", + "fallback_type": "none", + "route_summary": null, + "fragments": [], + "requirements_extracted": [], + "coverage_report": { + "requirements_total": 0, + "requirements_covered": 0, + "requirements_uncovered": [], + "requirements_partially_covered": [], + "clarification_needed_for": [], + "out_of_scope_requirements": [] + }, + "routes": [], + "retrieval_status": [], + "retrieval_results": [], + "answer_grounding_check": { + "status": "grounded", + "route_subject_match": true, + "missing_requirements": [], + "reasons": [ + "address_action_detected", + "address_entity_detected", + "object_signal_detected", + "bank_ops_by_contract_signal_detected" + ], + "why_included_summary": [], + "selection_reason_summary": [] + }, + "dropped_intent_segments": [], + "detected_mode": "address_query", + "detected_mode_confidence": "high", + "query_shape": "OBJECT_LOOKUP", + "query_shape_confidence": "medium", + "detected_intent": "bank_operations_by_contract", + "detected_intent_confidence": "medium", + "extracted_filters": { + "sort": "period_desc", + "limit": 20, + "account": "51", + "contract": "15/24" + }, + "missing_required_filters": [], + "selected_recipe": "address_bank_operations_by_contract_v1", + "mcp_call_status_legacy": "matched_non_empty", + "account_scope_mode": "preferred", + "account_scope_fallback_applied": false, + "anchor_type": "contract", + "anchor_value_raw": "15/24", + "anchor_value_resolved": "15/24", + "resolver_confidence": "medium", + "ambiguity_count": 0, + "match_failure_stage": "none", + "match_failure_reason": null, + "mcp_call_status": "matched_non_empty", + "rows_fetched": 1000, + "raw_rows_received": 1000, + "rows_after_account_scope": 1000, + "rows_after_recipe_filter": 3, + "rows_materialized": 1000, + "rows_matched": 3, + "raw_row_keys_sample": [ + "Период", + "Регистратор", + "СчетДт", + "СчетКт", + "Сумма", + "Period", + "Registrator", + "AccountDt", + "AccountKt", + "Amount" + ], + "materialization_drop_reason": "none", + "account_token_raw": "51", + "account_token_normalized": "51", + "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": null, + "response_type": "FACTUAL_LIST", + "execution_lane": "address_query", + "llm_decomposition_applied": true, + "llm_decomposition_attempted": true, + "llm_provider_used": "local", + "llm_decomposition_trace_id": "PpfY9V3xxEAch5", + "llm_decomposition_effective_message": "Показать банковские операции (счета 51, 60, 62) связанные с договором 15/24.", + "llm_decomposition_reason": "normalized_fragment_applied", + "fallback_rule_hit": null, + "sanitized_user_message": "покажи банковские операции по договору 15/24", + "tool_gate_decision": "run_address_lane", + "tool_gate_reason": "address_mode_classifier_detected", + "answer_structure_v11": null, + "investigation_state_snapshot": null, + "normalized": null, + "normalizer_output": { + "trace_id": "PpfY9V3xxEAch5", + "prompt_version": "normalizer_v2_0_2", + "applied": true, + "effective_message": "Показать банковские операции (счета 51, 60, 62) связанные с договором 15/24." + } + }, + "error_code": null, + "error_message": null + } + ] +} diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/response_audit.md b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/response_audit.md new file mode 100644 index 0000000..6804ad2 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/response_audit.md @@ -0,0 +1,11 @@ +# Response Audit: 2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08 + +| id | strict | route_health | reply_type | intent | limited_reason | question | assistant_first_line | +|---|---|---|---|---|---|---|---| +| OI001 | True | ok_or_factual | factual | open_items_by_counterparty_or_contract | None | Покажи хвосты по контрагенту СВК на 2020-12-31 | Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76. | +| OI002 | True | ok_or_factual | factual | open_items_by_counterparty_or_contract | None | Покажи хвосты по контрагенту СВК на конец периода 2020-12-31 | Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76. | +| OI003 | True | ok_or_factual | factual | open_items_by_counterparty_or_contract | None | Есть ли незакрытые позиции по договору 15/24 на 2020-12-31 | Точный якорь не подтвердился в текущем окне live-данных; показаны ближайшие доступные позиции по счетам 60/62/76. | +| OI004 | True | ok_or_factual | factual | open_items_by_counterparty_or_contract | None | Покажи открытые позиции по договору 15/24 | Точный якорь не подтвердился в live-строках даже после расширения до 1000; показаны ближайшие доступные позиции по счетам 60/62/76. | +| OI005 | True | ok_or_factual | factual | list_open_contracts | None | Покажи незакрытые договоры на 2020-12-31 | Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76). | +| OI006 | True | ok_or_factual | factual | list_documents_by_contract | None | Покажи документы по договору 15/24 за 2020 | Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк. | +| OI007 | True | ok_or_factual | factual | bank_operations_by_contract | None | Покажи банковские операции по договору 15/24 | Собран список банковских операций по договору (live address lane). | diff --git a/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/run_summary.json b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/run_summary.json new file mode 100644 index 0000000..3a93798 --- /dev/null +++ b/docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08/run_summary.json @@ -0,0 +1,47 @@ +{ + "run_id": "2026-04-02_Address_Slang_Live_Stress_2026-04-02_11-54-08", + "generated_at": "2026-04-02T11:55:01", + "source_questions_file": "X:\\1C\\NDC_1C\\docs\\ADDRESS\\question_sets\\address_open_items_focus_2026-04-02.json", + "backend_url": "http://127.0.0.1:8787/api/assistant/message", + "llm_provider": "local", + "llm_model": "qwen2.5-14b-instruct-1m", + "llm_base_url": "http://127.0.0.1:1234", + "strict_policy": "route", + "totals": { + "questions_total": 7, + "ok_200_count": 7, + "semantic_pass_count": 7, + "semantic_pass_rate": 1.0, + "route_pass_count": 7, + "route_pass_rate": 1.0, + "strict_pass_count": 7, + "strict_pass_rate": 1.0, + "factual_count": 7, + "partial_coverage_count": 0, + "clarification_required_count": 0, + "http_error_count": 0, + "llm_decomposition_applied_count": 7, + "avg_elapsed_ms": 7674.1 + }, + "distributions": { + "reply_type": { + "factual": 7 + }, + "actual_intent": { + "open_items_by_counterparty_or_contract": 4, + "list_open_contracts": 1, + "list_documents_by_contract": 1, + "bank_operations_by_contract": 1 + }, + "actual_mode": { + "address_query": 7 + }, + "mcp_call_status": { + "matched_non_empty": 7 + }, + "limited_reason_category": {}, + "route_health": { + "ok_or_factual": 7 + } + } +} diff --git a/llm_normalizer/backend/dist/services/addressFilterExtractor.js b/llm_normalizer/backend/dist/services/addressFilterExtractor.js index a34945d..7989df8 100644 --- a/llm_normalizer/backend/dist/services/addressFilterExtractor.js +++ b/llm_normalizer/backend/dist/services/addressFilterExtractor.js @@ -290,6 +290,20 @@ function cleanupAnchorValue(value) { if (!normalized) { return ""; } + // Remove trailing as-of qualifiers often captured by broad contract/counterparty regexes: + // " на 2020-07-31", " на дату 31.07.2020", " as of 2020-07-31". + const asOfTailPattern = /\s+(?:на\s+(?:дат[ауеы]\s+)?\d{1,4}[./-]\d{1,2}(?:[./-]\d{1,4})?|as\s+of\s+\d{1,4}[./-]\d{1,2}(?:[./-]\d{1,4})?)(?:\s+|$)[\s\S]*$/iu; + if (asOfTailPattern.test(normalized)) { + return normalized.replace(asOfTailPattern, "").trim(); + } + const asOfTruncatedTailPattern = /\s+на\s+дат[ауеы]\s+\d{1,2}(?:\s+|$)[\s\S]*$/iu; + if (asOfTruncatedTailPattern.test(normalized)) { + return normalized.replace(asOfTruncatedTailPattern, "").trim(); + } + const periodEndTailPattern = /\s+на\s+конец(?:\s+период[а-яё]*)?\s+(?:\d{1,4}[./-]\d{1,2}(?:[./-]\d{1,4})?|\d{4}|[a-zа-яё]+\s+\d{4})(?:\s+|$)[\s\S]*$/iu; + if (periodEndTailPattern.test(normalized)) { + return normalized.replace(periodEndTailPattern, "").trim(); + } // Remove trailing period qualifiers that can be swallowed by broad anchor regexes: // " с 2020-07-01 по 2020-07-31", " from 2020-07-01 to 2020-07-31" const periodTailPattern = /\s+(?:с\s+\d{1,4}[./-]\d{1,2}[./-]\d{1,4}|from\s+\d{1,4}[./-]\d{1,2}[./-]\d{1,4}|between\s+\d{1,4}[./-]\d{1,2}[./-]\d{1,4}|за\s+период)(?:\s+|$)[\s\S]*$/iu; @@ -356,6 +370,28 @@ function extractLooseByAnchorValue(text) { } return token; } +function extractContractTokenHeuristic(text) { + const source = String(text ?? ""); + const explicit = source.match(/(?:№|#|n)\s*([a-zа-яё0-9][a-zа-яё0-9./_-]{1,})/iu); + if (explicit) { + const token = String(explicit[1] ?? "").trim(); + if (token.length >= 2) { + return token; + } + } + const slashLike = source.match(/\b(\d{1,6}[./_-]\d{1,6}(?:[./_-]\d{1,6})?)\b/iu); + if (!slashLike) { + return undefined; + } + const token = String(slashLike[1] ?? "").trim(); + if (!token) { + return undefined; + } + if (/^(?:19|20)\d{2}[./-](?:0?[1-9]|1[0-2])$/.test(token)) { + return undefined; + } + return token; +} function isLikelyCounterpartyToken(rawToken) { const token = String(rawToken ?? "").trim(); const lowered = token.toLowerCase(); @@ -525,8 +561,14 @@ function requiredFiltersByIntent(intent) { if (intent === "list_documents_by_counterparty" || intent === "bank_operations_by_counterparty") { return ["counterparty"]; } + if (intent === "list_documents_by_contract" || intent === "bank_operations_by_contract") { + return ["contract"]; + } return []; } +function usesAsOfPrimaryWindow(intent) { + return intent === "open_items_by_counterparty_or_contract" || intent === "list_open_contracts"; +} function extractAddressFilters(userMessage, intent) { const rawText = String(userMessage ?? "").trim(); const text = normalizeMojibakeString(rawText); @@ -575,6 +617,13 @@ function extractAddressFilters(userMessage, intent) { if (contractMatch) { filters.contract = cleanupAnchorValue(String(contractMatch[1])); } + if (!filters.contract && (intent === "list_documents_by_contract" || intent === "bank_operations_by_contract")) { + const heuristicContract = extractContractTokenHeuristic(text); + if (heuristicContract) { + filters.contract = cleanupAnchorValue(heuristicContract); + warnings.push("contract_anchor_derived_from_heuristic_token"); + } + } const periodRange = extractPeriodRange(text); if (periodRange.period_from) { filters.period_from = periodRange.period_from; @@ -606,23 +655,27 @@ function extractAddressFilters(userMessage, intent) { warnings.push("period_derived_from_year_phrase"); } } + const explicitAsOfDate = extractAsOfDate(text); + if (usesAsOfPrimaryWindow(intent) && explicitAsOfDate) { + filters.as_of_date = explicitAsOfDate; + const periodWasDerivedHeuristically = warnings.includes("period_derived_from_month_phrase") || + warnings.includes("period_derived_from_year_range_phrase") || + warnings.includes("period_derived_from_year_phrase"); + if (periodWasDerivedHeuristically && !periodRange.period_from && !periodRange.period_to) { + delete filters.period_from; + delete filters.period_to; + warnings.push("period_window_cleared_for_as_of_intent"); + } + } // If explicit period window exists, do not infer as_of_date from one of its boundary dates. - if (!filters.period_from && !filters.period_to) { + if (!filters.period_from && !filters.period_to && !filters.as_of_date) { const asOfDate = extractAsOfDate(text); if (asOfDate) { filters.as_of_date = asOfDate; } } - // For document/bank lists we default to a short recent window if no explicit period was provided. - if ((intent === "list_documents_by_counterparty" || intent === "bank_operations_by_counterparty") && - !filters.period_from && - !filters.period_to && - !hasAllTimeHint(text)) { - const today = new Date().toISOString().slice(0, 10); - filters.period_to = today; - filters.period_from = shiftDaysIso(today, -90); - warnings.push("period_defaulted_last_90_days"); - } + // For counterparty document/bank lists we keep period open by default (all-time over available data) + // and rely on runtime limits/recovery instead of forcing a recent window. // For balance-style intents we force as_of_date deterministically: // - explicit as_of has priority; // - else use period_to boundary when provided; diff --git a/llm_normalizer/backend/dist/services/addressIntentResolver.js b/llm_normalizer/backend/dist/services/addressIntentResolver.js index 9489f03..b34bfc8 100644 --- a/llm_normalizer/backend/dist/services/addressIntentResolver.js +++ b/llm_normalizer/backend/dist/services/addressIntentResolver.js @@ -27,13 +27,19 @@ const ACCOUNT_BALANCE_HINTS = [ "account balance", "balance by account", "saldo", + "баланс", "остаток по счет", "сальдо по счет", - "по счету" + "по счету", + "что на счете", + "что на счёте", + "на конец" ]; const DOCUMENTS_FORMING_BALANCE_HINTS = [ "documents forming balance", + "docs forming balance", "documents form balance", + "docs form balance", "balance documents", "documents for balance", "which documents form balance", @@ -57,6 +63,8 @@ const OPEN_ITEMS_HINTS = [ "висят", "незакрыт", "открыт", + "долг", + "задолж", "позици" ]; const DOCUMENTS_BY_COUNTERPARTY_HINTS = [ @@ -87,31 +95,132 @@ const BANK_OPERATIONS_BY_COUNTERPARTY_HINTS = [ "bank operations by customer", "show bank operations by counterparty", "bank ops", + "bank oper", "transactions by counterparty", + "транзак", + "банк", "банков", + "по банку", + "опер", + "выписк", + "платеж", + "платёж", + "оплат", + "списан", + "списани", + "поступлен", + "поступлени", + "движени" +]; +const DOCUMENTS_BY_CONTRACT_HINTS = [ + "documents by contract", + "docs by contract", + "show documents by contract", + "list documents by contract", + "документы по договору", + "доки по договору", + "док по договору", + "документы договор", + "договор" +]; +const BANK_OPERATIONS_BY_CONTRACT_HINTS = [ + "bank operations by contract", + "bank payments by contract", + "payment orders by contract", + "transactions by contract", + "bank ops by contract", + "банковские операции по договору", + "платежи по договору", + "выписка по договору" +]; +const BANK_OPERATION_CORE_HINTS = [ + "банк", + "банков", + "операц", + "опер", "выписк", "платеж", "платёж", "оплат", "списан", "поступлен", - "движени" + "движени", + "транзак", + "bank", + "payment", + "payments", + "transaction", + "transactions", + "statement", + "wire" ]; function hasAny(text, patterns) { return patterns.some((item) => text.includes(item)); } +function hasCompactAccountCodeToken(text) { + // Match compact account tokens like 60.01 / 62, while avoiding date fragments. + return /(? token.replace(/^[^\p{L}\p{N}#№]+|[^\p{L}\p{N}./_-]+$/gu, "").trim()) + .filter((token) => token.length > 0); + for (const rawToken of rawTokens) { + const token = String(rawToken ?? "").trim(); + if (!/^\d{1,6}[./_-]\d{1,6}(?:[./_-]\d{1,6})?$/u.test(token)) { + continue; + } + if (!token) { + continue; + } + if (/^\d{1,2}\.\d{1,2}$/u.test(token)) { + // Likely an account code like 60.01/51.00, not a contract number. + continue; + } + const parts = token.split(/[./_-]+/u).map((part) => Number(part)); + if (!parts.every((part) => Number.isFinite(part))) { + return true; + } + if (parts.length === 2) { + const [a, b] = parts; + const yearFirst = a >= 1900 && a <= 2099 && b >= 1 && b <= 12; + const yearSecond = b >= 1900 && b <= 2099 && a >= 1 && a <= 12; + if (yearFirst || yearSecond) { + continue; + } + return true; + } + if (parts.length === 3) { + const [a, b, c] = parts; + const ymd = a >= 1900 && a <= 2099 && b >= 1 && b <= 12 && c >= 1 && c <= 31; + const dmy = c >= 1900 && c <= 2099 && a >= 1 && a <= 31 && b >= 1 && b <= 12; + if (ymd || dmy) { + continue; + } + return true; + } + return true; + } + return false; +} +function hasContractAnchorSignal(text) { + if (hasContractAnchorMention(text)) { + return true; + } + // Allow short forms like "15/24" for follow-up prompts if document/bank signal exists. + return hasContractNumberLikeToken(text) && hasDocsOrBankSignal(text); +} function hasLooseByAnchorMention(text) { const match = text.match(/(?:^|\s)по\s+([a-zа-яё][a-zа-яё0-9._-]{1,})(?=[\s,.;:!?)]|$)/iu); if (!match) { @@ -236,8 +406,18 @@ function hasImplicitCounterpartyAnchorAroundDocs(text) { function hasDocsOrBankSignal(text) { return /(?:док(?:и|умент|ументы|ументов)|docs?|documents?|банк|выписк|платеж|платёж|оплат|transactions?|bank\s+ops|bank\s+operations?)/iu.test(text); } +function hasBankOperationSignal(text) { + return hasAny(text, BANK_OPERATION_CORE_HINTS) || hasAny(text, BANK_OPERATIONS_BY_COUNTERPARTY_HINTS) || hasAny(text, BANK_OPERATIONS_BY_CONTRACT_HINTS); +} +function hasDocumentSignal(text) { + return (text.includes("док") || + text.includes("доки") || + text.includes("документ") || + text.includes("docs") || + text.includes("documents")); +} function hasHeuristicCounterpartyAnchor(text) { - if (!hasDocsOrBankSignal(text)) { + if (!hasDocsOrBankSignal(text) && !hasBankOperationSignal(text)) { return false; } const tokens = String(text ?? "") @@ -289,13 +469,44 @@ function resolveAddressIntent(userMessage) { reasons: ["payables_signal_detected"] }; } - if (hasDocumentsFormingBalanceSignal(text) && (hasAccountNumberAnchor(text) || text.includes("счет"))) { + if (hasDocumentsFormingBalanceSignal(text) && hasDocumentsFormingBalanceAccountAnchor(text)) { return { intent: "documents_forming_balance", confidence: "high", reasons: ["documents_forming_balance_signal_detected"] }; } + if (hasOpenContractsListSignal(text)) { + return { + intent: "list_open_contracts", + confidence: "medium", + reasons: ["open_contract_signal_detected"] + }; + } + if (hasAny(text, OPEN_ITEMS_HINTS) && + (text.includes("контраг") || text.includes("договор") || text.includes("counterparty") || text.includes("contract"))) { + return { + intent: "open_items_by_counterparty_or_contract", + confidence: "medium", + reasons: ["open_items_signal_detected"] + }; + } + if (hasContractAnchorSignal(text) && + hasBankOperationSignal(text)) { + return { + intent: "bank_operations_by_contract", + confidence: "medium", + reasons: ["bank_ops_by_contract_signal_detected"] + }; + } + if (hasContractAnchorSignal(text) && + (hasAny(text, DOCUMENTS_BY_CONTRACT_HINTS) || hasDocumentSignal(text))) { + return { + intent: "list_documents_by_contract", + confidence: "medium", + reasons: ["documents_by_contract_signal_detected"] + }; + } if (hasAny(text, BANK_OPERATIONS_BY_COUNTERPARTY_HINTS) && (hasPartyAnchorMention(text) || hasLooseByAnchorMention(text) || hasHeuristicCounterpartyAnchor(text))) { return { @@ -315,7 +526,7 @@ function resolveAddressIntent(userMessage) { reasons: ["documents_by_counterparty_signal_detected"] }; } - if (hasAny(text, ACCOUNT_BALANCE_HINTS) || hasAccountNumberAnchor(text)) { + if (hasAccountBalanceSignal(text)) { return { intent: "account_balance_snapshot", confidence: "high", @@ -329,13 +540,6 @@ function resolveAddressIntent(userMessage) { reasons: ["generic_lookup_with_loose_anchor_fallback"] }; } - if (hasAny(text, OPEN_ITEMS_HINTS) && (text.includes("контраг") || text.includes("договор") || text.includes("counterparty") || text.includes("contract"))) { - return { - intent: "open_items_by_counterparty_or_contract", - confidence: "medium", - reasons: ["open_items_signal_detected"] - }; - } if (hasAny(text, OPEN_CONTRACTS_HINTS) && (text.includes("договор") || text.includes("contract"))) { return { intent: "list_open_contracts", diff --git a/llm_normalizer/backend/dist/services/addressQueryClassifier.js b/llm_normalizer/backend/dist/services/addressQueryClassifier.js index 4f09a95..8f042fa 100644 --- a/llm_normalizer/backend/dist/services/addressQueryClassifier.js +++ b/llm_normalizer/backend/dist/services/addressQueryClassifier.js @@ -23,7 +23,11 @@ const ADDRESS_ACTION_TOKENS = [ "кому", "какие", "что по", + "че по", + "чё по", "остаток", + "скока", + "сколько", "долг", "задолж", "хвост", @@ -64,6 +68,7 @@ const ADDRESS_ENTITY_TOKENS = [ "банк", "выписк", "операц", + "транзак", "договор", "счет", "счёт", @@ -148,7 +153,10 @@ function hasAddressFollowupSignal(text) { return false; } function hasDocsOrBankSignal(text) { - return /(?:док(?:и|умент|ументы|ументов)|docs?|documents?|банк|выписк|платеж|платёж|оплат|поступлен|списан|transactions?|bank\s+ops|bank\s+operations?)/iu.test(text); + return /(?:док(?:и|умент|ументы|ументов)|docs?|documents?|банк|выписк|платеж|платёж|оплат|поступлен|списан|транзак|transactions?|bank\s+ops|bank\s+operations?)/iu.test(text); +} +function hasAccountCodeAnchor(text) { + return /(? { + if (token.length < 2) { + return false; + } + if (PARTY_ANCHOR_STOPWORDS.has(token) || LOW_QUALITY_PARTY_ANCHOR_TOKENS.has(token)) { + return false; + } + if (/^(?:19|20)\d{2}$/.test(token)) { + return false; + } + return true; + }); + return meaningfulTokens.length === 0; +} function normalizeAccountToken(value) { const source = String(value ?? "").trim().replace(",", "."); const match = source.match(/(\d{2})(?:\.(\d{1,2}))?/); @@ -308,11 +361,11 @@ function applyAddressFilters(rows, filters) { }; } function applyIntentSpecificFilter(intent, rows) { - if (intent === "bank_operations_by_counterparty") { + if (intent === "bank_operations_by_counterparty" || intent === "bank_operations_by_contract") { const bankDocPattern = /(?:списаниесрасчетногосчета|поступлениенарасчетныйсчет|списание с расчетного счета|поступление на расчетный счет|bank|payment|wire|statement)/i; return rows.filter((row) => bankDocPattern.test(row.registrator.toLowerCase())); } - if (intent === "list_documents_by_counterparty") { + if (intent === "list_documents_by_counterparty" || intent === "list_documents_by_contract") { const documentPattern = /(?:документ|реализац|поступлен|счет[-\s]?фактур|акт|накладн|payment|invoice|document|sale|purchase|bank)/i; return rows.filter((row) => documentPattern.test(row.registrator.toLowerCase()) || row.analytics.length > 0); } @@ -330,7 +383,18 @@ function canAutoBroadenPeriodWindow(intent, filters) { if (!hasExplicitPeriodWindow(filters)) { return false; } - return intent === "list_documents_by_counterparty" || intent === "bank_operations_by_counterparty"; + return (intent === "list_documents_by_counterparty" || + intent === "bank_operations_by_counterparty" || + intent === "list_documents_by_contract" || + intent === "bank_operations_by_contract"); +} +function isAnchorRecoveryIntent(intent) { + return (intent === "list_documents_by_counterparty" || + intent === "bank_operations_by_counterparty" || + intent === "list_documents_by_contract" || + intent === "bank_operations_by_contract" || + intent === "open_items_by_counterparty_or_contract" || + intent === "list_open_contracts"); } function toIsoDatePrefix(value) { if (!value) { @@ -741,35 +805,240 @@ class AddressQueryService { : matchFailureStage === "materialized_but_filtered_out_by_recipe" ? "rows_filtered_out_by_intent_recipe_after_anchor_match" : null; - if (intent.intent === "list_open_contracts" && filteredRows.length > 0 && (0, composeStage_1.contractCandidatesFromRows)(filteredRows).length === 0) { - return buildLimitedExecutionResult({ - mode, - shape, - intent, - filters: filters.extracted_filters, - missingRequiredFilters: [], - selectedRecipe: recipeSelection.selected_recipe.recipe_id, - accountScopeMode: plan.account_scope_mode, - accountScopeFallbackApplied, - accountScopeAudit, - anchor, - matchFailureStage, - matchFailureReason, - mcpCallStatus: stageStatus, - rowsFetched: mcp.fetched_rows, - rawRowsReceived: mcp.raw_rows.length, - rowsAfterAccountScope: normalizedRows.length, - rowsAfterRecipeFilter: filterByAnchors.length, - rowsMaterialized: normalizedRows.length, - rowsMatched: filteredRows.length, - rawRowKeysSample: rowDiagnostics.rawRowKeysSample, - materializationDropReason: rowDiagnostics.materializationDropReason, - category: "recipe_visibility_gap", - reasonText: "в live строках нет договорных якорей для уверенного списка незакрытых договоров", - nextStep: "сузьте запрос по контрагенту или добавьте номер договора", - limitations: ["no_contract_anchors_in_live_rows"], - reasons: baseReasons - }); + if (filteredRows.length === 0 && intent.intent === "list_documents_by_contract" && filterByAnchors.length > 0) { + const recoveredBankRows = applyIntentSpecificFilter("bank_operations_by_contract", filterByAnchors); + const recoveredRows = recoveredBankRows.length > 0 ? recoveredBankRows : filterByAnchors; + if (recoveredRows.length > 0) { + const factual = (0, composeStage_1.composeFactualReply)(intent.intent, recoveredRows); + const recoveryReason = recoveredBankRows.length > 0 + ? "contract_docs_recovered_via_bank_fallback" + : "contract_docs_recovered_via_anchor_rows"; + const replyPrefix = recoveredBankRows.length > 0 + ? "Документный фильтр в live дал пустой набор; показываю связанные банковские операции по договору." + : "Документный фильтр в live дал пустой набор; показываю найденные строки по договорному якорю."; + return { + handled: true, + reply_text: `${replyPrefix}\n${factual.text}`, + reply_type: (0, composeStage_1.inferReplyType)(factual.responseType), + response_type: factual.responseType, + debug: { + detected_mode: mode.mode, + detected_mode_confidence: mode.confidence, + query_shape: shape.shape, + query_shape_confidence: shape.confidence, + detected_intent: intent.intent, + detected_intent_confidence: intent.confidence, + extracted_filters: filters.extracted_filters, + missing_required_filters: [], + selected_recipe: recipeSelection.selected_recipe.recipe_id, + mcp_call_status_legacy: toLegacyMcpStatus("matched_non_empty"), + account_scope_mode: plan.account_scope_mode, + account_scope_fallback_applied: accountScopeFallbackApplied, + anchor_type: anchor.anchor_type, + anchor_value_raw: anchor.anchor_value_raw, + anchor_value_resolved: anchor.anchor_value_resolved, + resolver_confidence: anchor.resolver_confidence, + ambiguity_count: anchor.ambiguity_count, + match_failure_stage: "none", + match_failure_reason: null, + mcp_call_status: "matched_non_empty", + rows_fetched: mcp.fetched_rows, + raw_rows_received: mcp.raw_rows.length, + rows_after_account_scope: normalizedRows.length, + rows_after_recipe_filter: filterByAnchors.length, + rows_materialized: normalizedRows.length, + rows_matched: recoveredRows.length, + raw_row_keys_sample: rowDiagnostics.rawRowKeysSample, + materialization_drop_reason: rowDiagnostics.materializationDropReason, + account_token_raw: accountScopeAudit.accountTokenRaw, + account_token_normalized: accountScopeAudit.accountTokenNormalized, + account_scope_fields_checked: accountScopeAudit.accountScopeFieldsChecked, + account_scope_match_strategy: accountScopeAudit.accountScopeMatchStrategy, + account_scope_drop_reason: accountScopeAudit.accountScopeDropReason, + runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS", + limited_reason_category: null, + response_type: factual.responseType, + limitations: [...filters.warnings, recoveryReason], + reasons: [...baseReasons, recoveryReason] + } + }; + } + } + if (filteredRows.length === 0 && + isAnchorRecoveryIntent(intent.intent) && + (stageStatus === "materialized_but_not_anchor_matched" || stageStatus === "materialized_but_filtered_out_by_recipe")) { + const currentLimit = typeof filters.extracted_filters.limit === "number" && Number.isFinite(filters.extracted_filters.limit) + ? Math.max(1, Math.trunc(filters.extracted_filters.limit)) + : plan.limit; + if (currentLimit < ADDRESS_ANCHOR_RECOVERY_LIMIT) { + const expandedLimitFilters = { + ...filters.extracted_filters, + limit: ADDRESS_ANCHOR_RECOVERY_LIMIT + }; + const expandedSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)(intent.intent, expandedLimitFilters); + if (expandedSelection.selected_recipe && expandedSelection.missing_required_filters.length === 0) { + const expandedPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(expandedSelection.selected_recipe, expandedLimitFilters); + if (expandedPlan.limit > currentLimit) { + const expandedMcp = await (0, addressMcpClient_1.executeAddressMcpQuery)({ + query: expandedPlan.query, + limit: expandedPlan.limit + }); + if (!expandedMcp.error) { + const expandedRawRows = toNormalizedRows(expandedMcp.raw_rows); + const expandedScopedRows = applyAccountScopeFilter(expandedRawRows, expandedPlan.account_scope); + const expandedAccountScopeFallbackApplied = expandedPlan.account_scope_mode === "preferred" && + expandedPlan.account_scope.length > 0 && + expandedRawRows.length > 0 && + expandedScopedRows.length === 0; + const expandedNormalizedRows = expandedAccountScopeFallbackApplied ? expandedRawRows : expandedScopedRows; + let expandedAnchor = (0, resolveStage_1.resolvePrimaryAnchor)(intent.intent, expandedLimitFilters); + expandedAnchor = (0, resolveStage_1.refineAnchorFromRows)(expandedAnchor, expandedNormalizedRows); + const expandedFiltersForMatching = expandedAnchor.anchor_type === "counterparty" && expandedAnchor.anchor_value_resolved + ? { ...expandedLimitFilters, counterparty: expandedAnchor.anchor_value_resolved } + : expandedAnchor.anchor_type === "contract" && expandedAnchor.anchor_value_resolved + ? { ...expandedLimitFilters, contract: expandedAnchor.anchor_value_resolved } + : expandedLimitFilters; + const expandedAccountScopeAudit = buildAccountScopeAudit({ + intent: intent.intent, + filters: expandedFiltersForMatching, + accountScope: expandedPlan.account_scope, + rowsBeforeScope: expandedRawRows.length, + rowsAfterScope: expandedNormalizedRows.length + }); + const expandedAnchorFilter = applyAddressFilters(expandedNormalizedRows, expandedFiltersForMatching); + const expandedRowsByAnchor = expandedAnchorFilter.rows; + const expandedFilteredRows = applyIntentSpecificFilter(intent.intent, expandedRowsByAnchor); + if (expandedFilteredRows.length > 0) { + const expandedRowDiagnostics = deriveRowStageDiagnostics(expandedMcp.raw_rows, expandedNormalizedRows.length, expandedNormalizedRows.length); + const expandedStageStatus = deriveMcpStageStatus({ + rawRowsReceived: expandedMcp.raw_rows.length, + rowsMaterialized: expandedNormalizedRows.length, + rowsAnchorMatched: expandedRowsByAnchor.length, + rowsMatched: expandedFilteredRows.length + }); + const expandedFactual = (0, composeStage_1.composeFactualReply)(intent.intent, expandedFilteredRows); + const expandedPrefix = `Период сохранен. Глубина live-выборки автоматически расширена до ${expandedPlan.limit} строк.`; + const expandedLimitations = [...filters.warnings, "query_limit_auto_expanded_for_anchor_recovery"]; + const expandedReasons = [...baseReasons, "query_limit_auto_expanded_for_anchor_recovery"]; + return { + handled: true, + reply_text: `${expandedPrefix}\n${expandedFactual.text}`, + reply_type: (0, composeStage_1.inferReplyType)(expandedFactual.responseType), + response_type: expandedFactual.responseType, + debug: { + detected_mode: mode.mode, + detected_mode_confidence: mode.confidence, + query_shape: shape.shape, + query_shape_confidence: shape.confidence, + detected_intent: intent.intent, + detected_intent_confidence: intent.confidence, + extracted_filters: filters.extracted_filters, + missing_required_filters: [], + selected_recipe: expandedSelection.selected_recipe.recipe_id, + mcp_call_status_legacy: toLegacyMcpStatus(expandedStageStatus), + account_scope_mode: expandedPlan.account_scope_mode, + account_scope_fallback_applied: expandedAccountScopeFallbackApplied, + anchor_type: expandedAnchor.anchor_type, + anchor_value_raw: expandedAnchor.anchor_value_raw, + anchor_value_resolved: expandedAnchor.anchor_value_resolved, + resolver_confidence: expandedAnchor.resolver_confidence, + ambiguity_count: expandedAnchor.ambiguity_count, + match_failure_stage: "none", + match_failure_reason: null, + mcp_call_status: expandedStageStatus, + rows_fetched: expandedMcp.fetched_rows, + raw_rows_received: expandedMcp.raw_rows.length, + rows_after_account_scope: expandedNormalizedRows.length, + rows_after_recipe_filter: expandedRowsByAnchor.length, + rows_materialized: expandedNormalizedRows.length, + rows_matched: expandedFilteredRows.length, + raw_row_keys_sample: expandedRowDiagnostics.rawRowKeysSample, + materialization_drop_reason: expandedRowDiagnostics.materializationDropReason, + account_token_raw: expandedAccountScopeAudit.accountTokenRaw, + account_token_normalized: expandedAccountScopeAudit.accountTokenNormalized, + account_scope_fields_checked: expandedAccountScopeAudit.accountScopeFieldsChecked, + account_scope_match_strategy: expandedAccountScopeAudit.accountScopeMatchStrategy, + account_scope_drop_reason: expandedAccountScopeAudit.accountScopeDropReason, + runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS", + limited_reason_category: null, + response_type: expandedFactual.responseType, + limitations: expandedLimitations, + reasons: expandedReasons + } + }; + } + if (intent.intent === "open_items_by_counterparty_or_contract" && + expandedFilteredRows.length === 0 && + expandedRowsByAnchor.length === 0 && + expandedNormalizedRows.length > 0) { + const expandedFallbackRows = applyIntentSpecificFilter(intent.intent, expandedNormalizedRows); + if (expandedFallbackRows.length > 0) { + const expandedRowDiagnostics = deriveRowStageDiagnostics(expandedMcp.raw_rows, expandedNormalizedRows.length, expandedNormalizedRows.length); + const expandedFactual = (0, composeStage_1.composeFactualReply)(intent.intent, expandedFallbackRows); + const expandedLimitations = [ + ...filters.warnings, + "query_limit_auto_expanded_for_anchor_recovery", + "open_items_anchor_not_matched_fallback_rows" + ]; + const expandedReasons = [ + ...baseReasons, + "query_limit_auto_expanded_for_anchor_recovery", + "open_items_anchor_not_matched_fallback_rows" + ]; + return { + handled: true, + reply_text: `Точный якорь не подтвердился в live-строках даже после расширения до ${expandedPlan.limit}; ` + + "показаны ближайшие доступные позиции по счетам 60/62/76.\n" + + expandedFactual.text, + reply_type: (0, composeStage_1.inferReplyType)(expandedFactual.responseType), + response_type: expandedFactual.responseType, + debug: { + detected_mode: mode.mode, + detected_mode_confidence: mode.confidence, + query_shape: shape.shape, + query_shape_confidence: shape.confidence, + detected_intent: intent.intent, + detected_intent_confidence: intent.confidence, + extracted_filters: filters.extracted_filters, + missing_required_filters: [], + selected_recipe: expandedSelection.selected_recipe.recipe_id, + mcp_call_status_legacy: "matched_non_empty", + account_scope_mode: expandedPlan.account_scope_mode, + account_scope_fallback_applied: expandedAccountScopeFallbackApplied, + anchor_type: expandedAnchor.anchor_type, + anchor_value_raw: expandedAnchor.anchor_value_raw, + anchor_value_resolved: expandedAnchor.anchor_value_resolved, + resolver_confidence: expandedAnchor.resolver_confidence, + ambiguity_count: expandedAnchor.ambiguity_count, + match_failure_stage: "materialized_but_not_anchor_matched", + match_failure_reason: expandedAnchorFilter.mismatchReason ?? "anchor_not_matched_after_query_limit_expansion", + mcp_call_status: "matched_non_empty", + rows_fetched: expandedMcp.fetched_rows, + raw_rows_received: expandedMcp.raw_rows.length, + rows_after_account_scope: expandedNormalizedRows.length, + rows_after_recipe_filter: expandedRowsByAnchor.length, + rows_materialized: expandedNormalizedRows.length, + rows_matched: expandedFallbackRows.length, + raw_row_keys_sample: expandedRowDiagnostics.rawRowKeysSample, + materialization_drop_reason: expandedRowDiagnostics.materializationDropReason, + account_token_raw: expandedAccountScopeAudit.accountTokenRaw, + account_token_normalized: expandedAccountScopeAudit.accountTokenNormalized, + account_scope_fields_checked: expandedAccountScopeAudit.accountScopeFieldsChecked, + account_scope_match_strategy: expandedAccountScopeAudit.accountScopeMatchStrategy, + account_scope_drop_reason: expandedAccountScopeAudit.accountScopeDropReason, + runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS", + limited_reason_category: null, + response_type: expandedFactual.responseType, + limitations: expandedLimitations, + reasons: expandedReasons + } + }; + } + } + } + } + } + } } if (filteredRows.length === 0 && canAutoBroadenPeriodWindow(intent.intent, filters.extracted_filters)) { const autoBroadenedFilters = { ...filters.extracted_filters }; @@ -870,37 +1139,111 @@ class AddressQueryService { } } } + if (filteredRows.length === 0 && + intent.intent === "open_items_by_counterparty_or_contract" && + normalizedRows.length > 0) { + const openItemsFallbackRows = applyIntentSpecificFilter(intent.intent, normalizedRows); + if (openItemsFallbackRows.length > 0) { + const fallbackFactual = (0, composeStage_1.composeFactualReply)(intent.intent, openItemsFallbackRows); + const fallbackLimitations = [...filters.warnings, "open_items_anchor_not_matched_fallback_rows"]; + const fallbackReasons = [...baseReasons, "open_items_anchor_not_matched_fallback_rows"]; + return { + handled: true, + reply_text: "Точный якорь не подтвердился в текущем окне live-данных; показаны ближайшие доступные позиции по счетам 60/62/76.\n" + + fallbackFactual.text, + reply_type: (0, composeStage_1.inferReplyType)(fallbackFactual.responseType), + response_type: fallbackFactual.responseType, + debug: { + detected_mode: mode.mode, + detected_mode_confidence: mode.confidence, + query_shape: shape.shape, + query_shape_confidence: shape.confidence, + detected_intent: intent.intent, + detected_intent_confidence: intent.confidence, + extracted_filters: filters.extracted_filters, + missing_required_filters: [], + selected_recipe: recipeSelection.selected_recipe.recipe_id, + mcp_call_status_legacy: "matched_non_empty", + account_scope_mode: plan.account_scope_mode, + account_scope_fallback_applied: accountScopeFallbackApplied, + anchor_type: anchor.anchor_type, + anchor_value_raw: anchor.anchor_value_raw, + anchor_value_resolved: anchor.anchor_value_resolved, + resolver_confidence: anchor.resolver_confidence, + ambiguity_count: anchor.ambiguity_count, + match_failure_stage: matchFailureStage, + match_failure_reason: matchFailureReason, + mcp_call_status: "matched_non_empty", + rows_fetched: mcp.fetched_rows, + raw_rows_received: mcp.raw_rows.length, + rows_after_account_scope: normalizedRows.length, + rows_after_recipe_filter: filterByAnchors.length, + rows_materialized: normalizedRows.length, + rows_matched: openItemsFallbackRows.length, + raw_row_keys_sample: rowDiagnostics.rawRowKeysSample, + materialization_drop_reason: rowDiagnostics.materializationDropReason, + account_token_raw: accountScopeAudit.accountTokenRaw, + account_token_normalized: accountScopeAudit.accountTokenNormalized, + account_scope_fields_checked: accountScopeAudit.accountScopeFieldsChecked, + account_scope_match_strategy: accountScopeAudit.accountScopeMatchStrategy, + account_scope_drop_reason: accountScopeAudit.accountScopeDropReason, + runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS", + limited_reason_category: null, + response_type: fallbackFactual.responseType, + limitations: fallbackLimitations, + reasons: fallbackReasons + } + }; + } + } if (filteredRows.length === 0) { const hadBaseRows = normalizedRows.length > 0 || mcp.fetched_rows > 0; const hadAnchorMatchedRows = filterByAnchors.length > 0; const isVisibilityGapCandidate = hadBaseRows && hadAnchorMatchedRows && - (intent.intent === "list_documents_by_counterparty" || intent.intent === "bank_operations_by_counterparty"); + (intent.intent === "list_documents_by_counterparty" || + intent.intent === "bank_operations_by_counterparty" || + intent.intent === "list_documents_by_contract" || + intent.intent === "bank_operations_by_contract"); const isAnchorMismatch = stageStatus === "materialized_but_not_anchor_matched"; const isRecipeFilteredOut = stageStatus === "materialized_but_filtered_out_by_recipe"; + const isFollowupAnchorCarryover = Array.isArray(filters.warnings) && + (filters.warnings.includes("counterparty_from_followup_context") || + filters.warnings.includes("contract_from_followup_context")); + const isLowQualityPartyAnchor = (anchor.anchor_type === "counterparty" || anchor.anchor_type === "contract") && + isLikelyLowQualityPartyAnchor(anchor.anchor_value_raw); + const anchorMismatchCategory = isFollowupAnchorCarryover || !isLowQualityPartyAnchor ? "empty_match" : "missing_anchor"; const category = isAnchorMismatch - ? "missing_anchor" + ? anchorMismatchCategory : isRecipeFilteredOut ? "recipe_visibility_gap" : isVisibilityGapCandidate ? "recipe_visibility_gap" : "empty_match"; const reasonText = isAnchorMismatch - ? "якорь контрагента/договора не найден в материализованных live-строках" + ? anchorMismatchCategory === "missing_anchor" + ? "якорь контрагента/договора не найден в материализованных live-строках" + : "по указанному якорю и фильтрам в live-выборке нет строк" : isRecipeFilteredOut ? "строки по якорю найдены, но отфильтрованы intent-specific recipe" : isVisibilityGapCandidate ? "в текущем live recipe нет достаточной document/bank видимости после фильтрации" : "по выбранным фильтрам в live-выборке нет строк"; const nextStep = isAnchorMismatch - ? "уточните контрагента точным именем или добавьте ИНН/договор" + ? anchorMismatchCategory === "missing_anchor" + ? "уточните контрагента точным именем или добавьте ИНН/договор" + : "уточните период или снимите часть фильтров" : isRecipeFilteredOut ? "сузьте период, уточните контрагента или документный тип" : isVisibilityGapCandidate ? "нужен специализированный recipe для document/bank контуров или более точный документный anchor" : "уточните период, контрагента, договор или снимите часть фильтров"; const limitations = isAnchorMismatch - ? ["anchor_not_matched_after_materialization"] + ? [ + anchorMismatchCategory === "missing_anchor" + ? "anchor_not_matched_after_materialization" + : "no_rows_for_anchor_after_materialization" + ] : isRecipeFilteredOut ? ["rows_filtered_out_by_recipe_after_anchor_match"] : [ diff --git a/llm_normalizer/backend/dist/services/addressRecipeCatalog.js b/llm_normalizer/backend/dist/services/addressRecipeCatalog.js index 13091ff..a155794 100644 --- a/llm_normalizer/backend/dist/services/addressRecipeCatalog.js +++ b/llm_normalizer/backend/dist/services/addressRecipeCatalog.js @@ -103,6 +103,26 @@ const BASE_RECIPES = [ account_scope_mode: "preferred", query_template: "bank_docs" }, + { + recipe_id: "address_documents_by_contract_v1", + intent: "list_documents_by_contract", + purpose: "Collect contract-related document lines from movements", + required_filters: ["contract"], + optional_filters: ["period_from", "period_to", "as_of_date", "organization", "counterparty", "limit", "sort"], + default_limit: 100, + account_scope: ["60", "62", "76", "51", "52"], + account_scope_mode: "preferred" + }, + { + recipe_id: "address_bank_operations_by_contract_v1", + intent: "bank_operations_by_contract", + purpose: "Collect bank operation candidates by contract from movement lines", + required_filters: ["contract"], + optional_filters: ["period_from", "period_to", "as_of_date", "organization", "counterparty", "limit", "sort"], + default_limit: 100, + account_scope: ["51", "52"], + account_scope_mode: "preferred" + }, { recipe_id: "address_documents_forming_balance_v1", intent: "documents_forming_balance", @@ -216,8 +236,9 @@ function buildMovementAccountCondition(filters) { return clauses.length === 1 ? clauses[0] : `(${clauses.join(" ИЛИ ")})`; } function shouldBoostLimitForAllTimeCounterparty(filters) { - const hasCounterparty = typeof filters.counterparty === "string" && filters.counterparty.trim().length > 0; - if (!hasCounterparty) { + const hasAnchor = (typeof filters.counterparty === "string" && filters.counterparty.trim().length > 0) || + (typeof filters.contract === "string" && filters.contract.trim().length > 0); + if (!hasAnchor) { return false; } const hasPeriod = Boolean((typeof filters.period_from === "string" && filters.period_from.trim().length > 0) || @@ -226,7 +247,12 @@ function shouldBoostLimitForAllTimeCounterparty(filters) { return !hasPeriod; } function maxLimitForIntent(intent) { - if (intent === "list_documents_by_counterparty" || intent === "bank_operations_by_counterparty") { + if (intent === "list_documents_by_counterparty" || + intent === "bank_operations_by_counterparty" || + intent === "list_documents_by_contract" || + intent === "bank_operations_by_contract" || + intent === "open_items_by_counterparty_or_contract" || + intent === "list_open_contracts") { return ADDRESS_MAX_LIMIT_EXTENDED; } return ADDRESS_MAX_LIMIT_DEFAULT; @@ -255,7 +281,10 @@ function buildAddressRecipePlan(recipe, filters) { const baseLimit = typeof filters.limit === "number" && Number.isFinite(filters.limit) ? Math.max(1, Math.min(maxLimit, Math.trunc(filters.limit))) : recipe.default_limit; - const boostedLimit = (recipe.intent === "list_documents_by_counterparty" || recipe.intent === "bank_operations_by_counterparty") && + const boostedLimit = (recipe.intent === "list_documents_by_counterparty" || + recipe.intent === "bank_operations_by_counterparty" || + recipe.intent === "list_documents_by_contract" || + recipe.intent === "bank_operations_by_contract") && shouldBoostLimitForAllTimeCounterparty(filters) ? Math.max(baseLimit, maxLimit) : (recipe.intent === "account_balance_snapshot" || recipe.intent === "documents_forming_balance") && diff --git a/llm_normalizer/backend/dist/services/address_runtime/composeStage.js b/llm_normalizer/backend/dist/services/address_runtime/composeStage.js index 950e226..6d36235 100644 --- a/llm_normalizer/backend/dist/services/address_runtime/composeStage.js +++ b/llm_normalizer/backend/dist/services/address_runtime/composeStage.js @@ -67,7 +67,13 @@ function composeFactualReply(intent, rows) { `Строк движения: ${rows.length}.`, `Договорных кандидатов: ${contracts.length}.` ]; - lines.push(...contracts.slice(0, 8).map((item, index) => `${index + 1}. ${item}`)); + if (contracts.length > 0) { + lines.push(...contracts.slice(0, 8).map((item, index) => `${index + 1}. ${item}`)); + } + else { + lines.push("Договорные якоря в live-строках не выделены; показаны связанные движения как fallback."); + lines.push(...formatTopRows(rows, 6)); + } return { responseType: "FACTUAL_LIST", text: lines.join("\n") @@ -95,6 +101,17 @@ function composeFactualReply(intent, rows) { text: lines.join("\n") }; } + if (intent === "list_documents_by_contract") { + const lines = [ + "Собран список документов по договору (live address lane).", + `Строк отобрано: ${rows.length}.`, + ...formatTopRows(rows, rows.length) + ]; + return { + responseType: "FACTUAL_LIST", + text: lines.join("\n") + }; + } if (intent === "bank_operations_by_counterparty") { const lines = [ "Собран список банковских операций по контрагенту (live address lane).", @@ -106,6 +123,17 @@ function composeFactualReply(intent, rows) { text: lines.join("\n") }; } + if (intent === "bank_operations_by_contract") { + const lines = [ + "Собран список банковских операций по договору (live address lane).", + `Строк отобрано: ${rows.length}.`, + ...formatTopRows(rows, rows.length) + ]; + return { + responseType: "FACTUAL_LIST", + text: lines.join("\n") + }; + } const title = intent === "list_payables_counterparties" ? "Срез обязательств (payables) собран по движениям с account scope 60/76." : intent === "list_receivables_counterparties" diff --git a/llm_normalizer/backend/dist/services/address_runtime/decomposeStage.js b/llm_normalizer/backend/dist/services/address_runtime/decomposeStage.js index 8d3cc05..c95b54c 100644 --- a/llm_normalizer/backend/dist/services/address_runtime/decomposeStage.js +++ b/llm_normalizer/backend/dist/services/address_runtime/decomposeStage.js @@ -71,6 +71,15 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) { } } } + if (intent === "list_documents_by_contract" || intent === "bank_operations_by_contract") { + if (!toNonEmptyString(merged.contract)) { + const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null); + if (inheritedContract) { + merged.contract = inheritedContract; + reasons.push("contract_from_followup_context"); + } + } + } if (intent === "account_balance_snapshot" || intent === "documents_forming_balance") { if (!toNonEmptyString(merged.account)) { const inheritedAccount = previousAccount ?? (followupContext.previous_anchor_type === "account" ? previousAnchorValue : null); @@ -130,7 +139,9 @@ function resolveMissingRequiredFilters(intent, filters) { account_balance_snapshot: ["account", "as_of_date"], documents_forming_balance: ["account", "as_of_date"], list_documents_by_counterparty: ["counterparty"], - bank_operations_by_counterparty: ["counterparty"] + bank_operations_by_counterparty: ["counterparty"], + list_documents_by_contract: ["contract"], + bank_operations_by_contract: ["contract"] }; const required = requiredByIntent[intent] ?? []; return required.filter((key) => { diff --git a/llm_normalizer/backend/dist/services/address_runtime/resolveStage.js b/llm_normalizer/backend/dist/services/address_runtime/resolveStage.js index f7fe364..339e5f8 100644 --- a/llm_normalizer/backend/dist/services/address_runtime/resolveStage.js +++ b/llm_normalizer/backend/dist/services/address_runtime/resolveStage.js @@ -109,6 +109,17 @@ function resolvePrimaryAnchor(intent, filters) { }; } } + if (intent === "list_documents_by_contract" || intent === "bank_operations_by_contract") { + if (contract) { + return { + anchor_type: "contract", + anchor_value_raw: contract, + anchor_value_resolved: contract, + resolver_confidence: "medium", + ambiguity_count: 0 + }; + } + } if (counterparty) { return { anchor_type: "counterparty", diff --git a/llm_normalizer/backend/dist/services/assistantService.js b/llm_normalizer/backend/dist/services/assistantService.js index 1ddc5c1..16ae5a7 100644 --- a/llm_normalizer/backend/dist/services/assistantService.js +++ b/llm_normalizer/backend/dist/services/assistantService.js @@ -54,6 +54,8 @@ const companyAnchorResolver_1 = __importStar(require("./companyAnchorResolver")) const assistantRuntimeGuards_1 = __importStar(require("./assistantRuntimeGuards")); const assistantClaimBoundEvidence_1 = __importStar(require("./assistantClaimBoundEvidence")); const addressQueryService_1 = __importStar(require("./addressQueryService")); +const addressQueryClassifier_1 = __importStar(require("./addressQueryClassifier")); +const addressIntentResolver_1 = __importStar(require("./addressIntentResolver")); const iconv_lite_1 = __importDefault(require("iconv-lite")); function retrievalSummaryForRoute(route) { if (route === "store_canonical") @@ -1802,6 +1804,10 @@ function buildAddressDebugPayload(addressDebug, llmPreDecomposeMeta = null) { llm_decomposition_trace_id: llmMeta?.traceId ?? null, llm_decomposition_effective_message: llmMeta?.effectiveMessage ?? null, llm_decomposition_reason: llmMeta?.reason ?? null, + fallback_rule_hit: llmMeta?.fallbackRuleHit ?? null, + sanitized_user_message: llmMeta?.sanitizedUserMessage ?? null, + tool_gate_decision: llmMeta?.toolGateDecision ?? null, + tool_gate_reason: llmMeta?.toolGateReason ?? null, answer_structure_v11: null, investigation_state_snapshot: null, normalized: null, @@ -1865,9 +1871,24 @@ const ADDRESS_PREDECOMPOSE_NOISE_TOKENS = new Set([ "show", "list", "выведи", + "что", + "чо", "которые", "какие", "какой", + "были", + "был", + "была", + "было", + "ли", + "списания", + "списание", + "поступления", + "поступление", + "расчетного", + "расчётного", + "счета", + "счёта", "есть", "est", "kakie", @@ -1880,6 +1901,401 @@ const ADDRESS_PREDECOMPOSE_NOISE_TOKENS = new Set([ "ёпт", "бля" ]); +const ADDRESS_FALLBACK_STRIP_TOKENS = new Set([ + "бля", + "блять", + "blya", + "blyat", + "епт", + "ёпт", + "епта", + "нах", + "нахуй", + "плс", + "pls", + "пж", + "пжлст", + "пожалуйста", + "please" +]); +const ADDRESS_MONTH_ALIAS_MAP = { + янв: "01", + январ: "01", + january: "01", + jan: "01", + фев: "02", + феврал: "02", + february: "02", + feb: "02", + мар: "03", + март: "03", + march: "03", + apr: "04", + апр: "04", + апрел: "04", + april: "04", + май: "05", + ма: "05", + may: "05", + июн: "06", + июнь: "06", + june: "06", + jun: "06", + июл: "07", + июль: "07", + july: "07", + jul: "07", + авг: "08", + август: "08", + august: "08", + aug: "08", + сен: "09", + сент: "09", + сентябр: "09", + september: "09", + sep: "09", + окт: "10", + октябр: "10", + october: "10", + oct: "10", + ноя: "11", + ноябр: "11", + november: "11", + nov: "11", + дек: "12", + декабр: "12", + december: "12", + dec: "12" +}; +const ADDRESS_DOCS_SIGNAL_PATTERN = /(?:док|доки|документ|документы|документов|docs?|documents?|bank|выписк|плат[её]ж|оплат|поступлен|списан|операц|опер|transaction)/i; +const ADDRESS_BANK_SIGNAL_PATTERN = /(?:bank|банк|банков|выписк|плат[её]ж|оплат|поступлен|списан|операц|опер|расчетн|транзак)/i; +const ADDRESS_CONTRACT_SIGNAL_PATTERN = /(?:договор(?:а|у|ом|е)?|(?:^|[^\p{L}\p{N}_])(?:дог\.?|[dд][oо][gг]\.?|dog\.?)(?=$|[^\p{L}\p{N}_])|contract|dogovor)/iu; +const ADDRESS_BALANCE_SIGNAL_PATTERN = /(?:остат|сальдо|баланс|взаиморасч|долг|saldo|balance)/i; +const ADDRESS_ALL_TIME_PATTERN = /(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|for\s+all\s+time|all\s+time|entire\s+period|full\s+history)/iu; +function normalizeAddressMonthAliasToken(token) { + const source = String(token ?? "").trim().toLowerCase(); + if (!source) { + return null; + } + const direct = ADDRESS_MONTH_ALIAS_MAP[source]; + if (direct) { + return direct; + } + for (const [key, value] of Object.entries(ADDRESS_MONTH_ALIAS_MAP)) { + if (source.startsWith(key)) { + return value; + } + } + return null; +} +function normalizeAddressShortYearMentions(text) { + return String(text ?? "").replace(/(^|[^0-9])(\d{2})\s*(?:г(?:од|ода)?|г|год|year|god)(?=$|[^a-zа-яё0-9])/giu, (_full, prefix, shortYear) => { + const normalized = Number(shortYear); + if (!Number.isFinite(normalized) || normalized < 0 || normalized > 99) { + return _full; + } + return `${prefix}${String(2000 + normalized)} год`; + }); +} +function sanitizeAddressMessageForFallback(userMessage) { + const repaired = compactWhitespace(repairAddressMojibake(String(userMessage ?? ""))); + if (!repaired) { + return ""; + } + let sanitized = repaired.toLowerCase(); + sanitized = sanitized + .replace(/\bpokezh\b/giu, "покажи") + .replace(/\bpokazh(?:i)?\b/giu, "покажи") + .replace(/\bpokaji\b/giu, "покажи") + .replace(/\bop(?:er|ers?)\b/giu, "операции") + .replace(/(^|[^\p{L}\p{N}_])опер(?:аци[яиюе]|ы|)?(?=$|[^\p{L}\p{N}_])/giu, "$1операции") + .replace(/(^|[^\p{L}\p{N}_])дог\.?(?=$|[^\p{L}\p{N}_])/giu, "$1договор") + .replace(/(^|[^\p{L}\p{N}_])dog\.?(?=$|[^\p{L}\p{N}_])/giu, "$1contract") + .replace(/\bdok(?:i|y)?\b/giu, "доки") + .replace(/\bdocuments?\b/giu, "документы") + .replace(/\bdocs?\b/giu, "документы") + .replace(/\bschet(?:u)?\b/giu, "счет") + .replace(/\bsaldo\b/giu, "сальдо") + .replace(/\bgod\b/giu, "год"); + sanitized = normalizeAddressShortYearMentions(sanitized); + const tokens = sanitized + .split(/\s+/) + .map((item) => item.trim()) + .filter(Boolean); + const filteredTokens = tokens.filter((token) => { + const normalizedToken = token.replace(/^[^a-zа-яё0-9]+|[^a-zа-яё0-9]+$/giu, ""); + if (!normalizedToken) { + return true; + } + return !ADDRESS_FALLBACK_STRIP_TOKENS.has(normalizedToken); + }); + const compact = compactWhitespace(filteredTokens.join(" ")); + return compact || compactWhitespace(repaired.toLowerCase()); +} +function extractAddressFallbackYear(text) { + const source = String(text ?? ""); + const fullYearMatch = source.match(/\b(20\d{2})\b/); + if (fullYearMatch) { + return fullYearMatch[1]; + } + const shortYearMatch = source.match(/(?:^|[^0-9])(\d{2})\s*(?:г(?:од|ода)?|г|год|year|god)(?=$|[^a-zа-яё0-9])/iu); + if (shortYearMatch) { + const shortYear = Number(shortYearMatch[1]); + if (Number.isFinite(shortYear) && shortYear >= 0 && shortYear <= 99) { + return String(2000 + shortYear); + } + } + const shortOrdinalYearMatch = source.match(/(?:^|[^0-9])(\d{2})\s*(?:[-\s]?(?:й|ый|ой|th))(?=$|[^a-zа-яё0-9])/iu); + if (!shortOrdinalYearMatch) { + return null; + } + const shortOrdinalYear = Number(shortOrdinalYearMatch[1]); + if (!Number.isFinite(shortOrdinalYear) || shortOrdinalYear < 0 || shortOrdinalYear > 99) { + return null; + } + return String(2000 + shortOrdinalYear); +} +function extractAddressFallbackMonthYear(text) { + const source = String(text ?? ""); + const numericYearMonth = source.match(/\b(20\d{2})[./-](0?[1-9]|1[0-2])\b/); + if (numericYearMonth) { + const year = numericYearMonth[1]; + const month = String(Number(numericYearMonth[2])).padStart(2, "0"); + return `${year}-${month}`; + } + const numericMonthYear = source.match(/\b(0?[1-9]|1[0-2])[./-](20\d{2})\b/); + if (numericMonthYear) { + const month = String(Number(numericMonthYear[1])).padStart(2, "0"); + const year = numericMonthYear[2]; + return `${year}-${month}`; + } + const namedMonthYear = source.match(/(?:^|[^a-zа-яё0-9])([a-zа-яё]+)\s+(20\d{2})(?=$|[^a-zа-яё0-9])/iu); + if (namedMonthYear) { + const month = normalizeAddressMonthAliasToken(namedMonthYear[1]); + if (month) { + return `${namedMonthYear[2]}-${month}`; + } + } + const yearNamedMonth = source.match(/(?:^|[^a-zа-яё0-9])(20\d{2})\s+([a-zа-яё]+)(?=$|[^a-zа-яё0-9])/iu); + if (yearNamedMonth) { + const month = normalizeAddressMonthAliasToken(yearNamedMonth[2]); + if (month) { + return `${yearNamedMonth[1]}-${month}`; + } + } + return null; +} +function extractAddressFallbackAccountToken(text) { + const source = String(text ?? ""); + const explicitMatch = source.match(/(?:сч[её]т(?:а|у|ом|е)?|account)\D{0,12}(\d{2}(?:[.,]\d{1,2})?)/iu); + if (explicitMatch && explicitMatch[1]) { + return String(explicitMatch[1]).replace(",", "."); + } + const tokenPattern = /\b(\d{2}(?:[.,]\d{1,2})?)\b/giu; + let match = tokenPattern.exec(source); + while (match) { + const raw = String(match[1] ?? ""); + const start = match.index; + const end = start + raw.length; + const prev = start > 0 ? source[start - 1] : " "; + const next = end < source.length ? source[end] : " "; + if (!/[./-]/.test(prev) && !/[./-]/.test(next) && !/\d/.test(prev) && !/\d/.test(next)) { + return raw.replace(",", "."); + } + match = tokenPattern.exec(source); + } + return null; +} +function pickAddressFallbackCounterpartyToken(text) { + const source = String(text ?? ""); + const byAnchor = source.match(/(?:^|[\s,.;:!?()\-])(?:по|от)\s+([a-zа-яё][a-zа-яё0-9._-]{1,})(?=$|[\s,.;:!?()\-])/iu); + if (byAnchor && byAnchor[1]) { + const byToken = String(byAnchor[1]).trim(); + const normalizedByToken = byToken.toLowerCase(); + if (byToken && + !ADDRESS_PREDECOMPOSE_NOISE_TOKENS.has(normalizedByToken) && + !/^\d{2}(?:\.\d{1,2})?$/.test(normalizedByToken) && + !/^(?:19|20)\d{2}$/.test(normalizedByToken) && + !/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(normalizedByToken) && + !/^(?:договор|договора|договору|договором|договоре|contract|dogovor|dog|дог|d[oо]g|д[oо]г)$/.test(normalizedByToken)) { + return byToken; + } + } + const candidates = extractAddressAnchorTokens(text); + for (const token of candidates) { + const normalized = String(token ?? "").toLowerCase(); + if (!normalized || /^\d{2}(?:\.\d{1,2})?$/.test(normalized)) { + continue; + } + if (/^(?:19|20)\d{2}$/.test(normalized)) { + continue; + } + if (/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(normalized)) { + continue; + } + if (/^(?:договор|договора|договору|договором|договоре|contract|dogovor|dog|дог|d[oо]g|д[oо]г)$/.test(normalized)) { + continue; + } + return token; + } + return null; +} +function extractAddressFallbackContractToken(text) { + const source = String(text ?? ""); + const patterns = [ + /(?:договор(?:а|у|ом|е)?|дог\.?|[dд][oо][gг]\.?|contract|dogovor|dog\.?)\s*(?:№|#|n|no\.?)?\s*([a-zа-я0-9][a-zа-я0-9/_-]{1,})/iu, + /(?:№|#|n|no\.?)\s*([a-zа-я0-9][a-zа-я0-9/_-]{1,})\s*(?:договор(?:а|у|ом|е)?|дог\.?|[dд][oо][gг]\.?|contract|dogovor|dog\.?)/iu + ]; + for (const pattern of patterns) { + const match = pattern.exec(source); + if (!match || !match[1]) { + continue; + } + const candidate = String(match[1]).replace(/^[^a-zа-я0-9]+|[^a-zа-я0-9/_-]+$/giu, ""); + if (!candidate || candidate.length < 2) { + continue; + } + if (/^(?:19|20)\d{2}$/.test(candidate)) { + continue; + } + if (/^(?:19|20)\d{2}[./-](?:0?[1-9]|1[0-2])(?:[./-](?:0?[1-9]|[12]\d|3[01]))?$/.test(candidate)) { + continue; + } + return candidate; + } + if (ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source) || /(?:^|[^\p{L}\p{N}_])(?:[dд][oо][gг]|dogovor)(?=$|[^\p{L}\p{N}_])/iu.test(source)) { + const generic = source.match(/\b([a-zа-я0-9]{1,10}[/-][a-zа-я0-9]{1,10}(?:[/-][a-zа-я0-9]{1,10})?)\b/iu); + if (generic && generic[1]) { + return generic[1]; + } + } + return null; +} +function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage) { + const sourceRaw = compactWhitespace(repairAddressMojibake(String(userMessage ?? ""))); + const source = compactWhitespace(String(sanitizedUserMessage ?? sourceRaw).toLowerCase()); + if (!source) { + return null; + } + const monthYear = extractAddressFallbackMonthYear(source); + const year = extractAddressFallbackYear(source); + const allTime = ADDRESS_ALL_TIME_PATTERN.test(source); + const account = extractAddressFallbackAccountToken(source); + const docsSignal = ADDRESS_DOCS_SIGNAL_PATTERN.test(source); + const bankSignal = ADDRESS_BANK_SIGNAL_PATTERN.test(source); + const contractSignal = ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source); + const balanceSignal = ADDRESS_BALANCE_SIGNAL_PATTERN.test(source); + if (balanceSignal && account) { + let periodClause = ""; + let rule = "balance_account_rewrite"; + if (monthYear) { + periodClause = ` на ${monthYear}`; + rule = "balance_month_period_rewrite"; + } + else if (year) { + periodClause = ` на ${year}-12-31`; + rule = "balance_year_period_rewrite"; + } + const candidate = compactWhitespace(`остаток по счету ${account}${periodClause}`); + if (candidate && candidate !== sourceRaw.toLowerCase()) { + return { + candidate, + rule + }; + } + } + if (!docsSignal && !contractSignal && !balanceSignal) { + const counterparty = pickAddressFallbackCounterpartyToken(source); + const genericLookupSignal = /(?:\bесть\b|\bпокажи\b|\bвыведи\b|\bч[её]\b|\bчто\b)/iu.test(source); + if (counterparty && (allTime || monthYear || year) && genericLookupSignal) { + let periodClause = ""; + let rule = "documents_counterparty_rewrite_from_generic_lookup"; + if (allTime) { + periodClause = " за все время"; + rule = "documents_counterparty_all_time_rewrite_from_generic_lookup"; + } + else if (monthYear) { + periodClause = ` за ${monthYear}`; + rule = "documents_counterparty_month_rewrite_from_generic_lookup"; + } + else if (year) { + periodClause = ` за ${year} год`; + rule = "documents_counterparty_year_rewrite_from_generic_lookup"; + } + const candidate = compactWhitespace(`документы по контрагенту ${counterparty}${periodClause}`); + if (candidate && candidate !== sourceRaw.toLowerCase()) { + return { + candidate, + rule + }; + } + } + } + if (docsSignal) { + const contract = extractAddressFallbackContractToken(sourceRaw || source); + if (contractSignal || contract) { + if (contract) { + let periodClause = ""; + let rule = bankSignal ? "bank_operations_contract_rewrite" : "documents_contract_rewrite"; + if (allTime) { + periodClause = " за все время"; + rule = bankSignal ? "bank_operations_contract_all_time_rewrite" : "documents_contract_all_time_rewrite"; + } + else if (monthYear) { + periodClause = ` за ${monthYear}`; + rule = bankSignal ? "bank_operations_contract_month_rewrite" : "documents_contract_month_rewrite"; + } + else if (year) { + periodClause = ` за ${year} год`; + rule = bankSignal ? "bank_operations_contract_year_rewrite" : "documents_contract_year_rewrite"; + } + const subject = bankSignal ? "банковские операции" : "документы"; + const candidate = compactWhitespace(`${subject} по договору ${contract}${periodClause}`); + if (candidate && candidate !== sourceRaw.toLowerCase()) { + return { + candidate, + rule + }; + } + } + } + else { + const counterparty = pickAddressFallbackCounterpartyToken(source); + if (counterparty) { + let periodClause = ""; + const subject = bankSignal ? "банковские операции" : "документы"; + const rulePrefix = bankSignal ? "bank_operations_counterparty" : "documents_counterparty"; + let rule = `${rulePrefix}_rewrite`; + if (allTime) { + periodClause = " за все время"; + rule = `${rulePrefix}_all_time_rewrite`; + } + else if (monthYear) { + periodClause = ` за ${monthYear}`; + rule = `${rulePrefix}_month_rewrite`; + } + else if (year) { + periodClause = ` за ${year} год`; + rule = `${rulePrefix}_year_rewrite`; + } + const candidate = compactWhitespace(`${subject} по контрагенту ${counterparty}${periodClause}`); + if (candidate && candidate !== sourceRaw.toLowerCase()) { + return { + candidate, + rule + }; + } + } + } + } + if (source !== sourceRaw.toLowerCase() && isAddressLlmPreDecomposeCandidate(source)) { + return { + candidate: source, + rule: "noise_cleanup" + }; + } + return null; +} function textMojibakeScoreForAddress(value) { const source = String(value ?? ""); const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length; @@ -2203,21 +2619,56 @@ function extractAddressQuestionFromRawNormalizerOutput(rawModelOutput) { } async function runAddressLlmPreDecompose(normalizerService, payload, userMessage) { const provider = payload?.llmProvider === "local" ? "local" : payload?.llmProvider === "openai" ? "openai" : null; + const sanitizedUserMessage = sanitizeAddressMessageForFallback(userMessage); + const fallbackCandidate = resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage); + const hasAddressSignal = isAddressLlmPreDecomposeCandidate(userMessage) || isAddressLlmPreDecomposeCandidate(sanitizedUserMessage); const baseMeta = { attempted: false, applied: false, provider, traceId: null, effectiveMessage: userMessage, - reason: "not_attempted" + reason: "not_attempted", + fallbackRuleHit: null, + sanitizedUserMessage, + toolGateDecision: null, + toolGateReason: null }; if (Boolean(payload?.useMock)) { + if (fallbackCandidate) { + const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase()); + const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase()); + const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompact; + if (fallbackApplied) { + return { + ...baseMeta, + applied: true, + effectiveMessage: fallbackCandidate.candidate, + reason: "fallback_rule_applied_without_llm", + fallbackRuleHit: fallbackCandidate.rule + }; + } + } return { ...baseMeta, reason: "skipped_in_mock" }; } - if (!isAddressLlmPreDecomposeCandidate(userMessage)) { + if (!hasAddressSignal) { + if (fallbackCandidate) { + const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase()); + const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase()); + const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompact; + if (fallbackApplied) { + return { + ...baseMeta, + applied: true, + effectiveMessage: fallbackCandidate.candidate, + reason: "fallback_rule_applied_without_llm", + fallbackRuleHit: fallbackCandidate.rule + }; + } + } return { ...baseMeta, reason: "not_address_like" @@ -2242,6 +2693,22 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage const candidateFromRaw = candidateFromNormalized ? null : extractAddressQuestionFromRawNormalizerOutput(normalized?.raw_model_output); const candidate = candidateFromNormalized ?? candidateFromRaw; if (!candidate) { + if (fallbackCandidate) { + const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase()); + const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase()); + const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompact; + if (fallbackApplied) { + return { + ...baseMeta, + attempted: true, + applied: true, + traceId: normalized?.trace_id ?? null, + effectiveMessage: fallbackCandidate.candidate, + reason: "fallback_rule_applied_after_llm", + fallbackRuleHit: fallbackCandidate.rule + }; + } + } return { ...baseMeta, attempted: true, @@ -2249,6 +2716,32 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage reason: normalized?.ok ? "no_usable_fragment" : "normalize_failed" }; } + const repairedSourceMessage = repairAddressMojibake(userMessage); + const sourceIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedSourceMessage || userMessage); + const candidateIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(candidate); + const sourceIntentKnown = sourceIntentResolution.intent !== "unknown"; + const candidateIntentKnown = candidateIntentResolution.intent !== "unknown"; + const intentConflict = sourceIntentKnown && + candidateIntentKnown && + sourceIntentResolution.intent !== candidateIntentResolution.intent; + const intentDroppedByCandidate = sourceIntentKnown && !candidateIntentKnown; + const rejectCandidateForIntentSafety = intentDroppedByCandidate || + (intentConflict && + (sourceIntentResolution.confidence === "high" || candidateIntentResolution.confidence !== "high")); + if (rejectCandidateForIntentSafety) { + return { + ...baseMeta, + attempted: true, + applied: false, + traceId: normalized?.trace_id ?? null, + effectiveMessage: userMessage, + reason: intentDroppedByCandidate + ? "normalized_fragment_rejected_intent_drop" + : "normalized_fragment_rejected_intent_conflict", + fallbackRuleHit: null, + sanitizedUserMessage + }; + } const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase()); const candidateCompact = compactWhitespace(candidate.toLowerCase()); const applied = sourceCompact !== candidateCompact; @@ -2270,10 +2763,27 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage provider, traceId: normalized?.trace_id ?? null, effectiveMessage: applied ? candidate : userMessage, - reason + reason, + fallbackRuleHit: null, + sanitizedUserMessage }; } catch (error) { + if (fallbackCandidate) { + const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase()); + const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase()); + const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompact; + if (fallbackApplied) { + return { + ...baseMeta, + attempted: true, + applied: true, + effectiveMessage: fallbackCandidate.candidate, + reason: "fallback_rule_applied_after_llm_error", + fallbackRuleHit: fallbackCandidate.rule + }; + } + } return { ...baseMeta, attempted: true, @@ -2281,6 +2791,35 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage }; } } +function resolveAddressToolGateDecision(addressInputMessage, followupContext) { + const repairedInputMessage = repairAddressMojibake(String(addressInputMessage ?? "")); + const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedInputMessage || addressInputMessage); + const hasClassifierSignal = modeDetection.mode === "address_query"; + const hasMessageSignal = hasClassifierSignal || + isAddressLlmPreDecomposeCandidate(addressInputMessage) || + isAddressLlmPreDecomposeCandidate(repairedInputMessage) || + hasAccountingSignal(addressInputMessage) || + hasAccountingSignal(repairedInputMessage); + if (hasMessageSignal) { + return { + runAddressLane: true, + decision: "run_address_lane", + reason: hasClassifierSignal ? "address_mode_classifier_detected" : "address_signal_detected" + }; + } + if (followupContext) { + return { + runAddressLane: true, + decision: "run_address_lane", + reason: "followup_context_detected" + }; + } + return { + runAddressLane: false, + decision: "skip_address_lane", + reason: "no_address_signal_after_l0" + }; +} class AssistantService { normalizerService; sessions; @@ -2313,12 +2852,13 @@ class AssistantService { }; this.sessions.appendItem(sessionId, userItem); const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => { + const safeAddressReply = String((0, answerComposer_1.sanitizeAssistantReplyForUserFacing)(addressLane.reply_text) ?? "").trim() || String(addressLane.reply_text ?? ""); const debug = buildAddressDebugPayload(addressLane.debug, llmPreDecomposeMeta); const assistantItem = { message_id: `msg-${(0, nanoid_1.nanoid)(10)}`, session_id: sessionId, role: "assistant", - text: addressLane.reply_text, + text: safeAddressReply, reply_type: addressLane.reply_type, created_at: new Date().toISOString(), trace_id: debug.trace_id, @@ -2350,6 +2890,10 @@ class AssistantService { address_llm_predecompose_provider: llmPreDecomposeMeta?.provider ?? null, address_llm_predecompose_trace_id: llmPreDecomposeMeta?.traceId ?? null, address_llm_predecompose_reason: llmPreDecomposeMeta?.reason ?? null, + address_fallback_rule_hit: llmPreDecomposeMeta?.fallbackRuleHit ?? null, + address_sanitized_user_message: llmPreDecomposeMeta?.sanitizedUserMessage ?? null, + address_tool_gate_decision: llmPreDecomposeMeta?.toolGateDecision ?? null, + address_tool_gate_reason: llmPreDecomposeMeta?.toolGateReason ?? null, detected_mode: addressLane.debug.detected_mode, query_shape: addressLane.debug.query_shape, detected_intent: addressLane.debug.detected_intent, @@ -2403,29 +2947,62 @@ class AssistantService { provider: payload?.llmProvider === "local" ? "local" : payload?.llmProvider === "openai" ? "openai" : null, traceId: null, effectiveMessage: userMessage, - reason: "disabled_by_feature_flag" + reason: "disabled_by_feature_flag", + fallbackRuleHit: null, + sanitizedUserMessage: sanitizeAddressMessageForFallback(userMessage), + toolGateDecision: null, + toolGateReason: null }; const addressInputMessage = toNonEmptyString(addressPreDecompose?.effectiveMessage) ?? userMessage; const carryover = resolveAddressFollowupCarryoverContext(userMessage, session.items); - const shouldPreferContextualLane = Boolean(carryover?.followupContext); - if (shouldPreferContextualLane) { - const contextualAddressLane = await this.addressQueryService.tryHandle(addressInputMessage, { - followupContext: carryover.followupContext + const toolGate = resolveAddressToolGateDecision(addressInputMessage, carryover?.followupContext ?? null); + const addressRuntimeMeta = { + ...addressPreDecompose, + toolGateDecision: toolGate.decision, + toolGateReason: toolGate.reason + }; + if (!toolGate.runAddressLane) { + (0, log_1.logJson)({ + timestamp: new Date().toISOString(), + level: "info", + service: "assistant_loop", + message: "assistant_address_tool_gate_skip", + sessionId, + details: { + session_id: sessionId, + user_message: userMessage, + effective_address_user_message: addressInputMessage, + address_llm_predecompose_attempted: Boolean(addressRuntimeMeta?.attempted), + address_llm_predecompose_applied: Boolean(addressRuntimeMeta?.applied), + address_llm_predecompose_reason: addressRuntimeMeta?.reason ?? null, + address_fallback_rule_hit: addressRuntimeMeta?.fallbackRuleHit ?? null, + address_sanitized_user_message: addressRuntimeMeta?.sanitizedUserMessage ?? null, + address_tool_gate_decision: addressRuntimeMeta?.toolGateDecision ?? null, + address_tool_gate_reason: addressRuntimeMeta?.toolGateReason ?? null + } }); - if (contextualAddressLane?.handled) { - return finalizeAddressLaneResponse(contextualAddressLane, addressInputMessage, carryover, addressPreDecompose); + } + if (toolGate.runAddressLane) { + const shouldPreferContextualLane = Boolean(carryover?.followupContext); + if (shouldPreferContextualLane) { + const contextualAddressLane = await this.addressQueryService.tryHandle(addressInputMessage, { + followupContext: carryover.followupContext + }); + if (contextualAddressLane?.handled) { + return finalizeAddressLaneResponse(contextualAddressLane, addressInputMessage, carryover, addressRuntimeMeta); + } } - } - const primaryAddressLane = await this.addressQueryService.tryHandle(addressInputMessage); - if (primaryAddressLane?.handled) { - return finalizeAddressLaneResponse(primaryAddressLane, addressInputMessage, null, addressPreDecompose); - } - if (!shouldPreferContextualLane && carryover?.followupContext) { - const contextualAddressLane = await this.addressQueryService.tryHandle(addressInputMessage, { - followupContext: carryover.followupContext - }); - if (contextualAddressLane?.handled) { - return finalizeAddressLaneResponse(contextualAddressLane, addressInputMessage, carryover, addressPreDecompose); + const primaryAddressLane = await this.addressQueryService.tryHandle(addressInputMessage); + if (primaryAddressLane?.handled) { + return finalizeAddressLaneResponse(primaryAddressLane, addressInputMessage, null, addressRuntimeMeta); + } + if (!shouldPreferContextualLane && carryover?.followupContext) { + const contextualAddressLane = await this.addressQueryService.tryHandle(addressInputMessage, { + followupContext: carryover.followupContext + }); + if (contextualAddressLane?.handled) { + return finalizeAddressLaneResponse(contextualAddressLane, addressInputMessage, carryover, addressRuntimeMeta); + } } } } diff --git a/llm_normalizer/backend/dist/services/openaiResponsesClient.js b/llm_normalizer/backend/dist/services/openaiResponsesClient.js index 3686563..e0eceef 100644 --- a/llm_normalizer/backend/dist/services/openaiResponsesClient.js +++ b/llm_normalizer/backend/dist/services/openaiResponsesClient.js @@ -104,6 +104,9 @@ function shouldFallbackToChatCompletions(error) { if (!(error instanceof http_1.ApiError)) { return false; } + if (error.code === "OPENAI_OUTPUT_PARSE_FAILED" || error.code === "OPENAI_NON_JSON_RESPONSE") { + return true; + } if (error.code !== "OPENAI_REQUEST_FAILED") { return false; } @@ -115,6 +118,31 @@ function shouldFallbackToChatCompletions(error) { const message = String(error.message ?? "").toLowerCase(); return message.includes("/responses") || message.includes("responses"); } +function extractModelErrorMessage(data) { + const rawError = data.error; + if (typeof rawError === "string" && rawError.trim().length > 0) { + return rawError.trim(); + } + if (rawError && typeof rawError === "object") { + const errorObj = rawError; + const message = errorObj.message; + if (typeof message === "string" && message.trim().length > 0) { + return message.trim(); + } + } + return null; +} +function isRouteMismatchErrorMessage(message) { + const source = String(message ?? "").toLowerCase(); + if (!source) { + return false; + } + return (/unexpected endpoint|unexpected route|unknown endpoint|unknown route|unsupported endpoint|unsupported route/.test(source) || + (/endpoint/.test(source) && /method/.test(source)) || + (/endpoint/.test(source) && /not found/.test(source)) || + (/route/.test(source) && /not found/.test(source)) || + (/path/.test(source) && /not found/.test(source))); +} function loadSchemaForTransport(schemaVersion) { const schemaFile = schemaVersion === "v1" ? "normalized_query_v1.json" @@ -331,9 +359,13 @@ class OpenAIResponsesClient { }); } } + const modelErrorMessage = extractModelErrorMessage(data); + if (modelErrorMessage && canFallbackToAlternativeBase && !isLastCandidate && isRouteMismatchErrorMessage(modelErrorMessage)) { + continue; + } if (!response.ok) { const errorObj = (data.error ?? {}); - throw new http_1.ApiError("OPENAI_REQUEST_FAILED", String(errorObj.message ?? `Model endpoint failed: ${response.status}`), response.status, { + throw new http_1.ApiError("OPENAI_REQUEST_FAILED", modelErrorMessage ?? String(errorObj.message ?? `Model endpoint failed: ${response.status}`), response.status, { route: routePath, url, status: response.status, @@ -341,6 +373,13 @@ class OpenAIResponsesClient { code: errorObj.code ?? null }); } + if (modelErrorMessage) { + throw new http_1.ApiError("OPENAI_REQUEST_FAILED", modelErrorMessage, 502, { + route: routePath, + url, + status: response.status + }); + } return data; } throw new http_1.ApiError("OPENAI_REQUEST_FAILED", "Model endpoint is unreachable.", 502, { diff --git a/llm_normalizer/backend/src/services/addressFilterExtractor.ts b/llm_normalizer/backend/src/services/addressFilterExtractor.ts index 1c6fe7c..30ab603 100644 --- a/llm_normalizer/backend/src/services/addressFilterExtractor.ts +++ b/llm_normalizer/backend/src/services/addressFilterExtractor.ts @@ -323,6 +323,11 @@ function cleanupAnchorValue(value: string): string { if (asOfTruncatedTailPattern.test(normalized)) { return normalized.replace(asOfTruncatedTailPattern, "").trim(); } + const periodEndTailPattern = + /\s+на\s+конец(?:\s+период[а-яё]*)?\s+(?:\d{1,4}[./-]\d{1,2}(?:[./-]\d{1,4})?|\d{4}|[a-zа-яё]+\s+\d{4})(?:\s+|$)[\s\S]*$/iu; + if (periodEndTailPattern.test(normalized)) { + return normalized.replace(periodEndTailPattern, "").trim(); + } // Remove trailing period qualifiers that can be swallowed by broad anchor regexes: // " с 2020-07-01 по 2020-07-31", " from 2020-07-01 to 2020-07-31" @@ -618,6 +623,10 @@ function requiredFiltersByIntent(intent: AddressIntent): Array 0 && contractCandidatesFromRows(filteredRows).length === 0) { - return buildLimitedExecutionResult({ - mode, - shape, - intent, - filters: filters.extracted_filters, - missingRequiredFilters: [], - selectedRecipe: recipeSelection.selected_recipe.recipe_id, - accountScopeMode: plan.account_scope_mode, - accountScopeFallbackApplied, - accountScopeAudit, - anchor, - matchFailureStage, - matchFailureReason, - mcpCallStatus: stageStatus, - rowsFetched: mcp.fetched_rows, - rawRowsReceived: mcp.raw_rows.length, - rowsAfterAccountScope: normalizedRows.length, - rowsAfterRecipeFilter: filterByAnchors.length, - rowsMaterialized: normalizedRows.length, - rowsMatched: filteredRows.length, - rawRowKeysSample: rowDiagnostics.rawRowKeysSample, - materializationDropReason: rowDiagnostics.materializationDropReason, - category: "recipe_visibility_gap", - reasonText: "в live строках нет договорных якорей для уверенного списка незакрытых договоров", - nextStep: "сузьте запрос по контрагенту или добавьте номер договора", - limitations: ["no_contract_anchors_in_live_rows"], - reasons: baseReasons - }); - } - if ( filteredRows.length === 0 && - isDocumentOrBankIntent(intent.intent) && + isAnchorRecoveryIntent(intent.intent) && (stageStatus === "materialized_but_not_anchor_matched" || stageStatus === "materialized_but_filtered_out_by_recipe") ) { const currentLimit = @@ -1201,6 +1172,82 @@ export class AddressQueryService { } }; } + if ( + intent.intent === "open_items_by_counterparty_or_contract" && + expandedFilteredRows.length === 0 && + expandedRowsByAnchor.length === 0 && + expandedNormalizedRows.length > 0 + ) { + const expandedFallbackRows = applyIntentSpecificFilter(intent.intent, expandedNormalizedRows); + if (expandedFallbackRows.length > 0) { + const expandedRowDiagnostics = deriveRowStageDiagnostics( + expandedMcp.raw_rows, + expandedNormalizedRows.length, + expandedNormalizedRows.length + ); + const expandedFactual = composeFactualReply(intent.intent, expandedFallbackRows); + const expandedLimitations = [ + ...filters.warnings, + "query_limit_auto_expanded_for_anchor_recovery", + "open_items_anchor_not_matched_fallback_rows" + ]; + const expandedReasons = [ + ...baseReasons, + "query_limit_auto_expanded_for_anchor_recovery", + "open_items_anchor_not_matched_fallback_rows" + ]; + return { + handled: true, + reply_text: + `Точный якорь не подтвердился в live-строках даже после расширения до ${expandedPlan.limit}; ` + + "показаны ближайшие доступные позиции по счетам 60/62/76.\n" + + expandedFactual.text, + reply_type: inferReplyType(expandedFactual.responseType), + response_type: expandedFactual.responseType, + debug: { + detected_mode: mode.mode, + detected_mode_confidence: mode.confidence, + query_shape: shape.shape, + query_shape_confidence: shape.confidence, + detected_intent: intent.intent, + detected_intent_confidence: intent.confidence, + extracted_filters: filters.extracted_filters, + missing_required_filters: [], + selected_recipe: expandedSelection.selected_recipe.recipe_id, + mcp_call_status_legacy: "matched_non_empty", + account_scope_mode: expandedPlan.account_scope_mode, + account_scope_fallback_applied: expandedAccountScopeFallbackApplied, + anchor_type: expandedAnchor.anchor_type, + anchor_value_raw: expandedAnchor.anchor_value_raw, + anchor_value_resolved: expandedAnchor.anchor_value_resolved, + resolver_confidence: expandedAnchor.resolver_confidence, + ambiguity_count: expandedAnchor.ambiguity_count, + match_failure_stage: "materialized_but_not_anchor_matched", + match_failure_reason: + expandedAnchorFilter.mismatchReason ?? "anchor_not_matched_after_query_limit_expansion", + mcp_call_status: "matched_non_empty", + rows_fetched: expandedMcp.fetched_rows, + raw_rows_received: expandedMcp.raw_rows.length, + rows_after_account_scope: expandedNormalizedRows.length, + rows_after_recipe_filter: expandedRowsByAnchor.length, + rows_materialized: expandedNormalizedRows.length, + rows_matched: expandedFallbackRows.length, + raw_row_keys_sample: expandedRowDiagnostics.rawRowKeysSample, + materialization_drop_reason: expandedRowDiagnostics.materializationDropReason, + account_token_raw: expandedAccountScopeAudit.accountTokenRaw, + account_token_normalized: expandedAccountScopeAudit.accountTokenNormalized, + account_scope_fields_checked: expandedAccountScopeAudit.accountScopeFieldsChecked, + account_scope_match_strategy: expandedAccountScopeAudit.accountScopeMatchStrategy, + account_scope_drop_reason: expandedAccountScopeAudit.accountScopeDropReason, + runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS", + limited_reason_category: null, + response_type: expandedFactual.responseType, + limitations: expandedLimitations, + reasons: expandedReasons + } + }; + } + } } } } @@ -1313,6 +1360,67 @@ export class AddressQueryService { } } + if ( + filteredRows.length === 0 && + intent.intent === "open_items_by_counterparty_or_contract" && + normalizedRows.length > 0 + ) { + const openItemsFallbackRows = applyIntentSpecificFilter(intent.intent, normalizedRows); + if (openItemsFallbackRows.length > 0) { + const fallbackFactual = composeFactualReply(intent.intent, openItemsFallbackRows); + const fallbackLimitations = [...filters.warnings, "open_items_anchor_not_matched_fallback_rows"]; + const fallbackReasons = [...baseReasons, "open_items_anchor_not_matched_fallback_rows"]; + return { + handled: true, + reply_text: + "Точный якорь не подтвердился в текущем окне live-данных; показаны ближайшие доступные позиции по счетам 60/62/76.\n" + + fallbackFactual.text, + reply_type: inferReplyType(fallbackFactual.responseType), + response_type: fallbackFactual.responseType, + debug: { + detected_mode: mode.mode, + detected_mode_confidence: mode.confidence, + query_shape: shape.shape, + query_shape_confidence: shape.confidence, + detected_intent: intent.intent, + detected_intent_confidence: intent.confidence, + extracted_filters: filters.extracted_filters, + missing_required_filters: [], + selected_recipe: recipeSelection.selected_recipe.recipe_id, + mcp_call_status_legacy: "matched_non_empty", + account_scope_mode: plan.account_scope_mode, + account_scope_fallback_applied: accountScopeFallbackApplied, + anchor_type: anchor.anchor_type, + anchor_value_raw: anchor.anchor_value_raw, + anchor_value_resolved: anchor.anchor_value_resolved, + resolver_confidence: anchor.resolver_confidence, + ambiguity_count: anchor.ambiguity_count, + match_failure_stage: matchFailureStage, + match_failure_reason: matchFailureReason, + mcp_call_status: "matched_non_empty", + rows_fetched: mcp.fetched_rows, + raw_rows_received: mcp.raw_rows.length, + rows_after_account_scope: normalizedRows.length, + rows_after_recipe_filter: filterByAnchors.length, + rows_materialized: normalizedRows.length, + rows_matched: openItemsFallbackRows.length, + raw_row_keys_sample: rowDiagnostics.rawRowKeysSample, + materialization_drop_reason: rowDiagnostics.materializationDropReason, + account_token_raw: accountScopeAudit.accountTokenRaw, + account_token_normalized: accountScopeAudit.accountTokenNormalized, + account_scope_fields_checked: accountScopeAudit.accountScopeFieldsChecked, + account_scope_match_strategy: accountScopeAudit.accountScopeMatchStrategy, + account_scope_drop_reason: accountScopeAudit.accountScopeDropReason, + runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS", + limited_reason_category: null, + response_type: fallbackFactual.responseType, + limitations: fallbackLimitations, + reasons: fallbackReasons + } + }; + } + } + if (filteredRows.length === 0) { const hadBaseRows = normalizedRows.length > 0 || mcp.fetched_rows > 0; const hadAnchorMatchedRows = filterByAnchors.length > 0; diff --git a/llm_normalizer/backend/src/services/addressRecipeCatalog.ts b/llm_normalizer/backend/src/services/addressRecipeCatalog.ts index 212b5af..bbdae1a 100644 --- a/llm_normalizer/backend/src/services/addressRecipeCatalog.ts +++ b/llm_normalizer/backend/src/services/addressRecipeCatalog.ts @@ -280,7 +280,9 @@ function maxLimitForIntent(intent: AddressIntent): number { intent === "list_documents_by_counterparty" || intent === "bank_operations_by_counterparty" || intent === "list_documents_by_contract" || - intent === "bank_operations_by_contract" + intent === "bank_operations_by_contract" || + intent === "open_items_by_counterparty_or_contract" || + intent === "list_open_contracts" ) { return ADDRESS_MAX_LIMIT_EXTENDED; } diff --git a/llm_normalizer/backend/src/services/address_runtime/composeStage.ts b/llm_normalizer/backend/src/services/address_runtime/composeStage.ts index 4a80591..2a2cb50 100644 --- a/llm_normalizer/backend/src/services/address_runtime/composeStage.ts +++ b/llm_normalizer/backend/src/services/address_runtime/composeStage.ts @@ -85,7 +85,12 @@ export function composeFactualReply( `Строк движения: ${rows.length}.`, `Договорных кандидатов: ${contracts.length}.` ]; - lines.push(...contracts.slice(0, 8).map((item, index) => `${index + 1}. ${item}`)); + if (contracts.length > 0) { + lines.push(...contracts.slice(0, 8).map((item, index) => `${index + 1}. ${item}`)); + } else { + lines.push("Договорные якоря в live-строках не выделены; показаны связанные движения как fallback."); + lines.push(...formatTopRows(rows, 6)); + } return { responseType: "FACTUAL_LIST", text: lines.join("\n") diff --git a/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts b/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts index 616a11f..13bf38e 100644 --- a/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts +++ b/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts @@ -138,6 +138,11 @@ describe("address intent resolver expansion (M2.3a)", () => { expect(result.intent).toBe("open_items_by_counterparty_or_contract"); }); + it("resolves unclosed contracts list query without specific anchor", () => { + const result = resolveAddressIntent("Покажи незакрытые договоры на 2020-12-31"); + expect(result.intent).toBe("list_open_contracts"); + }); + it("resolves bank operations by contract for normalized phrase with linked contract wording", () => { const result = resolveAddressIntent( "Показать банковские операции (счета 51, 60, 62) связанные с договором 15/24." @@ -268,6 +273,18 @@ describe("address filter extraction for balance drilldown", () => { expect(result.warnings).toContain("period_derived_from_month_phrase"); }); + it("cuts period-end tail from counterparty anchor and keeps as_of for open-items query", () => { + const result = extractAddressFilters( + "Покажи хвосты по контрагенту СВК на конец периода 2020-12-31", + "open_items_by_counterparty_or_contract" + ); + expect(result.extracted_filters.counterparty).toBe("СВК"); + expect(result.extracted_filters.as_of_date).toBe("2020-12-31"); + expect(result.extracted_filters.period_from).toBeUndefined(); + expect(result.extracted_filters.period_to).toBeUndefined(); + expect(result.warnings).toContain("period_window_cleared_for_as_of_intent"); + }); + it("derives month period for balance snapshot from 'на май 2020'", () => { const result = extractAddressFilters("Какой остаток по счету 60 на май 2020", "account_balance_snapshot"); expect(result.extracted_filters.account).toBe("60"); @@ -703,6 +720,34 @@ describe("address recipe catalog counterparty filtering", () => { expect(plan.limit).toBe(200); }); + it("allows extended limit for open-items by contract intent", () => { + const selected = selectAddressRecipe("open_items_by_counterparty_or_contract", { + contract: "15/24", + limit: 1000 + }); + expect(selected.selected_recipe).toBeTruthy(); + const plan = buildAddressRecipePlan(selected.selected_recipe!, { + contract: "15/24", + limit: 1000 + }); + + expect(plan.limit).toBe(1000); + }); + + it("allows extended limit for open-contracts intent", () => { + const selected = selectAddressRecipe("list_open_contracts", { + as_of_date: "2020-12-31", + limit: 1000 + }); + expect(selected.selected_recipe).toBeTruthy(); + const plan = buildAddressRecipePlan(selected.selected_recipe!, { + as_of_date: "2020-12-31", + limit: 1000 + }); + + expect(plan.limit).toBe(1000); + }); + it("injects account condition into movements query for account snapshot", () => { const filters = extractAddressFilters( "Какой остаток по счету 60 на дату 2020-07-31", diff --git a/scripts/run_address_live_slang_stress.py b/scripts/run_address_live_slang_stress.py index 012b466..f3d7f6f 100644 --- a/scripts/run_address_live_slang_stress.py +++ b/scripts/run_address_live_slang_stress.py @@ -23,6 +23,7 @@ class QuestionCase: text: str expected_intent: str | None expected_mode: str | None + expected_reply_type: str | None session: str | None @@ -48,6 +49,12 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--max-output-tokens", type=int, default=900) parser.add_argument("--timeout-sec", type=int, default=120) parser.add_argument("--run-id", default="") + parser.add_argument( + "--strict-policy", + default="route", + choices=["semantic", "route", "factual"], + help="Pass policy: semantic=intent/mode only, route=semantic+non-blocked route, factual=semantic+factual reply", + ) parser.add_argument( "--output-root", default=str(PROJECT_ROOT / "docs" / "ADDRESS" / "runs"), @@ -61,7 +68,7 @@ def now_stamp() -> str: def load_cases(path: Path) -> list[QuestionCase]: - raw = json.loads(path.read_text(encoding="utf-8")) + raw = json.loads(path.read_text(encoding="utf-8-sig")) if not isinstance(raw, list): raise ValueError("questions-file must contain JSON array") @@ -77,6 +84,7 @@ def load_cases(path: Path) -> list[QuestionCase]: text=text, expected_intent=None, expected_mode="address_query", + expected_reply_type=None, session=None, ) ) @@ -91,6 +99,7 @@ def load_cases(path: Path) -> list[QuestionCase]: case_id = str(item.get("id", f"Q{idx:03d}")).strip() or f"Q{idx:03d}" expected_intent = item.get("expected_intent") expected_mode = item.get("expected_mode", "address_query") + expected_reply_type = item.get("expected_reply_type") session = item.get("session") cases.append( QuestionCase( @@ -98,6 +107,7 @@ def load_cases(path: Path) -> list[QuestionCase]: text=text, expected_intent=str(expected_intent).strip() if expected_intent else None, expected_mode=str(expected_mode).strip() if expected_mode else None, + expected_reply_type=str(expected_reply_type).strip() if expected_reply_type else None, session=str(session).strip() if session else None, ) ) @@ -124,6 +134,42 @@ def post_json(url: str, payload: dict[str, Any], timeout_sec: int) -> tuple[int, return status, {"ok": False, "error": {"code": "HTTP_ERROR", "message": raw}} +def first_line(text: str | None) -> str: + value = str(text or "").strip() + if not value: + return "" + return value.splitlines()[0].strip() + + +def classify_route_health( + *, + status_code: int, + ok_flag: bool, + reply_type: str | None, + limited_reason_category: str | None, + mcp_call_status: str | None, +) -> str: + if status_code != 200 or not ok_flag: + return "http_or_backend_error" + if reply_type == "clarification_required": + return "blocked_clarification" + if reply_type == "backend_error": + return "blocked_backend_error" + if reply_type != "partial_coverage": + return "ok_or_factual" + if limited_reason_category == "missing_anchor": + return "blocked_missing_anchor" + if limited_reason_category == "unsupported": + return "blocked_unsupported" + if limited_reason_category == "recipe_visibility_gap": + return "blocked_recipe_visibility_gap" + if limited_reason_category == "execution_error": + return "blocked_execution_error" + if mcp_call_status in {"skipped", "materialized_but_not_anchor_matched", "materialized_but_filtered_out_by_recipe"}: + return "likely_blocked_route" + return "partial_non_blocking" + + def main() -> None: args = parse_args() questions_path = Path(args.questions_file).resolve() @@ -179,7 +225,23 @@ def main() -> None: intent_match = case.expected_intent is None or actual_intent == case.expected_intent mode_match = case.expected_mode is None or actual_mode == case.expected_mode + reply_match = case.expected_reply_type is None or reply_type == case.expected_reply_type semantic_pass = bool(intent_match and mode_match and status_code == 200 and ok_flag) + route_health = classify_route_health( + status_code=status_code, + ok_flag=ok_flag, + reply_type=str(reply_type) if reply_type is not None else None, + limited_reason_category=debug.get("limited_reason_category"), + mcp_call_status=debug.get("mcp_call_status"), + ) + route_pass = bool(semantic_pass and not route_health.startswith("blocked") and route_health != "likely_blocked_route") + if args.strict_policy == "semantic": + policy_pass = semantic_pass + elif args.strict_policy == "factual": + policy_pass = semantic_pass if case.expected_reply_type is not None else bool(semantic_pass and reply_type == "factual") + else: + policy_pass = route_pass + strict_pass = bool(policy_pass and reply_match) row = { "index": index, @@ -193,18 +255,32 @@ def main() -> None: "reply_type": reply_type, "trace_id": trace_id, "assistant_reply": body.get("assistant_reply") if isinstance(body, dict) else None, + "assistant_reply_first_line": first_line(body.get("assistant_reply") if isinstance(body, dict) else None), "expected_intent": case.expected_intent, "actual_intent": actual_intent, "intent_match": intent_match, "expected_mode": case.expected_mode, "actual_mode": actual_mode, "mode_match": mode_match, + "expected_reply_type": case.expected_reply_type, + "reply_match": reply_match, "semantic_pass": semantic_pass, + "route_pass": route_pass, + "route_health": route_health, + "strict_policy": args.strict_policy, + "strict_pass": strict_pass, + "selected_recipe": debug.get("selected_recipe"), + "missing_required_filters": debug.get("missing_required_filters"), + "match_failure_stage": debug.get("match_failure_stage"), + "match_failure_reason": debug.get("match_failure_reason"), + "rows_fetched": debug.get("rows_fetched"), + "rows_matched": debug.get("rows_matched"), "mcp_call_status": debug.get("mcp_call_status"), "limited_reason_category": debug.get("limited_reason_category"), "llm_decomposition_applied": debug.get("llm_decomposition_applied"), "llm_decomposition_reason": debug.get("llm_decomposition_reason"), "fallback_rule_hit": debug.get("fallback_rule_hit"), + "debug_payload": debug, "error_code": body.get("error", {}).get("code") if isinstance(body, dict) and isinstance(body.get("error"), dict) else None, "error_message": body.get("error", {}).get("message") if isinstance(body, dict) and isinstance(body.get("error"), dict) else None, } @@ -212,7 +288,7 @@ def main() -> None: print( f"[{index:03d}/{len(cases):03d}] {case.id} | status={status_code} reply={reply_type} " - f"intent={actual_intent} mode={actual_mode} pass={semantic_pass}" + f"intent={actual_intent} mode={actual_mode} semantic={semantic_pass} route={route_pass} strict={strict_pass} health={route_health}" ) reply_counter = Counter(str(r.get("reply_type")) for r in rows) @@ -220,8 +296,11 @@ def main() -> None: mode_counter = Counter(str(r.get("actual_mode")) for r in rows) mcp_counter = Counter(str(r.get("mcp_call_status")) for r in rows) limited_counter = Counter(str(r.get("limited_reason_category")) for r in rows if r.get("limited_reason_category") is not None) + route_health_counter = Counter(str(r.get("route_health")) for r in rows) semantic_pass_count = sum(1 for r in rows if r.get("semantic_pass")) + route_pass_count = sum(1 for r in rows if r.get("route_pass")) + strict_pass_count = sum(1 for r in rows if r.get("strict_pass")) factual_count = sum(1 for r in rows if r.get("reply_type") == "factual") ok_200_count = sum(1 for r in rows if r.get("status_code") == 200 and r.get("ok")) llm_decomposition_applied_count = sum(1 for r in rows if r.get("llm_decomposition_applied") is True) @@ -235,11 +314,16 @@ def main() -> None: "llm_provider": args.llm_provider, "llm_model": args.llm_model, "llm_base_url": args.llm_base_url, + "strict_policy": args.strict_policy, "totals": { "questions_total": len(rows), "ok_200_count": ok_200_count, "semantic_pass_count": semantic_pass_count, "semantic_pass_rate": round(semantic_pass_count / len(rows), 4) if rows else 0.0, + "route_pass_count": route_pass_count, + "route_pass_rate": round(route_pass_count / len(rows), 4) if rows else 0.0, + "strict_pass_count": strict_pass_count, + "strict_pass_rate": round(strict_pass_count / len(rows), 4) if rows else 0.0, "factual_count": factual_count, "partial_coverage_count": sum(1 for r in rows if r.get("reply_type") == "partial_coverage"), "clarification_required_count": sum(1 for r in rows if r.get("reply_type") == "clarification_required"), @@ -253,13 +337,14 @@ def main() -> None: "actual_mode": dict(mode_counter), "mcp_call_status": dict(mcp_counter), "limited_reason_category": dict(limited_counter), + "route_health": dict(route_health_counter), }, } failures = [ r for r in rows - if not r.get("semantic_pass") + if not r.get("strict_pass") or r.get("status_code") != 200 or r.get("reply_type") in {"clarification_required", "backend_error"} ] @@ -279,12 +364,17 @@ def main() -> None: f"Questions file: {questions_path}", f"Backend URL: {args.backend_url}", f"LLM: {args.llm_provider} / {args.llm_model} @ {args.llm_base_url}", + f"Strict policy: {args.strict_policy}", "", "## Totals", f"- questions_total: {summary['totals']['questions_total']}", f"- ok_200_count: {summary['totals']['ok_200_count']}", f"- semantic_pass_count: {summary['totals']['semantic_pass_count']}", f"- semantic_pass_rate: {summary['totals']['semantic_pass_rate']}", + f"- route_pass_count: {summary['totals']['route_pass_count']}", + f"- route_pass_rate: {summary['totals']['route_pass_rate']}", + f"- strict_pass_count: {summary['totals']['strict_pass_count']}", + f"- strict_pass_rate: {summary['totals']['strict_pass_rate']}", f"- factual_count: {summary['totals']['factual_count']}", f"- partial_coverage_count: {summary['totals']['partial_coverage_count']}", f"- clarification_required_count: {summary['totals']['clarification_required_count']}", @@ -299,10 +389,25 @@ def main() -> None: ] (run_dir / "README.md").write_text("\n".join(lines) + "\n", encoding="utf-8") + audit_lines = [ + f"# Response Audit: {run_id}", + "", + "| id | strict | route_health | reply_type | intent | limited_reason | question | assistant_first_line |", + "|---|---|---|---|---|---|---|---|", + ] + for row in rows: + audit_lines.append( + f"| {row.get('id')} | {row.get('strict_pass')} | {row.get('route_health')} | {row.get('reply_type')} | " + f"{row.get('actual_intent')} | {row.get('limited_reason_category')} | " + f"{str(row.get('question', '')).replace('|', '/')} | {str(row.get('assistant_reply_first_line', '')).replace('|', '/')} |" + ) + (run_dir / "response_audit.md").write_text("\n".join(audit_lines) + "\n", encoding="utf-8") + print(f"\nRun directory: {run_dir}") print(f"Semantic pass: {semantic_pass_count}/{len(rows)}") + print(f"Route pass: {route_pass_count}/{len(rows)}") + print(f"Strict pass ({args.strict_policy}): {strict_pass_count}/{len(rows)}") if __name__ == "__main__": main() -