АДРЕСНЫЙ РЕЖИМ - стабилизация LLM fallback якорей и периода
This commit is contained in:
parent
2d95ce6332
commit
7dd6607ded
|
|
@ -0,0 +1,25 @@
|
||||||
|
"idx","question","reply_type","trace_id","detected_intent","query_shape","mcp_call_status","rows_matched","limited_reason_category","llm_decomposition_applied","llm_decomposition_reason","fallback_rule_hit","tool_gate_decision","runtime_readiness","period_from","period_to","as_of_date","anchor_type","anchor_value_raw","anchor_value_resolved","assistant_reply_head"
|
||||||
|
"1","СЃРІРє РґРѕРєРё Р·Р° 20РіРѕРґ покеж","factual","address-sugd3wlUri","list_documents_by_counterparty","DOCUMENT_LIST","matched_non_empty","3","","True","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-01-01","2020-12-31","","counterparty","свк","Группа СВК","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"2","СЃРІРє 20 РіРѕРґ - покажи РґРѕРєРё плс","factual","address--4sSq56ihd","list_documents_by_counterparty","DOCUMENT_LIST","matched_non_empty","3","","True","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-01-01","2020-12-31","","counterparty","свк","Группа СВК","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"3","что РїРѕ СЃРІРє Р·Р° 2020 РіРѕРґ выведи РІСЃРµ РґРѕРєРё плиз что есть","partial_coverage","address-MUkQr3-8cm","list_documents_by_counterparty","DOCUMENT_LIST","materialized_but_not_anchor_matched","0","missing_anchor","True","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-01-01","2020-12-31","","counterparty","что","что","Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
"4","какие документы РїРѕ контрагенту СЃРІРє Р·Р° РІСЃРµ время","factual","address-vfIW3ugGJ3","list_documents_by_counterparty","DOCUMENT_LIST","matched_non_empty","26","","True","fallback_rule_applied_after_llm_error","documents_counterparty_all_time_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","","","","counterparty","свк","Группа СВК","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"5","РґРѕРєРё РїРѕ СЃРІРє СЃ 01.07.2020 РїРѕ 31.07.2020","factual","address-OheS6P-gDn","list_documents_by_counterparty","DOCUMENT_LIST","matched_non_empty","2","","True","fallback_rule_applied_after_llm_error","documents_counterparty_month_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-07-01","2020-07-31","","counterparty","свк","Группа СВК","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"6","СЃРІРє июль 2020 какие РґРѕРєРё есть","factual","address-DCuYSZOqzh","list_documents_by_counterparty","DOCUMENT_LIST","matched_non_empty","2","","True","fallback_rule_applied_after_llm_error","documents_counterparty_month_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-07-01","2020-07-31","","counterparty","свк","Группа СВК","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"7","покажи сальдо РїРѕ 60.01 РЅР° 31.07.20","factual","address-aW8e72ada6","account_balance_snapshot","AGGREGATE_LOOKUP","matched_non_empty","200","","True","fallback_rule_applied_after_llm_error","balance_account_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","","","2026-04-01","account","60.01","60.01","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"8","остаток 60 на 2020.05","factual","address-o-EiCUq_sW","account_balance_snapshot","AGGREGATE_LOOKUP","matched_non_empty","6","","True","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-05-01","2020-05-31","2020-05-31","account","60","60","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"9","какой остаток по счету 60 на 2020 май","factual","address-jjAFfbUg3l","account_balance_snapshot","AGGREGATE_LOOKUP","matched_non_empty","6","","True","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-05-01","2020-05-31","2020-05-31","account","60","60","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"10","60 счет остаток РЅР° май 2020 покажи","factual","address-zp57Rg1ax2","account_balance_snapshot","AGGREGATE_LOOKUP","matched_non_empty","6","","True","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-05-01","2020-05-31","2020-05-31","account","60","60","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"11","какой остаток по счету 60 на 2020-05-31","factual","address-UdMlO0oihD","account_balance_snapshot","AGGREGATE_LOOKUP","matched_non_empty","6","","True","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-05-01","2020-05-31","2020-05-31","account","60","60","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"12","остаток по 60 на май 2020, не за весь 2020","factual","address-C9kv5RSpvX","account_balance_snapshot","AGGREGATE_LOOKUP","matched_non_empty","6","","True","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-05-01","2020-05-31","2020-05-31","account","60","60","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"13","покаж РїРѕ РґРѕРіРѕРІРѕСЂСѓ 1-ПМ/2020 РґРѕРєРё Р·Р° 2020","factual","address-nAM6RcOew7","list_documents_by_contract","DOCUMENT_LIST","matched_non_empty","233","","True","fallback_rule_applied_after_llm_error","documents_contract_year_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-01-01","2020-12-31","","contract","1-","1-","Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
"14","какие документы РїРѕ РґРѕРіРѕРІРѕСЂСѓ 1-ПМ/2020 Р·Р° РІСЃРµ время","factual","address-0KPk8HnFoG","list_documents_by_contract","DOCUMENT_LIST","matched_non_empty","120","","True","fallback_rule_applied_after_llm_error","documents_contract_all_time_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","","","","contract","1-","1-","Собран список документов по договору (live address lane)."
|
||||||
|
"15","есть ли долг РїРѕ РґРѕРіРѕРІРѕСЂСѓ 1-ПМ/2020 РЅР° 2020-07-31","partial_coverage","address-NAspBEx-B2","account_balance_snapshot","AGGREGATE_LOOKUP","no_raw_rows","0","empty_match","True","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-07-01","2020-07-31","2020-07-31","account","07","07","В live-данных по текущему фильтру записи не найдены."
|
||||||
|
"16","какие хвосты РїРѕ РґРѕРіРѕРІРѕСЂСѓ 1-ПМ/2020 РЅР° дату 31.07.2020","partial_coverage","address-9UG7ycCWej","account_balance_snapshot","UNKNOWN","no_raw_rows","0","empty_match","False","error:Failed to extract output_text from /responses payload.","","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-07-01","2020-07-31","2020-07-31","account","07","07","В live-данных по текущему фильтру записи не найдены."
|
||||||
|
"17","какие платежи были РїРѕ СЃРІРє РІ 2020","partial_coverage","address-db6MWzmaK3","list_documents_by_counterparty","DOCUMENT_LIST","materialized_but_not_anchor_matched","0","missing_anchor","True","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-01-01","2020-12-31","","counterparty","были","были","Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
"18","были ли поступления РѕС‚ СЃРІРє Р·Р° июль 2020","partial_coverage","address-kj2wxmasp5","list_documents_by_counterparty","DOCUMENT_LIST","materialized_but_not_anchor_matched","0","missing_anchor","True","fallback_rule_applied_after_llm_error","documents_counterparty_month_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-07-01","2020-07-31","","counterparty","были","были","Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
"19","покажи списания СЃ расчетного счета РїРѕ СЃРІРє Р·Р° 2020","partial_coverage","address-5ZApJI8tq_","bank_operations_by_counterparty","DOCUMENT_LIST","materialized_but_not_anchor_matched","0","missing_anchor","True","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-01-01","2020-12-31","","counterparty","списания","списания","Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
"20","СЃРІРє Р·Р° 2020 покаж РІСЃРµ поступления","factual","address-b8vcHTo3Mb","list_documents_by_counterparty","DOCUMENT_LIST","matched_non_empty","3","","True","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-01-01","2020-12-31","","counterparty","свк","Группа СВК","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"21","покажи документы РїРѕ СЃРІРє Р·Р° 2020","factual","address-3plzT4WuiP","list_documents_by_counterparty","DOCUMENT_LIST","matched_non_empty","3","","True","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-01-01","2020-12-31","","counterparty","свк","Группа СВК","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"22","а теперь только за май 2020","factual","address-ZAbNn9yMro","list_documents_by_counterparty","UNKNOWN","matched_non_empty","26","","False","not_address_like","","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-05-01","2020-05-31","","counterparty","свк","Группа СВК","По окну 2020-05-01..2020-05-31 строк не найдено; показаны ближайшие доступные данные 2020-07-27..2021-11-10."
|
||||||
|
"23","а по счету 60.01 на ту же дату","factual","address-8AhrNbM9OG","account_balance_snapshot","UNKNOWN","matched_non_empty","5","","False","error:Failed to extract output_text from /responses payload.","","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2020-05-01","2020-05-31","2020-05-31","account","60.01","60.01","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"24","бля епт покажи РґРѕРєРё РїРѕ СЃРІРє Р·Р° 20Р№","factual","address-t3_otb_RVf","list_documents_by_counterparty","DOCUMENT_LIST","matched_non_empty","26","","True","fallback_rule_applied_after_llm_error","documents_counterparty_rewrite","run_address_lane","LIVE_QUERYABLE_WITH_LIMITS","2026-01-01","2026-04-01","","counterparty","свк","Группа СВК","По окну 2026-01-01..2026-04-01 строк не найдено; показаны ближайшие доступные данные 2020-07-27..2021-11-10."
|
||||||
|
|
|
@ -0,0 +1,554 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"question": "свк доки за 20год покеж",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-sugd3wlUri",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-01-01",
|
||||||
|
"period_to": "2020-12-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"question": "свк 20 год - покажи доки плс",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address--4sSq56ihd",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-01-01",
|
||||||
|
"period_to": "2020-12-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"question": "что по свк за 2020 год выведи все доки плиз что есть",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"trace_id": "address-MUkQr3-8cm",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "materialized_but_not_anchor_matched",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"limited_reason_category": "missing_anchor",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-01-01",
|
||||||
|
"period_to": "2020-12-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "что",
|
||||||
|
"anchor_value_resolved": "что",
|
||||||
|
"assistant_reply_head": "Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 4,
|
||||||
|
"question": "какие документы по контрагенту свк за все время",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-vfIW3ugGJ3",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 26,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_all_time_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "",
|
||||||
|
"period_to": "",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"question": "РґРѕРєРё РїРѕ СЃРІРє СЃ 01.07.2020 РїРѕ 31.07.2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-OheS6P-gDn",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 2,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_month_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-07-01",
|
||||||
|
"period_to": "2020-07-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 6,
|
||||||
|
"question": "свк июль 2020 какие доки есть",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-DCuYSZOqzh",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 2,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_month_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-07-01",
|
||||||
|
"period_to": "2020-07-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 7,
|
||||||
|
"question": "покажи сальдо по 60.01 на 31.07.20",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-aW8e72ada6",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"query_shape": "AGGREGATE_LOOKUP",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 200,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_account_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "",
|
||||||
|
"period_to": "",
|
||||||
|
"as_of_date": "2026-04-01",
|
||||||
|
"anchor_type": "account",
|
||||||
|
"anchor_value_raw": "60.01",
|
||||||
|
"anchor_value_resolved": "60.01",
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 8,
|
||||||
|
"question": "остаток 60 на 2020.05",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-o-EiCUq_sW",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"query_shape": "AGGREGATE_LOOKUP",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-05-01",
|
||||||
|
"period_to": "2020-05-31",
|
||||||
|
"as_of_date": "2020-05-31",
|
||||||
|
"anchor_type": "account",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"question": "какой остаток по счету 60 на 2020 май",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-jjAFfbUg3l",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"query_shape": "AGGREGATE_LOOKUP",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-05-01",
|
||||||
|
"period_to": "2020-05-31",
|
||||||
|
"as_of_date": "2020-05-31",
|
||||||
|
"anchor_type": "account",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 10,
|
||||||
|
"question": "60 счет остаток на май 2020 покажи",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-zp57Rg1ax2",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"query_shape": "AGGREGATE_LOOKUP",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-05-01",
|
||||||
|
"period_to": "2020-05-31",
|
||||||
|
"as_of_date": "2020-05-31",
|
||||||
|
"anchor_type": "account",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 11,
|
||||||
|
"question": "какой остаток по счету 60 на 2020-05-31",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-UdMlO0oihD",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"query_shape": "AGGREGATE_LOOKUP",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-05-01",
|
||||||
|
"period_to": "2020-05-31",
|
||||||
|
"as_of_date": "2020-05-31",
|
||||||
|
"anchor_type": "account",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 12,
|
||||||
|
"question": "остаток по 60 на май 2020, не за весь 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-C9kv5RSpvX",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"query_shape": "AGGREGATE_LOOKUP",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-05-01",
|
||||||
|
"period_to": "2020-05-31",
|
||||||
|
"as_of_date": "2020-05-31",
|
||||||
|
"anchor_type": "account",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 13,
|
||||||
|
"question": "покаж РїРѕ РґРѕРіРѕРІРѕСЂСѓ 1-ПМ/2020 РґРѕРєРё Р·Р° 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-nAM6RcOew7",
|
||||||
|
"detected_intent": "list_documents_by_contract",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 233,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_contract_year_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-01-01",
|
||||||
|
"period_to": "2020-12-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "contract",
|
||||||
|
"anchor_value_raw": "1-",
|
||||||
|
"anchor_value_resolved": "1-",
|
||||||
|
"assistant_reply_head": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 14,
|
||||||
|
"question": "какие документы РїРѕ РґРѕРіРѕРІРѕСЂСѓ 1-ПМ/2020 Р·Р° РІСЃРµ время",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-0KPk8HnFoG",
|
||||||
|
"detected_intent": "list_documents_by_contract",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 120,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_contract_all_time_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "",
|
||||||
|
"period_to": "",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "contract",
|
||||||
|
"anchor_value_raw": "1-",
|
||||||
|
"anchor_value_resolved": "1-",
|
||||||
|
"assistant_reply_head": "Собран список документов по договору (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 15,
|
||||||
|
"question": "есть ли долг РїРѕ РґРѕРіРѕРІРѕСЂСѓ 1-ПМ/2020 РЅР° 2020-07-31",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"trace_id": "address-NAspBEx-B2",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"query_shape": "AGGREGATE_LOOKUP",
|
||||||
|
"mcp_call_status": "no_raw_rows",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"limited_reason_category": "empty_match",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-07-01",
|
||||||
|
"period_to": "2020-07-31",
|
||||||
|
"as_of_date": "2020-07-31",
|
||||||
|
"anchor_type": "account",
|
||||||
|
"anchor_value_raw": "07",
|
||||||
|
"anchor_value_resolved": "07",
|
||||||
|
"assistant_reply_head": "В live-данных по текущему фильтру записи не найдены."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 16,
|
||||||
|
"question": "какие хвосты РїРѕ РґРѕРіРѕРІРѕСЂСѓ 1-ПМ/2020 РЅР° дату 31.07.2020",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"trace_id": "address-9UG7ycCWej",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"query_shape": "UNKNOWN",
|
||||||
|
"mcp_call_status": "no_raw_rows",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"limited_reason_category": "empty_match",
|
||||||
|
"llm_decomposition_applied": false,
|
||||||
|
"llm_decomposition_reason": "error:Failed to extract output_text from /responses payload.",
|
||||||
|
"fallback_rule_hit": "",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-07-01",
|
||||||
|
"period_to": "2020-07-31",
|
||||||
|
"as_of_date": "2020-07-31",
|
||||||
|
"anchor_type": "account",
|
||||||
|
"anchor_value_raw": "07",
|
||||||
|
"anchor_value_resolved": "07",
|
||||||
|
"assistant_reply_head": "В live-данных по текущему фильтру записи не найдены."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 17,
|
||||||
|
"question": "какие платежи были по свк в 2020",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"trace_id": "address-db6MWzmaK3",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "materialized_but_not_anchor_matched",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"limited_reason_category": "missing_anchor",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-01-01",
|
||||||
|
"period_to": "2020-12-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "были",
|
||||||
|
"anchor_value_resolved": "были",
|
||||||
|
"assistant_reply_head": "Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 18,
|
||||||
|
"question": "были ли поступления от свк за июль 2020",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"trace_id": "address-kj2wxmasp5",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "materialized_but_not_anchor_matched",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"limited_reason_category": "missing_anchor",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_month_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-07-01",
|
||||||
|
"period_to": "2020-07-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "были",
|
||||||
|
"anchor_value_resolved": "были",
|
||||||
|
"assistant_reply_head": "Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 19,
|
||||||
|
"question": "покажи списания с расчетного счета по свк за 2020",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"trace_id": "address-5ZApJI8tq_",
|
||||||
|
"detected_intent": "bank_operations_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "materialized_but_not_anchor_matched",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"limited_reason_category": "missing_anchor",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-01-01",
|
||||||
|
"period_to": "2020-12-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "списания",
|
||||||
|
"anchor_value_resolved": "списания",
|
||||||
|
"assistant_reply_head": "Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 20,
|
||||||
|
"question": "свк за 2020 покаж все поступления",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-b8vcHTo3Mb",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-01-01",
|
||||||
|
"period_to": "2020-12-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 21,
|
||||||
|
"question": "покажи документы по свк за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-3plzT4WuiP",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-01-01",
|
||||||
|
"period_to": "2020-12-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 22,
|
||||||
|
"question": "а теперь только за май 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-ZAbNn9yMro",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "UNKNOWN",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 26,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": false,
|
||||||
|
"llm_decomposition_reason": "not_address_like",
|
||||||
|
"fallback_rule_hit": "",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-05-01",
|
||||||
|
"period_to": "2020-05-31",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"assistant_reply_head": "По окну 2020-05-01..2020-05-31 строк не найдено; показаны ближайшие доступные данные 2020-07-27..2021-11-10."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 23,
|
||||||
|
"question": "а по счету 60.01 на ту же дату",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-8AhrNbM9OG",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"query_shape": "UNKNOWN",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 5,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": false,
|
||||||
|
"llm_decomposition_reason": "error:Failed to extract output_text from /responses payload.",
|
||||||
|
"fallback_rule_hit": "",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2020-05-01",
|
||||||
|
"period_to": "2020-05-31",
|
||||||
|
"as_of_date": "2020-05-31",
|
||||||
|
"anchor_type": "account",
|
||||||
|
"anchor_value_raw": "60.01",
|
||||||
|
"anchor_value_resolved": "60.01",
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 24,
|
||||||
|
"question": "бля епт покажи доки по свк за 20й",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"trace_id": "address-t3_otb_RVf",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"query_shape": "DOCUMENT_LIST",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"rows_matched": 26,
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"llm_decomposition_applied": true,
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_rewrite",
|
||||||
|
"tool_gate_decision": "run_address_lane",
|
||||||
|
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
|
||||||
|
"period_from": "2026-01-01",
|
||||||
|
"period_to": "2026-04-01",
|
||||||
|
"as_of_date": "",
|
||||||
|
"anchor_type": "counterparty",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"assistant_reply_head": "По окну 2026-01-01..2026-04-01 строк не найдено; показаны ближайшие доступные данные 2020-07-27..2021-11-10."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
"idx","question","reply_type","mcp_call_status","limited_reason_category","detected_intent","llm_decomposition_reason","fallback_rule_hit","anchor_value_raw","anchor_value_resolved","rows_matched","assistant_reply_head"
|
||||||
|
"1","свк доки за 20год покеж","factual","matched_non_empty","","list_documents_by_counterparty","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","свк","Группа СВК","3","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"2","свк 20 год - покажи доки плс","factual","matched_non_empty","","list_documents_by_counterparty","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","свк","Группа СВК","3","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"3","что по свк за 2020 год выведи все доки плиз что есть","factual","matched_non_empty","","list_documents_by_counterparty","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","свк","Группа СВК","3","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"4","какие документы по контрагенту свк за все время","partial_coverage","error","execution_error","list_documents_by_counterparty","fallback_rule_applied_after_llm_error","documents_counterparty_all_time_rewrite","свк","свк","0","Не удалось выполнить адресный live-запрос в V1."
|
||||||
|
"5","доки по свк с 01.07.2020 по 31.07.2020","factual","matched_non_empty","","list_documents_by_counterparty","fallback_rule_applied_after_llm_error","documents_counterparty_month_rewrite","свк","Группа СВК","2","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"6","свк июль 2020 какие доки есть","factual","matched_non_empty","","list_documents_by_counterparty","fallback_rule_applied_after_llm_error","documents_counterparty_month_rewrite","свк","Группа СВК","2","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"7","покажи сальдо по 60.01 на 31.07.20","factual","matched_non_empty","","account_balance_snapshot","fallback_rule_applied_after_llm_error","balance_account_rewrite","60.01","60.01","200","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"8","остаток 60 на 2020.05","factual","matched_non_empty","","account_balance_snapshot","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","60","60","6","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"9","какой остаток по счету 60 на 2020 май","factual","matched_non_empty","","account_balance_snapshot","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","60","60","6","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"10","60 счет остаток на май 2020 покажи","factual","matched_non_empty","","account_balance_snapshot","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","60","60","6","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"11","какой остаток по счету 60 на 2020-05-31","factual","matched_non_empty","","account_balance_snapshot","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","60","60","6","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"12","остаток по 60 на май 2020, не за весь 2020","factual","matched_non_empty","","account_balance_snapshot","fallback_rule_applied_after_llm_error","balance_month_period_rewrite","60","60","6","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"13","покаж по договору 1-<2D><>/2020 доки за 2020","factual","matched_non_empty","","list_documents_by_contract","fallback_rule_applied_after_llm_error","documents_contract_year_rewrite","1-","1-","233","Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
"14","какие документы по договору 1-<2D><>/2020 за все время","factual","matched_non_empty","","list_documents_by_contract","fallback_rule_applied_after_llm_error","documents_contract_all_time_rewrite","1-","1-","120","Собран список документов по договору (live address lane)."
|
||||||
|
"15","есть ли долг по договору 1-<2D><>/2020 на 2020-07-31","partial_coverage","materialized_but_filtered_out_by_recipe","recipe_visibility_gap","list_documents_by_contract","error:Failed to extract output_text from /responses payload.","","1-<2D><>/2020 на 2020-07-31","1-<2D><>/2020 на 2020-07-31","0","Текущий live recipe не дает нужную видимость данных для этого сценария."
|
||||||
|
"16","какие хвосты по договору 1-<2D><>/2020 на дату 31.07.2020","partial_coverage","materialized_but_not_anchor_matched","missing_anchor","list_documents_by_contract","error:Failed to extract output_text from /responses payload.","","1-<2D><>/2020 на дату 31","1-<2D><>/2020 на дату 31","0","Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
"17","какие платежи были по свк в 2020","factual","matched_non_empty","","bank_operations_by_counterparty","fallback_rule_applied_after_llm_error","bank_operations_counterparty_year_rewrite","свк","Группа СВК","3","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"18","были ли поступления от свк за июль 2020","factual","matched_non_empty","","bank_operations_by_counterparty","fallback_rule_applied_after_llm_error","bank_operations_counterparty_month_rewrite","свк","Группа СВК","2","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"19","покажи списания с расчетного счета по свк за 2020","factual","matched_non_empty","","bank_operations_by_counterparty","fallback_rule_applied_after_llm_error","bank_operations_counterparty_year_rewrite","свк","Группа СВК","3","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"20","свк за 2020 покаж все поступления","factual","matched_non_empty","","bank_operations_by_counterparty","fallback_rule_applied_after_llm_error","bank_operations_counterparty_year_rewrite","свк","Группа СВК","3","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"21","покажи документы по свк за 2020","factual","matched_non_empty","","list_documents_by_counterparty","fallback_rule_applied_after_llm_error","documents_counterparty_year_rewrite","свк","Группа СВК","3","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"22","а теперь только за май 2020","partial_coverage","materialized_but_not_anchor_matched","missing_anchor","list_documents_by_counterparty","not_address_like","","свк","свк","0","Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
"23","а по счету 60.01 на ту же дату","factual","matched_non_empty","","account_balance_snapshot","error:Failed to extract output_text from /responses payload.","","60.01","60.01","5","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"24","бля епт покажи доки по свк за 20й","factual","matched_non_empty","","list_documents_by_counterparty","fallback_rule_applied_after_llm_error","documents_counterparty_rewrite","свк","Группа СВК","26","По окну 2026-01-01..2026-04-01 строк не найдено; показаны ближайшие доступные данные 2020-07-27..2021-11-10."
|
||||||
|
|
|
@ -0,0 +1,338 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"question": "свк доки за 20год покеж",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"question": "свк 20 год - покажи доки плс",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"question": "что по свк за 2020 год выведи все доки плиз что есть",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 4,
|
||||||
|
"question": "какие документы по контрагенту свк за все время",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"mcp_call_status": "error",
|
||||||
|
"limited_reason_category": "execution_error",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_all_time_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "свк",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"assistant_reply_head": "Не удалось выполнить адресный live-запрос в V1."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"question": "доки по свк с 01.07.2020 по 31.07.2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_month_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 2,
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 6,
|
||||||
|
"question": "свк июль 2020 какие доки есть",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_month_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 2,
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 7,
|
||||||
|
"question": "покажи сальдо по 60.01 на 31.07.20",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_account_rewrite",
|
||||||
|
"anchor_value_raw": "60.01",
|
||||||
|
"anchor_value_resolved": "60.01",
|
||||||
|
"rows_matched": 200,
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 8,
|
||||||
|
"question": "остаток 60 на 2020.05",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"question": "какой остаток по счету 60 на 2020 май",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 10,
|
||||||
|
"question": "60 счет остаток на май 2020 покажи",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 11,
|
||||||
|
"question": "какой остаток по счету 60 на 2020-05-31",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 12,
|
||||||
|
"question": "остаток по 60 на май 2020, не за весь 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "balance_month_period_rewrite",
|
||||||
|
"anchor_value_raw": "60",
|
||||||
|
"anchor_value_resolved": "60",
|
||||||
|
"rows_matched": 6,
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 13,
|
||||||
|
"question": "покаж по договору 1-<2D><>/2020 доки за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "list_documents_by_contract",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_contract_year_rewrite",
|
||||||
|
"anchor_value_raw": "1-",
|
||||||
|
"anchor_value_resolved": "1-",
|
||||||
|
"rows_matched": 233,
|
||||||
|
"assistant_reply_head": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 14,
|
||||||
|
"question": "какие документы по договору 1-<2D><>/2020 за все время",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "list_documents_by_contract",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_contract_all_time_rewrite",
|
||||||
|
"anchor_value_raw": "1-",
|
||||||
|
"anchor_value_resolved": "1-",
|
||||||
|
"rows_matched": 120,
|
||||||
|
"assistant_reply_head": "Собран список документов по договору (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 15,
|
||||||
|
"question": "есть ли долг по договору 1-<2D><>/2020 на 2020-07-31",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"mcp_call_status": "materialized_but_filtered_out_by_recipe",
|
||||||
|
"limited_reason_category": "recipe_visibility_gap",
|
||||||
|
"detected_intent": "list_documents_by_contract",
|
||||||
|
"llm_decomposition_reason": "error:Failed to extract output_text from /responses payload.",
|
||||||
|
"fallback_rule_hit": "",
|
||||||
|
"anchor_value_raw": "1-<2D><>/2020 на 2020-07-31",
|
||||||
|
"anchor_value_resolved": "1-<2D><>/2020 на 2020-07-31",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"assistant_reply_head": "Текущий live recipe не дает нужную видимость данных для этого сценария."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 16,
|
||||||
|
"question": "какие хвосты по договору 1-<2D><>/2020 на дату 31.07.2020",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"mcp_call_status": "materialized_but_not_anchor_matched",
|
||||||
|
"limited_reason_category": "missing_anchor",
|
||||||
|
"detected_intent": "list_documents_by_contract",
|
||||||
|
"llm_decomposition_reason": "error:Failed to extract output_text from /responses payload.",
|
||||||
|
"fallback_rule_hit": "",
|
||||||
|
"anchor_value_raw": "1-<2D><>/2020 на дату 31",
|
||||||
|
"anchor_value_resolved": "1-<2D><>/2020 на дату 31",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"assistant_reply_head": "Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 17,
|
||||||
|
"question": "какие платежи были по свк в 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "bank_operations_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "bank_operations_counterparty_year_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"assistant_reply_head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 18,
|
||||||
|
"question": "были ли поступления от свк за июль 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "bank_operations_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "bank_operations_counterparty_month_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 2,
|
||||||
|
"assistant_reply_head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 19,
|
||||||
|
"question": "покажи списания с расчетного счета по свк за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "bank_operations_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "bank_operations_counterparty_year_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"assistant_reply_head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 20,
|
||||||
|
"question": "свк за 2020 покаж все поступления",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "bank_operations_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "bank_operations_counterparty_year_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"assistant_reply_head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 21,
|
||||||
|
"question": "покажи документы по свк за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_year_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 3,
|
||||||
|
"assistant_reply_head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 22,
|
||||||
|
"question": "а теперь только за май 2020",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"mcp_call_status": "materialized_but_not_anchor_matched",
|
||||||
|
"limited_reason_category": "missing_anchor",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "not_address_like",
|
||||||
|
"fallback_rule_hit": "",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "свк",
|
||||||
|
"rows_matched": 0,
|
||||||
|
"assistant_reply_head": "Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 23,
|
||||||
|
"question": "а по счету 60.01 на ту же дату",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "account_balance_snapshot",
|
||||||
|
"llm_decomposition_reason": "error:Failed to extract output_text from /responses payload.",
|
||||||
|
"fallback_rule_hit": "",
|
||||||
|
"anchor_value_raw": "60.01",
|
||||||
|
"anchor_value_resolved": "60.01",
|
||||||
|
"rows_matched": 5,
|
||||||
|
"assistant_reply_head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 24,
|
||||||
|
"question": "бля епт покажи доки по свк за 20й",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_call_status": "matched_non_empty",
|
||||||
|
"limited_reason_category": "",
|
||||||
|
"detected_intent": "list_documents_by_counterparty",
|
||||||
|
"llm_decomposition_reason": "fallback_rule_applied_after_llm_error",
|
||||||
|
"fallback_rule_hit": "documents_counterparty_rewrite",
|
||||||
|
"anchor_value_raw": "свк",
|
||||||
|
"anchor_value_resolved": "Группа СВК",
|
||||||
|
"rows_matched": 26,
|
||||||
|
"assistant_reply_head": "По окну 2026-01-01..2026-04-01 строк не найдено; показаны ближайшие доступные данные 2020-07-27..2021-11-10."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
"idx","question","reply_type","mcp","limited","intent","anchor_raw","anchor_resolved","fallback","head"
|
||||||
|
"1","свк доки за 20год покеж","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_year_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"2","свк 20 год - покажи доки плс","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_year_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"3","что по свк за 2020 год выведи все доки плиз что есть","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_year_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"4","какие документы по контрагенту свк за все время","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_all_time_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"5","доки по свк с 01.07.2020 по 31.07.2020","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_month_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"6","свк июль 2020 какие доки есть","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_month_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"7","покажи сальдо по 60.01 на 31.07.20","factual","matched_non_empty","","account_balance_snapshot","60.01","60.01","balance_account_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"8","остаток 60 на 2020.05","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"9","какой остаток по счету 60 на 2020 май","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"10","60 счет остаток на май 2020 покажи","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"11","какой остаток по счету 60 на 2020-05-31","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"12","остаток по 60 на май 2020, не за весь 2020","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"13","покаж по договору 1-<2D><>/2020 доки за 2020","factual","matched_non_empty","","list_documents_by_contract","1-","1-","documents_contract_year_rewrite","По окну 2020-01-01..2020-12-31 строк не найдено; показаны ближайшие доступные данные 2020-09-30..2022-09-30."
|
||||||
|
"14","какие документы по договору 1-<2D><>/2020 за все время","factual","matched_non_empty","","list_documents_by_contract","1-","1-","documents_contract_all_time_rewrite","Собран список документов по договору (live address lane)."
|
||||||
|
"15","есть ли долг по договору 1-<2D><>/2020 на 2020-07-31","factual","matched_non_empty","","list_documents_by_contract","1-<2D><>/2020","1-<2D><>/2020","","Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
"16","какие хвосты по договору 1-<2D><>/2020 на дату 31.07.2020","factual","matched_non_empty","","list_documents_by_contract","1-<2D><>/2020","1-<2D><>/2020","","Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
"17","какие платежи были по свк в 2020","factual","matched_non_empty","","bank_operations_by_counterparty","свк","Группа СВК","bank_operations_counterparty_year_rewrite","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"18","были ли поступления от свк за июль 2020","factual","matched_non_empty","","bank_operations_by_counterparty","свк","Группа СВК","bank_operations_counterparty_month_rewrite","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"19","покажи списания с расчетного счета по свк за 2020","factual","matched_non_empty","","bank_operations_by_counterparty","свк","Группа СВК","bank_operations_counterparty_year_rewrite","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"20","свк за 2020 покаж все поступления","factual","matched_non_empty","","bank_operations_by_counterparty","свк","Группа СВК","bank_operations_counterparty_year_rewrite","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"21","покажи документы по свк за 2020","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_year_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"22","а теперь только за май 2020","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","","По окну 2020-05-01..2020-05-31 строк не найдено; показаны ближайшие доступные данные 2020-07-27..2021-11-10."
|
||||||
|
"23","а по счету 60.01 на ту же дату","factual","matched_non_empty","","account_balance_snapshot","60.01","60.01","","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"24","бля епт покажи доки по свк за 20й","partial_coverage","no_raw_rows","empty_match","list_documents_by_counterparty","свк","свк","documents_counterparty_rewrite","В live-данных по текущему фильтру записи не найдены."
|
||||||
|
|
|
@ -0,0 +1,290 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"question": "свк доки за 20год покеж",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"question": "свк 20 год - покажи доки плс",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"question": "что по свк за 2020 год выведи все доки плиз что есть",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 4,
|
||||||
|
"question": "какие документы по контрагенту свк за все время",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_all_time_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"question": "доки по свк с 01.07.2020 по 31.07.2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_month_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 6,
|
||||||
|
"question": "свк июль 2020 какие доки есть",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_month_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 7,
|
||||||
|
"question": "покажи сальдо по 60.01 на 31.07.20",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60.01",
|
||||||
|
"anchor_resolved": "60.01",
|
||||||
|
"fallback": "balance_account_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 8,
|
||||||
|
"question": "остаток 60 на 2020.05",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"question": "какой остаток по счету 60 на 2020 май",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 10,
|
||||||
|
"question": "60 счет остаток на май 2020 покажи",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 11,
|
||||||
|
"question": "какой остаток по счету 60 на 2020-05-31",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 12,
|
||||||
|
"question": "остаток по 60 на май 2020, не за весь 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 13,
|
||||||
|
"question": "покаж по договору 1-<2D><>/2020 доки за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_contract",
|
||||||
|
"anchor_raw": "1-",
|
||||||
|
"anchor_resolved": "1-",
|
||||||
|
"fallback": "documents_contract_year_rewrite",
|
||||||
|
"head": "По окну 2020-01-01..2020-12-31 строк не найдено; показаны ближайшие доступные данные 2020-09-30..2022-09-30."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 14,
|
||||||
|
"question": "какие документы по договору 1-<2D><>/2020 за все время",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_contract",
|
||||||
|
"anchor_raw": "1-",
|
||||||
|
"anchor_resolved": "1-",
|
||||||
|
"fallback": "documents_contract_all_time_rewrite",
|
||||||
|
"head": "Собран список документов по договору (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 15,
|
||||||
|
"question": "есть ли долг по договору 1-<2D><>/2020 на 2020-07-31",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_contract",
|
||||||
|
"anchor_raw": "1-<2D><>/2020",
|
||||||
|
"anchor_resolved": "1-<2D><>/2020",
|
||||||
|
"fallback": "",
|
||||||
|
"head": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 16,
|
||||||
|
"question": "какие хвосты по договору 1-<2D><>/2020 на дату 31.07.2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_contract",
|
||||||
|
"anchor_raw": "1-<2D><>/2020",
|
||||||
|
"anchor_resolved": "1-<2D><>/2020",
|
||||||
|
"fallback": "",
|
||||||
|
"head": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 17,
|
||||||
|
"question": "какие платежи были по свк в 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "bank_operations_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "bank_operations_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 18,
|
||||||
|
"question": "были ли поступления от свк за июль 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "bank_operations_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "bank_operations_counterparty_month_rewrite",
|
||||||
|
"head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 19,
|
||||||
|
"question": "покажи списания с расчетного счета по свк за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "bank_operations_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "bank_operations_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 20,
|
||||||
|
"question": "свк за 2020 покаж все поступления",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "bank_operations_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "bank_operations_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 21,
|
||||||
|
"question": "покажи документы по свк за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 22,
|
||||||
|
"question": "а теперь только за май 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "",
|
||||||
|
"head": "По окну 2020-05-01..2020-05-31 строк не найдено; показаны ближайшие доступные данные 2020-07-27..2021-11-10."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 23,
|
||||||
|
"question": "а по счету 60.01 на ту же дату",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60.01",
|
||||||
|
"anchor_resolved": "60.01",
|
||||||
|
"fallback": "",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 24,
|
||||||
|
"question": "бля епт покажи доки по свк за 20й",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"mcp": "no_raw_rows",
|
||||||
|
"limited": "empty_match",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "свк",
|
||||||
|
"fallback": "documents_counterparty_rewrite",
|
||||||
|
"head": "В live-данных по текущему фильтру записи не найдены."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
"idx","question","reply_type","mcp","limited","intent","anchor_raw","anchor_resolved","fallback","head"
|
||||||
|
"1","свк доки за 20год покеж","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_year_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"2","свк 20 год - покажи доки плс","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_year_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"3","что по свк за 2020 год выведи все доки плиз что есть","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_year_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"4","какие документы по контрагенту свк за все время","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_all_time_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"5","доки по свк с 01.07.2020 по 31.07.2020","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_month_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"6","свк июль 2020 какие доки есть","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_month_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"7","покажи сальдо по 60.01 на 31.07.20","factual","matched_non_empty","","account_balance_snapshot","60.01","60.01","balance_account_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"8","остаток 60 на 2020.05","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"9","какой остаток по счету 60 на 2020 май","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"10","60 счет остаток на май 2020 покажи","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"11","какой остаток по счету 60 на 2020-05-31","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"12","остаток по 60 на май 2020, не за весь 2020","factual","matched_non_empty","","account_balance_snapshot","60","60","balance_month_period_rewrite","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"13","покаж по договору 1-<2D><>/2020 доки за 2020","factual","matched_non_empty","","list_documents_by_contract","1-","1-","documents_contract_year_rewrite","Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
"14","какие документы по договору 1-<2D><>/2020 за все время","factual","matched_non_empty","","list_documents_by_contract","1-","1-","documents_contract_all_time_rewrite","Собран список документов по договору (live address lane)."
|
||||||
|
"15","есть ли долг по договору 1-<2D><>/2020 на 2020-07-31","factual","matched_non_empty","","list_documents_by_contract","1-<2D><>/2020","1-<2D><>/2020","","Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
"16","какие хвосты по договору 1-<2D><>/2020 на дату 31.07.2020","factual","matched_non_empty","","list_documents_by_contract","1-<2D><>/2020","1-<2D><>/2020","","Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
"17","какие платежи были по свк в 2020","factual","matched_non_empty","","bank_operations_by_counterparty","свк","Группа СВК","bank_operations_counterparty_year_rewrite","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"18","были ли поступления от свк за июль 2020","factual","matched_non_empty","","bank_operations_by_counterparty","свк","Группа СВК","bank_operations_counterparty_month_rewrite","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"19","покажи списания с расчетного счета по свк за 2020","factual","matched_non_empty","","bank_operations_by_counterparty","свк","Группа СВК","bank_operations_counterparty_year_rewrite","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"20","свк за 2020 покаж все поступления","factual","matched_non_empty","","bank_operations_by_counterparty","свк","Группа СВК","bank_operations_counterparty_year_rewrite","Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
"21","покажи документы по свк за 2020","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_year_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
"22","а теперь только за май 2020","partial_coverage","materialized_but_not_anchor_matched","missing_anchor","list_documents_by_counterparty","свк","свк","","Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
"23","а по счету 60.01 на ту же дату","factual","matched_non_empty","","account_balance_snapshot","60.01","60.01","","Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
"24","бля епт покажи доки по свк за 20й","factual","matched_non_empty","","list_documents_by_counterparty","свк","Группа СВК","documents_counterparty_year_rewrite","Собран список документов по контрагенту (live address lane)."
|
||||||
|
|
|
@ -0,0 +1,290 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"question": "свк доки за 20год покеж",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"question": "свк 20 год - покажи доки плс",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"question": "что по свк за 2020 год выведи все доки плиз что есть",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 4,
|
||||||
|
"question": "какие документы по контрагенту свк за все время",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_all_time_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"question": "доки по свк с 01.07.2020 по 31.07.2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_month_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 6,
|
||||||
|
"question": "свк июль 2020 какие доки есть",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_month_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 7,
|
||||||
|
"question": "покажи сальдо по 60.01 на 31.07.20",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60.01",
|
||||||
|
"anchor_resolved": "60.01",
|
||||||
|
"fallback": "balance_account_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 8,
|
||||||
|
"question": "остаток 60 на 2020.05",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"question": "какой остаток по счету 60 на 2020 май",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 10,
|
||||||
|
"question": "60 счет остаток на май 2020 покажи",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 11,
|
||||||
|
"question": "какой остаток по счету 60 на 2020-05-31",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 12,
|
||||||
|
"question": "остаток по 60 на май 2020, не за весь 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60",
|
||||||
|
"anchor_resolved": "60",
|
||||||
|
"fallback": "balance_month_period_rewrite",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 13,
|
||||||
|
"question": "покаж по договору 1-<2D><>/2020 доки за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_contract",
|
||||||
|
"anchor_raw": "1-",
|
||||||
|
"anchor_resolved": "1-",
|
||||||
|
"fallback": "documents_contract_year_rewrite",
|
||||||
|
"head": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 14,
|
||||||
|
"question": "какие документы по договору 1-<2D><>/2020 за все время",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_contract",
|
||||||
|
"anchor_raw": "1-",
|
||||||
|
"anchor_resolved": "1-",
|
||||||
|
"fallback": "documents_contract_all_time_rewrite",
|
||||||
|
"head": "Собран список документов по договору (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 15,
|
||||||
|
"question": "есть ли долг по договору 1-<2D><>/2020 на 2020-07-31",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_contract",
|
||||||
|
"anchor_raw": "1-<2D><>/2020",
|
||||||
|
"anchor_resolved": "1-<2D><>/2020",
|
||||||
|
"fallback": "",
|
||||||
|
"head": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 16,
|
||||||
|
"question": "какие хвосты по договору 1-<2D><>/2020 на дату 31.07.2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_contract",
|
||||||
|
"anchor_raw": "1-<2D><>/2020",
|
||||||
|
"anchor_resolved": "1-<2D><>/2020",
|
||||||
|
"fallback": "",
|
||||||
|
"head": "Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 17,
|
||||||
|
"question": "какие платежи были по свк в 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "bank_operations_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "bank_operations_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 18,
|
||||||
|
"question": "были ли поступления от свк за июль 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "bank_operations_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "bank_operations_counterparty_month_rewrite",
|
||||||
|
"head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 19,
|
||||||
|
"question": "покажи списания с расчетного счета по свк за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "bank_operations_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "bank_operations_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 20,
|
||||||
|
"question": "свк за 2020 покаж все поступления",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "bank_operations_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "bank_operations_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список банковских операций по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 21,
|
||||||
|
"question": "покажи документы по свк за 2020",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 22,
|
||||||
|
"question": "а теперь только за май 2020",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"mcp": "materialized_but_not_anchor_matched",
|
||||||
|
"limited": "missing_anchor",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "свк",
|
||||||
|
"fallback": "",
|
||||||
|
"head": "Для точного адресного поиска не хватает обязательного якоря."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 23,
|
||||||
|
"question": "а по счету 60.01 на ту же дату",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "account_balance_snapshot",
|
||||||
|
"anchor_raw": "60.01",
|
||||||
|
"anchor_resolved": "60.01",
|
||||||
|
"fallback": "",
|
||||||
|
"head": "Адресный срез по счету собран (по движениям live MCP)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 24,
|
||||||
|
"question": "бля епт покажи доки по свк за 20й",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp": "matched_non_empty",
|
||||||
|
"limited": "",
|
||||||
|
"intent": "list_documents_by_counterparty",
|
||||||
|
"anchor_raw": "свк",
|
||||||
|
"anchor_resolved": "Группа СВК",
|
||||||
|
"fallback": "documents_counterparty_year_rewrite",
|
||||||
|
"head": "Собран список документов по контрагенту (live address lane)."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -312,6 +312,18 @@ function cleanupAnchorValue(value: string): string {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove trailing as-of qualifiers often captured by broad contract/counterparty regexes:
|
||||||
|
// "<anchor> на 2020-07-31", "<anchor> на дату 31.07.2020", "<anchor> 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();
|
||||||
|
}
|
||||||
|
|
||||||
// Remove trailing period qualifiers that can be swallowed by broad anchor regexes:
|
// Remove trailing period qualifiers that can be swallowed by broad anchor regexes:
|
||||||
// "<counterparty> с 2020-07-01 по 2020-07-31", "<counterparty> from 2020-07-01 to 2020-07-31"
|
// "<counterparty> с 2020-07-01 по 2020-07-31", "<counterparty> from 2020-07-01 to 2020-07-31"
|
||||||
const periodTailPattern =
|
const periodTailPattern =
|
||||||
|
|
@ -386,6 +398,29 @@ function extractLooseByAnchorValue(text: string): string | undefined {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractContractTokenHeuristic(text: string): string | undefined {
|
||||||
|
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: string): boolean {
|
function isLikelyCounterpartyToken(rawToken: string): boolean {
|
||||||
const token = String(rawToken ?? "").trim();
|
const token = String(rawToken ?? "").trim();
|
||||||
const lowered = token.toLowerCase();
|
const lowered = token.toLowerCase();
|
||||||
|
|
@ -577,6 +612,9 @@ function requiredFiltersByIntent(intent: AddressIntent): Array<keyof AddressFilt
|
||||||
if (intent === "list_documents_by_counterparty" || intent === "bank_operations_by_counterparty") {
|
if (intent === "list_documents_by_counterparty" || intent === "bank_operations_by_counterparty") {
|
||||||
return ["counterparty"];
|
return ["counterparty"];
|
||||||
}
|
}
|
||||||
|
if (intent === "list_documents_by_contract" || intent === "bank_operations_by_contract") {
|
||||||
|
return ["contract"];
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -632,6 +670,13 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
|
||||||
if (contractMatch) {
|
if (contractMatch) {
|
||||||
filters.contract = cleanupAnchorValue(String(contractMatch[1]));
|
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);
|
const periodRange = extractPeriodRange(text);
|
||||||
if (periodRange.period_from) {
|
if (periodRange.period_from) {
|
||||||
|
|
@ -678,7 +723,8 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
|
||||||
|
|
||||||
// For document/bank lists we default to a short recent window if no explicit period was provided.
|
// For document/bank lists we default to a short recent window if no explicit period was provided.
|
||||||
if (
|
if (
|
||||||
(intent === "list_documents_by_counterparty" || intent === "bank_operations_by_counterparty") &&
|
(intent === "list_documents_by_counterparty" ||
|
||||||
|
intent === "bank_operations_by_counterparty") &&
|
||||||
!filters.period_from &&
|
!filters.period_from &&
|
||||||
!filters.period_to &&
|
!filters.period_to &&
|
||||||
!hasAllTimeHint(text)
|
!hasAllTimeHint(text)
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,46 @@ const BANK_OPERATIONS_BY_COUNTERPARTY_HINTS = [
|
||||||
"поступлен",
|
"поступлен",
|
||||||
"движени"
|
"движени"
|
||||||
];
|
];
|
||||||
|
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: string, patterns: string[]): boolean {
|
function hasAny(text: string, patterns: string[]): boolean {
|
||||||
return patterns.some((item) => text.includes(item));
|
return patterns.some((item) => text.includes(item));
|
||||||
|
|
@ -198,6 +238,72 @@ function hasPartyAnchorMention(text: string): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasContractAnchorMention(text: string): boolean {
|
||||||
|
return (
|
||||||
|
text.includes("договор") ||
|
||||||
|
text.includes("дог.") ||
|
||||||
|
text.includes("contract") ||
|
||||||
|
text.includes("dogovor")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasContractNumberLikeToken(text: string): boolean {
|
||||||
|
if (/(?:^|[\s([{])(?:№|#|n)\s*[a-zа-яё0-9][a-zа-яё0-9./_-]{1,}(?=$|[\s,.;:!?)\]}])/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawTokens = text
|
||||||
|
.split(/[\s,;:!?()[\]{}"«»]+/u)
|
||||||
|
.map((token) => 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;
|
||||||
|
}
|
||||||
|
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: string): boolean {
|
||||||
|
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: string): boolean {
|
function hasLooseByAnchorMention(text: string): boolean {
|
||||||
const match = text.match(/(?:^|\s)по\s+([a-zа-яё][a-zа-яё0-9._-]{1,})(?=[\s,.;:!?)]|$)/iu);
|
const match = text.match(/(?:^|\s)по\s+([a-zа-яё][a-zа-яё0-9._-]{1,})(?=[\s,.;:!?)]|$)/iu);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
|
|
@ -260,6 +366,20 @@ function hasDocsOrBankSignal(text: string): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasBankOperationSignal(text: string): boolean {
|
||||||
|
return hasAny(text, BANK_OPERATION_CORE_HINTS) || hasAny(text, BANK_OPERATIONS_BY_COUNTERPARTY_HINTS) || hasAny(text, BANK_OPERATIONS_BY_CONTRACT_HINTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasDocumentSignal(text: string): boolean {
|
||||||
|
return (
|
||||||
|
text.includes("док") ||
|
||||||
|
text.includes("доки") ||
|
||||||
|
text.includes("документ") ||
|
||||||
|
text.includes("docs") ||
|
||||||
|
text.includes("documents")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function hasHeuristicCounterpartyAnchor(text: string): boolean {
|
function hasHeuristicCounterpartyAnchor(text: string): boolean {
|
||||||
if (!hasDocsOrBankSignal(text)) {
|
if (!hasDocsOrBankSignal(text)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -329,6 +449,28 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
if (
|
||||||
hasAny(text, BANK_OPERATIONS_BY_COUNTERPARTY_HINTS) &&
|
hasAny(text, BANK_OPERATIONS_BY_COUNTERPARTY_HINTS) &&
|
||||||
(hasPartyAnchorMention(text) || hasLooseByAnchorMention(text) || hasHeuristicCounterpartyAnchor(text))
|
(hasPartyAnchorMention(text) || hasLooseByAnchorMention(text) || hasHeuristicCounterpartyAnchor(text))
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ interface AddressTryHandleOptions {
|
||||||
|
|
||||||
const ACCOUNT_SCOPE_FIELDS_CHECKED = ["account_dt", "account_kt", "registrator", "analytics"] as const;
|
const ACCOUNT_SCOPE_FIELDS_CHECKED = ["account_dt", "account_kt", "registrator", "analytics"] as const;
|
||||||
const ACCOUNT_SCOPE_MATCH_STRATEGY = "account_code_regex_plus_alias_map_v1" as const;
|
const ACCOUNT_SCOPE_MATCH_STRATEGY = "account_code_regex_plus_alias_map_v1" as const;
|
||||||
|
const ADDRESS_ANCHOR_RECOVERY_LIMIT = 1000;
|
||||||
const PARTY_ANCHOR_STOPWORDS = new Set([
|
const PARTY_ANCHOR_STOPWORDS = new Set([
|
||||||
"ооо",
|
"ооо",
|
||||||
"ао",
|
"ао",
|
||||||
|
|
@ -48,6 +49,35 @@ const PARTY_ANCHOR_STOPWORDS = new Set([
|
||||||
"по",
|
"по",
|
||||||
"by"
|
"by"
|
||||||
]);
|
]);
|
||||||
|
const LOW_QUALITY_PARTY_ANCHOR_TOKENS = new Set([
|
||||||
|
"что",
|
||||||
|
"чо",
|
||||||
|
"были",
|
||||||
|
"был",
|
||||||
|
"была",
|
||||||
|
"было",
|
||||||
|
"ли",
|
||||||
|
"какие",
|
||||||
|
"какой",
|
||||||
|
"покажи",
|
||||||
|
"показать",
|
||||||
|
"выведи",
|
||||||
|
"списания",
|
||||||
|
"списание",
|
||||||
|
"поступления",
|
||||||
|
"поступление",
|
||||||
|
"доки",
|
||||||
|
"документ",
|
||||||
|
"документы",
|
||||||
|
"документов",
|
||||||
|
"банковские",
|
||||||
|
"операции",
|
||||||
|
"платежи",
|
||||||
|
"платеж",
|
||||||
|
"платежи",
|
||||||
|
"плс",
|
||||||
|
"please"
|
||||||
|
]);
|
||||||
const ACCOUNT_ALIAS_MAP: Record<string, string[]> = {
|
const ACCOUNT_ALIAS_MAP: Record<string, string[]> = {
|
||||||
"51": ["расчетный счет", "расчетные счета", "bank account"],
|
"51": ["расчетный счет", "расчетные счета", "bank account"],
|
||||||
"52": ["валютный счет", "валютные счета", "currency account"],
|
"52": ["валютный счет", "валютные счета", "currency account"],
|
||||||
|
|
@ -152,6 +182,30 @@ function matchesAnchorText(searchable: string, anchor: string): boolean {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLikelyLowQualityPartyAnchor(value: string | null | undefined): boolean {
|
||||||
|
const normalized = normalizeSearchText(String(value ?? ""));
|
||||||
|
if (!normalized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const tokens = normalized.split(" ").filter(Boolean);
|
||||||
|
if (tokens.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const meaningfulTokens = tokens.filter((token) => {
|
||||||
|
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: string): string {
|
function normalizeAccountToken(value: string): string {
|
||||||
const source = String(value ?? "").trim().replace(",", ".");
|
const source = String(value ?? "").trim().replace(",", ".");
|
||||||
const match = source.match(/(\d{2})(?:\.(\d{1,2}))?/);
|
const match = source.match(/(\d{2})(?:\.(\d{1,2}))?/);
|
||||||
|
|
@ -370,13 +424,13 @@ function applyAddressFilters(rows: NormalizedAddressRow[], filters: AddressFilte
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyIntentSpecificFilter(intent: AddressIntent, rows: NormalizedAddressRow[]): NormalizedAddressRow[] {
|
function applyIntentSpecificFilter(intent: AddressIntent, rows: NormalizedAddressRow[]): NormalizedAddressRow[] {
|
||||||
if (intent === "bank_operations_by_counterparty") {
|
if (intent === "bank_operations_by_counterparty" || intent === "bank_operations_by_contract") {
|
||||||
const bankDocPattern =
|
const bankDocPattern =
|
||||||
/(?:списаниесрасчетногосчета|поступлениенарасчетныйсчет|списание с расчетного счета|поступление на расчетный счет|bank|payment|wire|statement)/i;
|
/(?:списаниесрасчетногосчета|поступлениенарасчетныйсчет|списание с расчетного счета|поступление на расчетный счет|bank|payment|wire|statement)/i;
|
||||||
return rows.filter((row) => bankDocPattern.test(row.registrator.toLowerCase()));
|
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 =
|
const documentPattern =
|
||||||
/(?:документ|реализац|поступлен|счет[-\s]?фактур|акт|накладн|payment|invoice|document|sale|purchase|bank)/i;
|
/(?:документ|реализац|поступлен|счет[-\s]?фактур|акт|накладн|payment|invoice|document|sale|purchase|bank)/i;
|
||||||
return rows.filter((row) => documentPattern.test(row.registrator.toLowerCase()) || row.analytics.length > 0);
|
return rows.filter((row) => documentPattern.test(row.registrator.toLowerCase()) || row.analytics.length > 0);
|
||||||
|
|
@ -402,7 +456,21 @@ function canAutoBroadenPeriodWindow(intent: AddressIntent, filters: AddressFilte
|
||||||
if (!hasExplicitPeriodWindow(filters)) {
|
if (!hasExplicitPeriodWindow(filters)) {
|
||||||
return false;
|
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 isDocumentOrBankIntent(intent: AddressIntent): boolean {
|
||||||
|
return (
|
||||||
|
intent === "list_documents_by_counterparty" ||
|
||||||
|
intent === "bank_operations_by_counterparty" ||
|
||||||
|
intent === "list_documents_by_contract" ||
|
||||||
|
intent === "bank_operations_by_contract"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toIsoDatePrefix(value: string | null): string | null {
|
function toIsoDatePrefix(value: string | null): string | null {
|
||||||
|
|
@ -959,6 +1027,124 @@ export class AddressQueryService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
filteredRows.length === 0 &&
|
||||||
|
isDocumentOrBankIntent(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: AddressFilterSet = {
|
||||||
|
...filters.extracted_filters,
|
||||||
|
limit: ADDRESS_ANCHOR_RECOVERY_LIMIT
|
||||||
|
};
|
||||||
|
const expandedSelection = selectAddressRecipe(intent.intent, expandedLimitFilters);
|
||||||
|
if (expandedSelection.selected_recipe && expandedSelection.missing_required_filters.length === 0) {
|
||||||
|
const expandedPlan = buildAddressRecipePlan(expandedSelection.selected_recipe, expandedLimitFilters);
|
||||||
|
if (expandedPlan.limit > currentLimit) {
|
||||||
|
const expandedMcp = await 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 = resolvePrimaryAnchor(intent.intent, expandedLimitFilters);
|
||||||
|
expandedAnchor = refineAnchorFromRows(expandedAnchor, expandedNormalizedRows);
|
||||||
|
const expandedFiltersForMatching: AddressFilterSet =
|
||||||
|
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 = 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: 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 (filteredRows.length === 0 && canAutoBroadenPeriodWindow(intent.intent, filters.extracted_filters)) {
|
if (filteredRows.length === 0 && canAutoBroadenPeriodWindow(intent.intent, filters.extracted_filters)) {
|
||||||
const autoBroadenedFilters: AddressFilterSet = { ...filters.extracted_filters };
|
const autoBroadenedFilters: AddressFilterSet = { ...filters.extracted_filters };
|
||||||
delete autoBroadenedFilters.period_from;
|
delete autoBroadenedFilters.period_from;
|
||||||
|
|
@ -1071,32 +1257,52 @@ export class AddressQueryService {
|
||||||
const isVisibilityGapCandidate =
|
const isVisibilityGapCandidate =
|
||||||
hadBaseRows &&
|
hadBaseRows &&
|
||||||
hadAnchorMatchedRows &&
|
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 isAnchorMismatch = stageStatus === "materialized_but_not_anchor_matched";
|
||||||
const isRecipeFilteredOut = stageStatus === "materialized_but_filtered_out_by_recipe";
|
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: AddressLimitedReasonCategory =
|
||||||
|
isFollowupAnchorCarryover || !isLowQualityPartyAnchor ? "empty_match" : "missing_anchor";
|
||||||
const category: AddressLimitedReasonCategory = isAnchorMismatch
|
const category: AddressLimitedReasonCategory = isAnchorMismatch
|
||||||
? "missing_anchor"
|
? anchorMismatchCategory
|
||||||
: isRecipeFilteredOut
|
: isRecipeFilteredOut
|
||||||
? "recipe_visibility_gap"
|
? "recipe_visibility_gap"
|
||||||
: isVisibilityGapCandidate
|
: isVisibilityGapCandidate
|
||||||
? "recipe_visibility_gap"
|
? "recipe_visibility_gap"
|
||||||
: "empty_match";
|
: "empty_match";
|
||||||
const reasonText = isAnchorMismatch
|
const reasonText = isAnchorMismatch
|
||||||
|
? anchorMismatchCategory === "missing_anchor"
|
||||||
? "якорь контрагента/договора не найден в материализованных live-строках"
|
? "якорь контрагента/договора не найден в материализованных live-строках"
|
||||||
|
: "по указанному якорю и фильтрам в live-выборке нет строк"
|
||||||
: isRecipeFilteredOut
|
: isRecipeFilteredOut
|
||||||
? "строки по якорю найдены, но отфильтрованы intent-specific recipe"
|
? "строки по якорю найдены, но отфильтрованы intent-specific recipe"
|
||||||
: isVisibilityGapCandidate
|
: isVisibilityGapCandidate
|
||||||
? "в текущем live recipe нет достаточной document/bank видимости после фильтрации"
|
? "в текущем live recipe нет достаточной document/bank видимости после фильтрации"
|
||||||
: "по выбранным фильтрам в live-выборке нет строк";
|
: "по выбранным фильтрам в live-выборке нет строк";
|
||||||
const nextStep = isAnchorMismatch
|
const nextStep = isAnchorMismatch
|
||||||
|
? anchorMismatchCategory === "missing_anchor"
|
||||||
? "уточните контрагента точным именем или добавьте ИНН/договор"
|
? "уточните контрагента точным именем или добавьте ИНН/договор"
|
||||||
|
: "уточните период или снимите часть фильтров"
|
||||||
: isRecipeFilteredOut
|
: isRecipeFilteredOut
|
||||||
? "сузьте период, уточните контрагента или документный тип"
|
? "сузьте период, уточните контрагента или документный тип"
|
||||||
: isVisibilityGapCandidate
|
: isVisibilityGapCandidate
|
||||||
? "нужен специализированный recipe для document/bank контуров или более точный документный anchor"
|
? "нужен специализированный recipe для document/bank контуров или более точный документный anchor"
|
||||||
: "уточните период, контрагента, договор или снимите часть фильтров";
|
: "уточните период, контрагента, договор или снимите часть фильтров";
|
||||||
const limitations = isAnchorMismatch
|
const limitations = isAnchorMismatch
|
||||||
? ["anchor_not_matched_after_materialization"]
|
? [
|
||||||
|
anchorMismatchCategory === "missing_anchor"
|
||||||
|
? "anchor_not_matched_after_materialization"
|
||||||
|
: "no_rows_for_anchor_after_materialization"
|
||||||
|
]
|
||||||
: isRecipeFilteredOut
|
: isRecipeFilteredOut
|
||||||
? ["rows_filtered_out_by_recipe_after_anchor_match"]
|
? ["rows_filtered_out_by_recipe_after_anchor_match"]
|
||||||
: [
|
: [
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,26 @@ const BASE_RECIPES: AddressRecipeDefinition[] = [
|
||||||
account_scope_mode: "preferred",
|
account_scope_mode: "preferred",
|
||||||
query_template: "bank_docs"
|
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",
|
recipe_id: "address_documents_forming_balance_v1",
|
||||||
intent: "documents_forming_balance",
|
intent: "documents_forming_balance",
|
||||||
|
|
@ -241,8 +261,10 @@ function buildMovementAccountCondition(filters: AddressFilterSet): string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldBoostLimitForAllTimeCounterparty(filters: AddressFilterSet): boolean {
|
function shouldBoostLimitForAllTimeCounterparty(filters: AddressFilterSet): boolean {
|
||||||
const hasCounterparty = typeof filters.counterparty === "string" && filters.counterparty.trim().length > 0;
|
const hasAnchor =
|
||||||
if (!hasCounterparty) {
|
(typeof filters.counterparty === "string" && filters.counterparty.trim().length > 0) ||
|
||||||
|
(typeof filters.contract === "string" && filters.contract.trim().length > 0);
|
||||||
|
if (!hasAnchor) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const hasPeriod = Boolean(
|
const hasPeriod = Boolean(
|
||||||
|
|
@ -254,7 +276,12 @@ function shouldBoostLimitForAllTimeCounterparty(filters: AddressFilterSet): bool
|
||||||
}
|
}
|
||||||
|
|
||||||
function maxLimitForIntent(intent: AddressIntent): number {
|
function maxLimitForIntent(intent: AddressIntent): number {
|
||||||
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"
|
||||||
|
) {
|
||||||
return ADDRESS_MAX_LIMIT_EXTENDED;
|
return ADDRESS_MAX_LIMIT_EXTENDED;
|
||||||
}
|
}
|
||||||
return ADDRESS_MAX_LIMIT_DEFAULT;
|
return ADDRESS_MAX_LIMIT_DEFAULT;
|
||||||
|
|
@ -292,7 +319,10 @@ export function buildAddressRecipePlan(
|
||||||
? Math.max(1, Math.min(maxLimit, Math.trunc(filters.limit)))
|
? Math.max(1, Math.min(maxLimit, Math.trunc(filters.limit)))
|
||||||
: recipe.default_limit;
|
: recipe.default_limit;
|
||||||
const boostedLimit =
|
const boostedLimit =
|
||||||
(recipe.intent === "list_documents_by_counterparty" || recipe.intent === "bank_operations_by_counterparty") &&
|
(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)
|
shouldBoostLimitForAllTimeCounterparty(filters)
|
||||||
? Math.max(baseLimit, maxLimit)
|
? Math.max(baseLimit, maxLimit)
|
||||||
: (recipe.intent === "account_balance_snapshot" || recipe.intent === "documents_forming_balance") &&
|
: (recipe.intent === "account_balance_snapshot" || recipe.intent === "documents_forming_balance") &&
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,18 @@ export function composeFactualReply(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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") {
|
if (intent === "bank_operations_by_counterparty") {
|
||||||
const lines = [
|
const lines = [
|
||||||
"Собран список банковских операций по контрагенту (live address lane).",
|
"Собран список банковских операций по контрагенту (live address lane).",
|
||||||
|
|
@ -128,6 +140,18 @@ export function composeFactualReply(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 =
|
const title =
|
||||||
intent === "list_payables_counterparties"
|
intent === "list_payables_counterparties"
|
||||||
? "Срез обязательств (payables) собран по движениям с account scope 60/76."
|
? "Срез обязательств (payables) собран по движениям с account scope 60/76."
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,16 @@ function mergeFollowupFilters(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (intent === "account_balance_snapshot" || intent === "documents_forming_balance") {
|
||||||
if (!toNonEmptyString(merged.account)) {
|
if (!toNonEmptyString(merged.account)) {
|
||||||
const inheritedAccount = previousAccount ?? (followupContext.previous_anchor_type === "account" ? previousAnchorValue : null);
|
const inheritedAccount = previousAccount ?? (followupContext.previous_anchor_type === "account" ? previousAnchorValue : null);
|
||||||
|
|
@ -184,7 +194,9 @@ function resolveMissingRequiredFilters(intent: AddressIntent, filters: AddressFi
|
||||||
account_balance_snapshot: ["account", "as_of_date"],
|
account_balance_snapshot: ["account", "as_of_date"],
|
||||||
documents_forming_balance: ["account", "as_of_date"],
|
documents_forming_balance: ["account", "as_of_date"],
|
||||||
list_documents_by_counterparty: ["counterparty"],
|
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] ?? [];
|
const required = requiredByIntent[intent] ?? [];
|
||||||
return required.filter((key) => {
|
return required.filter((key) => {
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,18 @@ export function resolvePrimaryAnchor(intent: AddressIntent, filters: AddressFilt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
if (counterparty) {
|
||||||
return {
|
return {
|
||||||
anchor_type: "counterparty",
|
anchor_type: "counterparty",
|
||||||
|
|
@ -208,4 +220,3 @@ export function refineAnchorFromRows(anchor: AnchorResolutionDebug, rows: Resolv
|
||||||
ambiguity_count: candidates.length - 1
|
ambiguity_count: candidates.length - 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1828,9 +1828,24 @@ const ADDRESS_PREDECOMPOSE_NOISE_TOKENS = new Set([
|
||||||
"show",
|
"show",
|
||||||
"list",
|
"list",
|
||||||
"выведи",
|
"выведи",
|
||||||
|
"что",
|
||||||
|
"чо",
|
||||||
"которые",
|
"которые",
|
||||||
"какие",
|
"какие",
|
||||||
"какой",
|
"какой",
|
||||||
|
"были",
|
||||||
|
"был",
|
||||||
|
"была",
|
||||||
|
"было",
|
||||||
|
"ли",
|
||||||
|
"списания",
|
||||||
|
"списание",
|
||||||
|
"поступления",
|
||||||
|
"поступление",
|
||||||
|
"расчетного",
|
||||||
|
"расчётного",
|
||||||
|
"счета",
|
||||||
|
"счёта",
|
||||||
"есть",
|
"есть",
|
||||||
"est",
|
"est",
|
||||||
"kakie",
|
"kakie",
|
||||||
|
|
@ -1910,6 +1925,8 @@ const ADDRESS_MONTH_ALIAS_MAP = {
|
||||||
dec: "12"
|
dec: "12"
|
||||||
};
|
};
|
||||||
const ADDRESS_DOCS_SIGNAL_PATTERN = /(?:док|доки|документ|документы|документов|docs?|documents?|bank|выписк|плат[её]ж|оплат|поступлен|списан|операц)/i;
|
const ADDRESS_DOCS_SIGNAL_PATTERN = /(?:док|доки|документ|документы|документов|docs?|documents?|bank|выписк|плат[её]ж|оплат|поступлен|списан|операц)/i;
|
||||||
|
const ADDRESS_BANK_SIGNAL_PATTERN = /(?:bank|банк|банков|выписк|плат[её]ж|оплат|поступлен|списан|операц|расчетн)/i;
|
||||||
|
const ADDRESS_CONTRACT_SIGNAL_PATTERN = /(?:договор(?:а|у|ом|е)?|\bcontract\b)/iu;
|
||||||
const ADDRESS_BALANCE_SIGNAL_PATTERN = /(?:остат|сальдо|баланс|взаиморасч|долг|saldo|balance)/i;
|
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;
|
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) {
|
function normalizeAddressMonthAliasToken(token) {
|
||||||
|
|
@ -1975,14 +1992,21 @@ function extractAddressFallbackYear(text) {
|
||||||
return fullYearMatch[1];
|
return fullYearMatch[1];
|
||||||
}
|
}
|
||||||
const shortYearMatch = source.match(/(?:^|[^0-9])(\d{2})\s*(?:г(?:од|ода)?|г|год|year|god)(?=$|[^a-zа-яё0-9])/iu);
|
const shortYearMatch = source.match(/(?:^|[^0-9])(\d{2})\s*(?:г(?:од|ода)?|г|год|year|god)(?=$|[^a-zа-яё0-9])/iu);
|
||||||
if (!shortYearMatch) {
|
if (shortYearMatch) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const shortYear = Number(shortYearMatch[1]);
|
const shortYear = Number(shortYearMatch[1]);
|
||||||
if (!Number.isFinite(shortYear) || shortYear < 0 || shortYear > 99) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
return String(2000 + shortYear);
|
const shortOrdinalYear = Number(shortOrdinalYearMatch[1]);
|
||||||
|
if (!Number.isFinite(shortOrdinalYear) || shortOrdinalYear < 0 || shortOrdinalYear > 99) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return String(2000 + shortOrdinalYear);
|
||||||
}
|
}
|
||||||
function extractAddressFallbackMonthYear(text) {
|
function extractAddressFallbackMonthYear(text) {
|
||||||
const source = String(text ?? "");
|
const source = String(text ?? "");
|
||||||
|
|
@ -1998,14 +2022,14 @@ function extractAddressFallbackMonthYear(text) {
|
||||||
const year = numericMonthYear[2];
|
const year = numericMonthYear[2];
|
||||||
return `${year}-${month}`;
|
return `${year}-${month}`;
|
||||||
}
|
}
|
||||||
const namedMonthYear = source.match(/\b([a-zа-яё]+)\s+(20\d{2})\b/iu);
|
const namedMonthYear = source.match(/(?:^|[^a-zа-яё0-9])([a-zа-яё]+)\s+(20\d{2})(?=$|[^a-zа-яё0-9])/iu);
|
||||||
if (namedMonthYear) {
|
if (namedMonthYear) {
|
||||||
const month = normalizeAddressMonthAliasToken(namedMonthYear[1]);
|
const month = normalizeAddressMonthAliasToken(namedMonthYear[1]);
|
||||||
if (month) {
|
if (month) {
|
||||||
return `${namedMonthYear[2]}-${month}`;
|
return `${namedMonthYear[2]}-${month}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const yearNamedMonth = source.match(/\b(20\d{2})\s+([a-zа-яё]+)\b/iu);
|
const yearNamedMonth = source.match(/(?:^|[^a-zа-яё0-9])(20\d{2})\s+([a-zа-яё]+)(?=$|[^a-zа-яё0-9])/iu);
|
||||||
if (yearNamedMonth) {
|
if (yearNamedMonth) {
|
||||||
const month = normalizeAddressMonthAliasToken(yearNamedMonth[2]);
|
const month = normalizeAddressMonthAliasToken(yearNamedMonth[2]);
|
||||||
if (month) {
|
if (month) {
|
||||||
|
|
@ -2014,7 +2038,42 @@ function extractAddressFallbackMonthYear(text) {
|
||||||
}
|
}
|
||||||
return null;
|
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) {
|
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)$/.test(normalizedByToken)) {
|
||||||
|
return byToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
const candidates = extractAddressAnchorTokens(text);
|
const candidates = extractAddressAnchorTokens(text);
|
||||||
for (const token of candidates) {
|
for (const token of candidates) {
|
||||||
const normalized = String(token ?? "").toLowerCase();
|
const normalized = String(token ?? "").toLowerCase();
|
||||||
|
|
@ -2027,10 +2086,44 @@ function pickAddressFallbackCounterpartyToken(text) {
|
||||||
if (/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(normalized)) {
|
if (/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(normalized)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (/^(?:договор|договора|договору|договором|договоре|contract)$/.test(normalized)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
function extractAddressFallbackContractToken(text) {
|
||||||
|
const source = String(text ?? "");
|
||||||
|
const patterns = [
|
||||||
|
/(?:договор(?:а|у|ом|е)?|\bcontract\b)\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*(?:договор(?:а|у|ом|е)?|\bcontract\b)/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)) {
|
||||||
|
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) {
|
function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage) {
|
||||||
const sourceRaw = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")));
|
const sourceRaw = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")));
|
||||||
const source = compactWhitespace(String(sanitizedUserMessage ?? sourceRaw).toLowerCase());
|
const source = compactWhitespace(String(sanitizedUserMessage ?? sourceRaw).toLowerCase());
|
||||||
|
|
@ -2040,9 +2133,10 @@ function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage)
|
||||||
const monthYear = extractAddressFallbackMonthYear(source);
|
const monthYear = extractAddressFallbackMonthYear(source);
|
||||||
const year = extractAddressFallbackYear(source);
|
const year = extractAddressFallbackYear(source);
|
||||||
const allTime = ADDRESS_ALL_TIME_PATTERN.test(source);
|
const allTime = ADDRESS_ALL_TIME_PATTERN.test(source);
|
||||||
const accountMatch = source.match(/\b(\d{2}(?:[.,]\d{1,2})?)\b/);
|
const account = extractAddressFallbackAccountToken(source);
|
||||||
const account = accountMatch ? String(accountMatch[1]).replace(",", ".") : null;
|
|
||||||
const docsSignal = ADDRESS_DOCS_SIGNAL_PATTERN.test(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);
|
const balanceSignal = ADDRESS_BALANCE_SIGNAL_PATTERN.test(source);
|
||||||
if (balanceSignal && account) {
|
if (balanceSignal && account) {
|
||||||
let periodClause = "";
|
let periodClause = "";
|
||||||
|
|
@ -2064,23 +2158,25 @@ function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (docsSignal) {
|
if (docsSignal) {
|
||||||
const counterparty = pickAddressFallbackCounterpartyToken(source);
|
if (contractSignal) {
|
||||||
if (counterparty) {
|
const contract = extractAddressFallbackContractToken(sourceRaw || source);
|
||||||
|
if (contract) {
|
||||||
let periodClause = "";
|
let periodClause = "";
|
||||||
let rule = "documents_counterparty_rewrite";
|
let rule = bankSignal ? "bank_operations_contract_rewrite" : "documents_contract_rewrite";
|
||||||
if (allTime) {
|
if (allTime) {
|
||||||
periodClause = " за все время";
|
periodClause = " за все время";
|
||||||
rule = "documents_counterparty_all_time_rewrite";
|
rule = bankSignal ? "bank_operations_contract_all_time_rewrite" : "documents_contract_all_time_rewrite";
|
||||||
}
|
}
|
||||||
else if (monthYear) {
|
else if (monthYear) {
|
||||||
periodClause = ` за ${monthYear}`;
|
periodClause = ` за ${monthYear}`;
|
||||||
rule = "documents_counterparty_month_rewrite";
|
rule = bankSignal ? "bank_operations_contract_month_rewrite" : "documents_contract_month_rewrite";
|
||||||
}
|
}
|
||||||
else if (year) {
|
else if (year) {
|
||||||
periodClause = ` за ${year} год`;
|
periodClause = ` за ${year} год`;
|
||||||
rule = "documents_counterparty_year_rewrite";
|
rule = bankSignal ? "bank_operations_contract_year_rewrite" : "documents_contract_year_rewrite";
|
||||||
}
|
}
|
||||||
const candidate = compactWhitespace(`документы по контрагенту ${counterparty}${periodClause}`);
|
const subject = bankSignal ? "банковские операции" : "документы";
|
||||||
|
const candidate = compactWhitespace(`${subject} по договору ${contract}${periodClause}`);
|
||||||
if (candidate && candidate !== sourceRaw.toLowerCase()) {
|
if (candidate && candidate !== sourceRaw.toLowerCase()) {
|
||||||
return {
|
return {
|
||||||
candidate,
|
candidate,
|
||||||
|
|
@ -2089,6 +2185,35 @@ function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)) {
|
if (source !== sourceRaw.toLowerCase() && isAddressLlmPreDecomposeCandidate(source)) {
|
||||||
return {
|
return {
|
||||||
candidate: source,
|
candidate: source,
|
||||||
|
|
@ -2620,12 +2745,13 @@ export class AssistantService {
|
||||||
};
|
};
|
||||||
this.sessions.appendItem(sessionId, userItem);
|
this.sessions.appendItem(sessionId, userItem);
|
||||||
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => {
|
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 debug = buildAddressDebugPayload(addressLane.debug, llmPreDecomposeMeta);
|
||||||
const assistantItem = {
|
const assistantItem = {
|
||||||
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
|
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||||
session_id: sessionId,
|
session_id: sessionId,
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
text: addressLane.reply_text,
|
text: safeAddressReply,
|
||||||
reply_type: addressLane.reply_type,
|
reply_type: addressLane.reply_type,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
trace_id: debug.trace_id,
|
trace_id: debug.trace_id,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ export type AddressIntent =
|
||||||
| "open_items_by_counterparty_or_contract"
|
| "open_items_by_counterparty_or_contract"
|
||||||
| "list_documents_by_counterparty"
|
| "list_documents_by_counterparty"
|
||||||
| "bank_operations_by_counterparty"
|
| "bank_operations_by_counterparty"
|
||||||
|
| "list_documents_by_contract"
|
||||||
|
| "bank_operations_by_contract"
|
||||||
| "documents_forming_balance"
|
| "documents_forming_balance"
|
||||||
| "unknown";
|
| "unknown";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { extractAddressFilters } from "../src/services/addressFilterExtractor";
|
||||||
import { AddressQueryService } from "../src/services/addressQueryService";
|
import { AddressQueryService } from "../src/services/addressQueryService";
|
||||||
import { buildAddressRecipePlan, selectAddressRecipe } from "../src/services/addressRecipeCatalog";
|
import { buildAddressRecipePlan, selectAddressRecipe } from "../src/services/addressRecipeCatalog";
|
||||||
import { runAddressDecomposeStage } from "../src/services/address_runtime/decomposeStage";
|
import { runAddressDecomposeStage } from "../src/services/address_runtime/decomposeStage";
|
||||||
|
import { composeFactualReply } from "../src/services/address_runtime/composeStage";
|
||||||
|
|
||||||
describe("address query shape classifier", () => {
|
describe("address query shape classifier", () => {
|
||||||
it("classifies explain question as deep-shape", () => {
|
it("classifies explain question as deep-shape", () => {
|
||||||
|
|
@ -35,6 +36,36 @@ describe("address query shape classifier", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("address compose stage utf8 headers", () => {
|
||||||
|
it("renders readable russian header for contract document list", () => {
|
||||||
|
const reply = composeFactualReply("list_documents_by_contract", [
|
||||||
|
{
|
||||||
|
period: "2020-10-15T13:34:53Z",
|
||||||
|
registrator: "Списание с расчетного счета 00000000246",
|
||||||
|
account_dt: "66.02",
|
||||||
|
account_kt: "51",
|
||||||
|
amount: 30819.47,
|
||||||
|
analytics: []
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
expect(reply.text).toContain("Собран список документов по договору (live address lane).");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders readable russian header for contract bank operations", () => {
|
||||||
|
const reply = composeFactualReply("bank_operations_by_contract", [
|
||||||
|
{
|
||||||
|
period: "2020-10-15T13:34:53Z",
|
||||||
|
registrator: "Списание с расчетного счета 00000000246",
|
||||||
|
account_dt: "66.02",
|
||||||
|
account_kt: "51",
|
||||||
|
amount: 30819.47,
|
||||||
|
analytics: []
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
expect(reply.text).toContain("Собран список банковских операций по договору (live address lane).");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("address intent resolver expansion (M2.3a)", () => {
|
describe("address intent resolver expansion (M2.3a)", () => {
|
||||||
it("resolves documents by counterparty intent", () => {
|
it("resolves documents by counterparty intent", () => {
|
||||||
const result = resolveAddressIntent("show documents by counterparty Alfa from 2020-07-01 to 2020-07-31");
|
const result = resolveAddressIntent("show documents by counterparty Alfa from 2020-07-01 to 2020-07-31");
|
||||||
|
|
@ -66,6 +97,23 @@ describe("address intent resolver expansion (M2.3a)", () => {
|
||||||
expect(result.intent).toBe("bank_operations_by_counterparty");
|
expect(result.intent).toBe("bank_operations_by_counterparty");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("resolves documents by contract intent", () => {
|
||||||
|
const result = resolveAddressIntent("Покажи документы по договору 15/24 за 2020");
|
||||||
|
expect(result.intent).toBe("list_documents_by_contract");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves bank operations by contract intent", () => {
|
||||||
|
const result = resolveAddressIntent("Покажи банковские операции по договору 15/24");
|
||||||
|
expect(result.intent).toBe("bank_operations_by_contract");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves bank operations by contract for normalized phrase with linked contract wording", () => {
|
||||||
|
const result = resolveAddressIntent(
|
||||||
|
"Показать банковские операции (счета 51, 60, 62) связанные с договором 15/24."
|
||||||
|
);
|
||||||
|
expect(result.intent).toBe("bank_operations_by_contract");
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps bank_operations_by_counterparty even when account hints are present", () => {
|
it("keeps bank_operations_by_counterparty even when account hints are present", () => {
|
||||||
const result = resolveAddressIntent("Показать банковские операции (счета 51, 62) для контрагента СВК за 2020 год");
|
const result = resolveAddressIntent("Показать банковские операции (счета 51, 62) для контрагента СВК за 2020 год");
|
||||||
expect(result.intent).toBe("bank_operations_by_counterparty");
|
expect(result.intent).toBe("bank_operations_by_counterparty");
|
||||||
|
|
@ -287,6 +335,49 @@ describe("address filter extraction for balance drilldown", () => {
|
||||||
expect(result.warnings).toContain("period_derived_from_year_range_phrase");
|
expect(result.warnings).toContain("period_derived_from_year_range_phrase");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("extracts contract and year period for contract document list", () => {
|
||||||
|
const result = extractAddressFilters(
|
||||||
|
"Покажи документы по договору 15/24 за 2020 год",
|
||||||
|
"list_documents_by_contract"
|
||||||
|
);
|
||||||
|
expect(result.extracted_filters.contract).toBe("15/24");
|
||||||
|
expect(result.extracted_filters.period_from).toBe("2020-01-01");
|
||||||
|
expect(result.extracted_filters.period_to).toBe("2020-12-31");
|
||||||
|
expect(result.warnings).toContain("period_derived_from_year_phrase");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cuts trailing as-of date from contract anchor", () => {
|
||||||
|
const result = extractAddressFilters(
|
||||||
|
"Покажи документы по договору 1-ПМ/2020 на дату 31.07.2020",
|
||||||
|
"list_documents_by_contract"
|
||||||
|
);
|
||||||
|
expect(result.extracted_filters.contract).toBe("1-ПМ/2020");
|
||||||
|
expect(result.extracted_filters.period_from).toBe("2020-07-01");
|
||||||
|
expect(result.extracted_filters.period_to).toBe("2020-07-31");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not force 90-day default window for by-contract query without explicit period", () => {
|
||||||
|
const result = extractAddressFilters(
|
||||||
|
"Покажи документы по договору 15/24",
|
||||||
|
"list_documents_by_contract"
|
||||||
|
);
|
||||||
|
expect(result.extracted_filters.contract).toBe("15/24");
|
||||||
|
expect(result.extracted_filters.period_from).toBeUndefined();
|
||||||
|
expect(result.extracted_filters.period_to).toBeUndefined();
|
||||||
|
expect(result.warnings).not.toContain("period_defaulted_last_90_days");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("extracts heuristic contract token for noisy contract phrase", () => {
|
||||||
|
const result = extractAddressFilters(
|
||||||
|
"доки 15/24 за 2020",
|
||||||
|
"list_documents_by_contract"
|
||||||
|
);
|
||||||
|
expect(result.extracted_filters.contract).toBe("15/24");
|
||||||
|
expect(result.warnings).toContain("contract_anchor_derived_from_heuristic_token");
|
||||||
|
expect(result.extracted_filters.period_from).toBe("2020-01-01");
|
||||||
|
expect(result.extracted_filters.period_to).toBe("2020-12-31");
|
||||||
|
});
|
||||||
|
|
||||||
it("extracts multiline year range period from phrase", () => {
|
it("extracts multiline year range period from phrase", () => {
|
||||||
const result = extractAddressFilters(
|
const result = extractAddressFilters(
|
||||||
"Какие документы по СВК за 2000 - 2025\n год?",
|
"Какие документы по СВК за 2000 - 2025\n год?",
|
||||||
|
|
@ -334,13 +425,24 @@ describe("address query limited taxonomy and stage diagnostics", () => {
|
||||||
expect(result?.debug.mcp_call_status).toBe("skipped");
|
expect(result?.debug.mcp_call_status).toBe("skipped");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns unsupported for not-implemented contract document list intent", async () => {
|
it("routes contract document list intent into address recipe", async () => {
|
||||||
const service = new AddressQueryService();
|
const service = new AddressQueryService();
|
||||||
const result = await service.tryHandle("show documents by contract 15/24");
|
const result = await service.tryHandle("show documents by contract 15/24");
|
||||||
expect(result?.handled).toBe(true);
|
expect(result?.handled).toBe(true);
|
||||||
expect(result?.response_type).toBe("LIMITED_WITH_REASON");
|
expect(result?.debug.detected_intent).toBe("list_documents_by_contract");
|
||||||
expect(result?.debug.limited_reason_category).toBe("unsupported");
|
expect(result?.debug.selected_recipe).toBe("address_documents_by_contract_v1");
|
||||||
expect(result?.debug.mcp_call_status).toBe("skipped");
|
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
|
||||||
|
expect(result?.debug.mcp_call_status).not.toBe("skipped");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("routes bank operations by contract intent into address recipe", async () => {
|
||||||
|
const service = new AddressQueryService();
|
||||||
|
const result = await service.tryHandle("Покажи банковские операции по договору 15/24");
|
||||||
|
expect(result?.handled).toBe(true);
|
||||||
|
expect(result?.debug.detected_intent).toBe("bank_operations_by_contract");
|
||||||
|
expect(result?.debug.selected_recipe).toBe("address_bank_operations_by_contract_v1");
|
||||||
|
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
|
||||||
|
expect(result?.debug.mcp_call_status).not.toBe("skipped");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes resolver and row-stage diagnostics", async () => {
|
it("includes resolver and row-stage diagnostics", async () => {
|
||||||
|
|
@ -461,6 +563,24 @@ describe("address decompose stage follow-up carryover", () => {
|
||||||
expect(result?.baseReasons).toContain("as_of_date_from_followup_context");
|
expect(result?.baseReasons).toContain("as_of_date_from_followup_context");
|
||||||
expect(result?.baseReasons).toContain("address_followup_context_applied");
|
expect(result?.baseReasons).toContain("address_followup_context_applied");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not downgrade inherited follow-up anchor to missing_anchor when period has no rows", async () => {
|
||||||
|
const service = new AddressQueryService();
|
||||||
|
const seed = await service.tryHandle("покажи документы по свк за 2020");
|
||||||
|
expect(seed?.handled).toBe(true);
|
||||||
|
const followup = await service.tryHandle("а теперь только за май 2020", {
|
||||||
|
followupContext: {
|
||||||
|
previous_intent: (seed?.debug.detected_intent as any) ?? "list_documents_by_counterparty",
|
||||||
|
previous_filters: seed?.debug.extracted_filters,
|
||||||
|
previous_anchor_type: (seed?.debug.anchor_type as any) ?? "counterparty",
|
||||||
|
previous_anchor_value: seed?.debug.anchor_value_resolved ?? seed?.debug.anchor_value_raw ?? null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(followup?.handled).toBe(true);
|
||||||
|
if (followup?.reply_type === "partial_coverage") {
|
||||||
|
expect(followup?.debug.limited_reason_category).not.toBe("missing_anchor");
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("address recipe catalog counterparty filtering", () => {
|
describe("address recipe catalog counterparty filtering", () => {
|
||||||
|
|
|
||||||
|
|
@ -215,4 +215,391 @@ describe("assistant address llm pre-decompose candidate preference", () => {
|
||||||
expect(response.debug?.sanitized_user_message).toContain("свк");
|
expect(response.debug?.sanitized_user_message).toContain("свк");
|
||||||
expect(response.debug?.tool_gate_decision).toBe("run_address_lane");
|
expect(response.debug?.tool_gate_decision).toBe("run_address_lane");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps contract anchor in deterministic fallback when llm output is unusable", async () => {
|
||||||
|
const calls: Array<{ message: string }> = [];
|
||||||
|
const addressQueryService = {
|
||||||
|
tryHandle: vi.fn(async (message: string) => {
|
||||||
|
calls.push({ message });
|
||||||
|
return buildAddressLaneResult(message);
|
||||||
|
})
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const normalizerService = {
|
||||||
|
normalize: vi.fn(async () => ({
|
||||||
|
trace_id: "norm-predecompose-contract-docs",
|
||||||
|
ok: true,
|
||||||
|
normalized: {
|
||||||
|
schema_version: "normalized_query_v2_0_2",
|
||||||
|
user_message_raw: "Покажи документы по договору 15/24 за 2020",
|
||||||
|
message_in_scope: true,
|
||||||
|
scope_confidence: "medium",
|
||||||
|
contains_multiple_tasks: false,
|
||||||
|
fragments: []
|
||||||
|
},
|
||||||
|
raw_model_output: null,
|
||||||
|
validation: { passed: true, errors: [] },
|
||||||
|
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||||
|
latency_ms: 10,
|
||||||
|
prompt_version: "normalizer_v2_0_2",
|
||||||
|
schema_version: "v2_0_2",
|
||||||
|
request_count_for_case: 1
|
||||||
|
}))
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const sessions = new AssistantSessionStore();
|
||||||
|
const service = new AssistantService(
|
||||||
|
normalizerService,
|
||||||
|
sessions as any,
|
||||||
|
{} as any,
|
||||||
|
{ persistSession: vi.fn() } as any,
|
||||||
|
addressQueryService
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await service.handleMessage({
|
||||||
|
session_id: `asst-predecompose-contract-docs-${Date.now()}`,
|
||||||
|
user_message: "Покажи документы по договору 15/24 за 2020",
|
||||||
|
llmProvider: "local",
|
||||||
|
useMock: false
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(response.ok).toBe(true);
|
||||||
|
expect(response.reply_type).toBe("factual");
|
||||||
|
expect(calls).toHaveLength(1);
|
||||||
|
expect(calls[0].message).toBe("документы по договору 15/24 за 2020 год");
|
||||||
|
expect(response.debug?.llm_decomposition_applied).toBe(true);
|
||||||
|
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||||
|
expect(response.debug?.fallback_rule_hit).toBe("documents_contract_year_rewrite");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps bank-by-contract intent in deterministic fallback when llm output is unusable", async () => {
|
||||||
|
const calls: Array<{ message: string }> = [];
|
||||||
|
const addressQueryService = {
|
||||||
|
tryHandle: vi.fn(async (message: string) => {
|
||||||
|
calls.push({ message });
|
||||||
|
return buildAddressLaneResult(message);
|
||||||
|
})
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const normalizerService = {
|
||||||
|
normalize: vi.fn(async () => ({
|
||||||
|
trace_id: "norm-predecompose-contract-bank",
|
||||||
|
ok: true,
|
||||||
|
normalized: {
|
||||||
|
schema_version: "normalized_query_v2_0_2",
|
||||||
|
user_message_raw: "Покажи банковские операции по договору 15/24",
|
||||||
|
message_in_scope: true,
|
||||||
|
scope_confidence: "medium",
|
||||||
|
contains_multiple_tasks: false,
|
||||||
|
fragments: []
|
||||||
|
},
|
||||||
|
raw_model_output: null,
|
||||||
|
validation: { passed: true, errors: [] },
|
||||||
|
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||||
|
latency_ms: 10,
|
||||||
|
prompt_version: "normalizer_v2_0_2",
|
||||||
|
schema_version: "v2_0_2",
|
||||||
|
request_count_for_case: 1
|
||||||
|
}))
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const sessions = new AssistantSessionStore();
|
||||||
|
const service = new AssistantService(
|
||||||
|
normalizerService,
|
||||||
|
sessions as any,
|
||||||
|
{} as any,
|
||||||
|
{ persistSession: vi.fn() } as any,
|
||||||
|
addressQueryService
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await service.handleMessage({
|
||||||
|
session_id: `asst-predecompose-contract-bank-${Date.now()}`,
|
||||||
|
user_message: "Покажи банковские операции по договору 15/24",
|
||||||
|
llmProvider: "local",
|
||||||
|
useMock: false
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(response.ok).toBe(true);
|
||||||
|
expect(response.reply_type).toBe("factual");
|
||||||
|
expect(calls).toHaveLength(1);
|
||||||
|
expect(calls[0].message).toBe("банковские операции по договору 15/24");
|
||||||
|
expect(response.debug?.llm_decomposition_applied).toBe(true);
|
||||||
|
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||||
|
expect(response.debug?.fallback_rule_hit).toBe("bank_operations_contract_rewrite");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps month scope for balance fallback in 'year month' phrasing", async () => {
|
||||||
|
const calls: Array<{ message: string }> = [];
|
||||||
|
const addressQueryService = {
|
||||||
|
tryHandle: vi.fn(async (message: string) => {
|
||||||
|
calls.push({ message });
|
||||||
|
return buildAddressLaneResult(message);
|
||||||
|
})
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const normalizerService = {
|
||||||
|
normalize: vi.fn(async () => ({
|
||||||
|
trace_id: "norm-predecompose-balance-year-month",
|
||||||
|
ok: true,
|
||||||
|
normalized: {
|
||||||
|
schema_version: "normalized_query_v2_0_2",
|
||||||
|
user_message_raw: "Какой остаток по счету 60 на 2020 май",
|
||||||
|
message_in_scope: true,
|
||||||
|
scope_confidence: "medium",
|
||||||
|
contains_multiple_tasks: false,
|
||||||
|
fragments: []
|
||||||
|
},
|
||||||
|
raw_model_output: null,
|
||||||
|
validation: { passed: true, errors: [] },
|
||||||
|
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||||
|
latency_ms: 10,
|
||||||
|
prompt_version: "normalizer_v2_0_2",
|
||||||
|
schema_version: "v2_0_2",
|
||||||
|
request_count_for_case: 1
|
||||||
|
}))
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const sessions = new AssistantSessionStore();
|
||||||
|
const service = new AssistantService(
|
||||||
|
normalizerService,
|
||||||
|
sessions as any,
|
||||||
|
{} as any,
|
||||||
|
{ persistSession: vi.fn() } as any,
|
||||||
|
addressQueryService
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await service.handleMessage({
|
||||||
|
session_id: `asst-predecompose-balance-year-month-${Date.now()}`,
|
||||||
|
user_message: "Какой остаток по счету 60 на 2020 май",
|
||||||
|
llmProvider: "local",
|
||||||
|
useMock: false
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(response.ok).toBe(true);
|
||||||
|
expect(response.reply_type).toBe("factual");
|
||||||
|
expect(calls).toHaveLength(1);
|
||||||
|
expect(calls[0].message).toBe("остаток по счету 60 на 2020-05");
|
||||||
|
expect(response.debug?.llm_decomposition_applied).toBe(true);
|
||||||
|
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||||
|
expect(response.debug?.fallback_rule_hit).toBe("balance_month_period_rewrite");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not pick service words as counterparty anchor in noisy docs query", async () => {
|
||||||
|
const calls: Array<{ message: string }> = [];
|
||||||
|
const addressQueryService = {
|
||||||
|
tryHandle: vi.fn(async (message: string) => {
|
||||||
|
calls.push({ message });
|
||||||
|
return buildAddressLaneResult(message);
|
||||||
|
})
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const normalizerService = {
|
||||||
|
normalize: vi.fn(async () => ({
|
||||||
|
trace_id: "norm-predecompose-noisy-counterparty",
|
||||||
|
ok: true,
|
||||||
|
normalized: {
|
||||||
|
schema_version: "normalized_query_v2_0_2",
|
||||||
|
user_message_raw: "что по свк за 2020 год выведи все доки плиз что есть",
|
||||||
|
message_in_scope: true,
|
||||||
|
scope_confidence: "medium",
|
||||||
|
contains_multiple_tasks: false,
|
||||||
|
fragments: []
|
||||||
|
},
|
||||||
|
raw_model_output: null,
|
||||||
|
validation: { passed: true, errors: [] },
|
||||||
|
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||||
|
latency_ms: 10,
|
||||||
|
prompt_version: "normalizer_v2_0_2",
|
||||||
|
schema_version: "v2_0_2",
|
||||||
|
request_count_for_case: 1
|
||||||
|
}))
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const sessions = new AssistantSessionStore();
|
||||||
|
const service = new AssistantService(
|
||||||
|
normalizerService,
|
||||||
|
sessions as any,
|
||||||
|
{} as any,
|
||||||
|
{ persistSession: vi.fn() } as any,
|
||||||
|
addressQueryService
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await service.handleMessage({
|
||||||
|
session_id: `asst-predecompose-noisy-counterparty-${Date.now()}`,
|
||||||
|
user_message: "что по свк за 2020 год выведи все доки плиз что есть",
|
||||||
|
llmProvider: "local",
|
||||||
|
useMock: false
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(response.ok).toBe(true);
|
||||||
|
expect(response.reply_type).toBe("factual");
|
||||||
|
expect(calls).toHaveLength(1);
|
||||||
|
expect(calls[0].message).toBe("документы по контрагенту свк за 2020 год");
|
||||||
|
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||||
|
expect(response.debug?.fallback_rule_hit).toBe("documents_counterparty_year_rewrite");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rewrites payment-style counterparty phrasing to bank operations", async () => {
|
||||||
|
const calls: Array<{ message: string }> = [];
|
||||||
|
const addressQueryService = {
|
||||||
|
tryHandle: vi.fn(async (message: string) => {
|
||||||
|
calls.push({ message });
|
||||||
|
return buildAddressLaneResult(message);
|
||||||
|
})
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const normalizerService = {
|
||||||
|
normalize: vi.fn(async () => ({
|
||||||
|
trace_id: "norm-predecompose-bank-counterparty",
|
||||||
|
ok: true,
|
||||||
|
normalized: {
|
||||||
|
schema_version: "normalized_query_v2_0_2",
|
||||||
|
user_message_raw: "какие платежи были по свк в 2020",
|
||||||
|
message_in_scope: true,
|
||||||
|
scope_confidence: "medium",
|
||||||
|
contains_multiple_tasks: false,
|
||||||
|
fragments: []
|
||||||
|
},
|
||||||
|
raw_model_output: null,
|
||||||
|
validation: { passed: true, errors: [] },
|
||||||
|
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||||
|
latency_ms: 10,
|
||||||
|
prompt_version: "normalizer_v2_0_2",
|
||||||
|
schema_version: "v2_0_2",
|
||||||
|
request_count_for_case: 1
|
||||||
|
}))
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const sessions = new AssistantSessionStore();
|
||||||
|
const service = new AssistantService(
|
||||||
|
normalizerService,
|
||||||
|
sessions as any,
|
||||||
|
{} as any,
|
||||||
|
{ persistSession: vi.fn() } as any,
|
||||||
|
addressQueryService
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await service.handleMessage({
|
||||||
|
session_id: `asst-predecompose-bank-counterparty-${Date.now()}`,
|
||||||
|
user_message: "какие платежи были по свк в 2020",
|
||||||
|
llmProvider: "local",
|
||||||
|
useMock: false
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(response.ok).toBe(true);
|
||||||
|
expect(response.reply_type).toBe("factual");
|
||||||
|
expect(calls).toHaveLength(1);
|
||||||
|
expect(calls[0].message).toBe("банковские операции по контрагенту свк за 2020 год");
|
||||||
|
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||||
|
expect(response.debug?.fallback_rule_hit).toBe("bank_operations_counterparty_year_rewrite");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes short ordinal year like '20й' in noisy docs phrasing", async () => {
|
||||||
|
const calls: Array<{ message: string }> = [];
|
||||||
|
const addressQueryService = {
|
||||||
|
tryHandle: vi.fn(async (message: string) => {
|
||||||
|
calls.push({ message });
|
||||||
|
return buildAddressLaneResult(message);
|
||||||
|
})
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const normalizerService = {
|
||||||
|
normalize: vi.fn(async () => ({
|
||||||
|
trace_id: "norm-predecompose-short-ordinal-year",
|
||||||
|
ok: true,
|
||||||
|
normalized: {
|
||||||
|
schema_version: "normalized_query_v2_0_2",
|
||||||
|
user_message_raw: "бля епт покажи доки по свк за 20й",
|
||||||
|
message_in_scope: true,
|
||||||
|
scope_confidence: "medium",
|
||||||
|
contains_multiple_tasks: false,
|
||||||
|
fragments: []
|
||||||
|
},
|
||||||
|
raw_model_output: null,
|
||||||
|
validation: { passed: true, errors: [] },
|
||||||
|
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||||
|
latency_ms: 10,
|
||||||
|
prompt_version: "normalizer_v2_0_2",
|
||||||
|
schema_version: "v2_0_2",
|
||||||
|
request_count_for_case: 1
|
||||||
|
}))
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const sessions = new AssistantSessionStore();
|
||||||
|
const service = new AssistantService(
|
||||||
|
normalizerService,
|
||||||
|
sessions as any,
|
||||||
|
{} as any,
|
||||||
|
{ persistSession: vi.fn() } as any,
|
||||||
|
addressQueryService
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await service.handleMessage({
|
||||||
|
session_id: `asst-predecompose-short-ordinal-year-${Date.now()}`,
|
||||||
|
user_message: "бля епт покажи доки по свк за 20й",
|
||||||
|
llmProvider: "local",
|
||||||
|
useMock: false
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(response.ok).toBe(true);
|
||||||
|
expect(response.reply_type).toBe("factual");
|
||||||
|
expect(calls).toHaveLength(1);
|
||||||
|
expect(calls[0].message).toBe("документы по контрагенту свк за 2020 год");
|
||||||
|
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||||
|
expect(response.debug?.fallback_rule_hit).toBe("documents_counterparty_year_rewrite");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not treat date fragments as account in balance fallback", async () => {
|
||||||
|
const calls: Array<{ message: string }> = [];
|
||||||
|
const addressQueryService = {
|
||||||
|
tryHandle: vi.fn(async (message: string) => {
|
||||||
|
calls.push({ message });
|
||||||
|
return buildAddressLaneResult(message);
|
||||||
|
})
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const normalizerService = {
|
||||||
|
normalize: vi.fn(async () => ({
|
||||||
|
trace_id: "norm-predecompose-date-not-account",
|
||||||
|
ok: true,
|
||||||
|
normalized: {
|
||||||
|
schema_version: "normalized_query_v2_0_2",
|
||||||
|
user_message_raw: "есть ли долг по договору 1-ПМ/2020 на 2020-07-31",
|
||||||
|
message_in_scope: true,
|
||||||
|
scope_confidence: "medium",
|
||||||
|
contains_multiple_tasks: false,
|
||||||
|
fragments: []
|
||||||
|
},
|
||||||
|
raw_model_output: null,
|
||||||
|
validation: { passed: true, errors: [] },
|
||||||
|
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||||
|
latency_ms: 10,
|
||||||
|
prompt_version: "normalizer_v2_0_2",
|
||||||
|
schema_version: "v2_0_2",
|
||||||
|
request_count_for_case: 1
|
||||||
|
}))
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const sessions = new AssistantSessionStore();
|
||||||
|
const service = new AssistantService(
|
||||||
|
normalizerService,
|
||||||
|
sessions as any,
|
||||||
|
{} as any,
|
||||||
|
{ persistSession: vi.fn() } as any,
|
||||||
|
addressQueryService
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await service.handleMessage({
|
||||||
|
session_id: `asst-predecompose-date-not-account-${Date.now()}`,
|
||||||
|
user_message: "есть ли долг по договору 1-ПМ/2020 на 2020-07-31",
|
||||||
|
llmProvider: "local",
|
||||||
|
useMock: false
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(response.ok).toBe(true);
|
||||||
|
expect(response.reply_type).toBe("factual");
|
||||||
|
expect(calls).toHaveLength(1);
|
||||||
|
expect(calls[0].message).not.toContain("остаток по счету 07");
|
||||||
|
expect(calls[0].message.toLowerCase()).toContain("договору 1-пм/2020");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue