Удержать контекст СВК в follow-up ассистента

This commit is contained in:
2026-06-15 22:56:10 +03:00
parent 7e70ec1d46
commit b3f2e405bb
23 changed files with 950 additions and 91 deletions
@@ -164,6 +164,28 @@ describe("address follow-up temporal regressions", () => {
expect(result?.baseReasons).not.toContain("counterparty_carryover_suppressed_for_short_debt_mirror");
});
it("keeps explicit year period for selected counterparty payout follow-up", () => {
const result = runAddressDecomposeStage("а теперь сколько заплатили?", {
previous_intent: "customer_revenue_and_payments" as const,
target_intent: "customer_revenue_and_payments" as const,
previous_filters: {
counterparty: "Группа СВК",
period_from: "2020-01-01",
period_to: "2020-12-31"
},
previous_anchor_type: "counterparty" as const,
previous_anchor_value: "Группа СВК",
resolved_counterparty_from_display: true
});
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("customer_revenue_and_payments");
expect(result?.filters.extracted_filters.counterparty).toBe("Группа СВК");
expect(result?.filters.extracted_filters.period_from).toBe("2020-01-01");
expect(result?.filters.extracted_filters.period_to).toBe("2020-12-31");
expect(result?.baseReasons).toContain("period_from_followup_context");
});
it("keeps same-date inventory pivot anchored to the previous VAT date", () => {
const result = runAddressDecomposeStage("какие остатки по складу на эту же дату", {
previous_intent: "vat_payable_confirmed_as_of_date",
@@ -454,6 +476,29 @@ describe("address follow-up temporal regressions", () => {
expect(movements?.baseReasons).toContain("counterparty_from_followup_context");
});
it("keeps value-flow period for counterparty document evidence follow-up", () => {
const followupContext = {
previous_intent: "customer_revenue_and_payments" as const,
target_intent: "list_documents_by_counterparty" as const,
previous_filters: {
counterparty: "Группа СВК",
period_from: "2020-01-01",
period_to: "2020-12-31"
},
previous_anchor_type: "counterparty" as const,
previous_anchor_value: "Группа СВК",
resolved_counterparty_from_display: true
};
const documents = runAddressDecomposeStage("а по документам?", followupContext);
expect(documents?.intent.intent).toBe("list_documents_by_counterparty");
expect(documents?.filters.extracted_filters.counterparty).toBe("Группа СВК");
expect(documents?.filters.extracted_filters.period_from).toBe("2020-01-01");
expect(documents?.filters.extracted_filters.period_to).toBe("2020-12-31");
expect(documents?.baseReasons).toContain("period_from_followup_context");
});
it("replaces pronoun chain anchors from counterparty follow-up context", () => {
const followupContext = {
previous_intent: "customer_revenue_and_payments" as const,
@@ -220,6 +220,262 @@ describe("address navigation state", () => {
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
});
it("captures resolved entity-resolution focus as fresh current discovery state", () => {
const base = createEmptyAddressNavigationState("asst-entity", "2026-04-12T10:00:00.000Z");
const assistantItem = {
message_id: "msg-entity",
session_id: "asst-entity",
role: "assistant",
text: "В проверенном каталожном срезе 1С найден наиболее вероятный контрагент: Группа СВК.",
created_at: "2026-04-12T10:12:30.000Z",
debug: {
detected_mode: "chat",
detected_intent: "unknown",
extracted_filters: {},
anchor_type: "unknown",
mcp_discovery_response_applied: true,
mcp_discovery_selected_chain_id: "entity_resolution",
assistant_mcp_discovery_entry_point_v1: {
turn_input: {
turn_meaning_ref: {
asked_domain_family: "entity_resolution",
asked_action_family: "search_business_entity",
explicit_entity_candidates: ["СВК"],
stale_replay_forbidden: true
}
},
bridge: {
pilot: {
pilot_scope: "entity_resolution_search_v1",
derived_entity_resolution: {
requested_entity: "СВК",
resolution_status: "resolved",
resolved_entity: "Группа СВК"
}
}
}
},
dialog_continuation_contract_v2: {
decision: "new_topic"
}
}
} as any;
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 1);
expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty");
expect(evolved.session_context.active_focus_object?.label).toBe("Группа СВК");
expect(evolved.result_sets[0]?.filters.counterparty).toBe("Группа СВК");
expect(evolved.navigation_history[0]?.target_object_id).toBe("counterparty:группа свк");
});
it("captures single-direction value-flow focus before the net bundle exists", () => {
const base = createEmptyAddressNavigationState("asst-vf-single", "2026-04-12T10:00:00.000Z");
const assistantItem = {
message_id: "msg-vf-single",
session_id: "asst-vf-single",
role: "assistant",
text: "Входящие денежные поступления по контрагенту Группа СВК за период 2020: 12 093 465 руб.",
created_at: "2026-04-12T10:13:00.000Z",
debug: {
detected_mode: "chat",
detected_intent: "unknown",
extracted_filters: {
period_from: "2020-01-01",
period_to: "2020-12-31"
},
anchor_type: "unknown",
mcp_discovery_response_applied: true,
mcp_discovery_selected_chain_id: "value_flow",
assistant_mcp_discovery_entry_point_v1: {
turn_input: {
turn_meaning_ref: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020",
stale_replay_forbidden: true
}
},
bridge: {
pilot: {
pilot_scope: "counterparty_value_flow_query_movements_v1",
derived_value_flow: {
value_flow_direction: "incoming_customer_revenue",
counterparty: "Группа СВК",
period_scope: "2020",
total_amount_human_ru: "12 093 465 руб."
}
}
}
},
dialog_continuation_contract_v2: {
decision: "continue_previous"
}
}
} as any;
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 2);
expect(evolved.result_sets[0]?.intent).toBe("customer_revenue_and_payments");
expect(evolved.result_sets[0]?.filters.counterparty).toBe("Группа СВК");
expect(evolved.session_context.active_focus_object?.label).toBe("Группа СВК");
expect(evolved.session_context.date_scope.period_from).toBe("2020-01-01");
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
});
it("stores bidirectional value-flow proof bundle for adjacent evidence drilldowns", () => {
const base = createEmptyAddressNavigationState("asst-vf-bundle", "2026-04-12T10:00:00.000Z");
const valueFlowBundle = {
counterparty: "Группа СВК",
period_scope: "2020",
incoming_customer_revenue: {
total_amount_human_ru: "12 093 465 руб."
},
outgoing_supplier_payout: {
total_amount_human_ru: "0 руб."
},
net_amount_human_ru: "12 093 465 руб.",
net_direction: "net_incoming"
};
const netItem = {
message_id: "msg-vf-net",
session_id: "asst-vf-bundle",
role: "assistant",
text: "По контрагенту Группа СВК за период 2020 получили 12 093 465 руб., заплатили 0 руб.; расчетное нетто в нашу сторону: 12 093 465 руб.",
created_at: "2026-04-12T10:14:00.000Z",
debug: {
detected_mode: "chat",
detected_intent: "customer_revenue_and_payments",
extracted_filters: {
period_from: "2020-01-01",
period_to: "2020-12-31"
},
anchor_type: "unknown",
mcp_discovery_response_applied: true,
mcp_discovery_selected_chain_id: "value_flow_comparison",
assistant_mcp_discovery_entry_point_v1: {
turn_input: {
turn_meaning_ref: {
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020",
stale_replay_forbidden: true
}
},
bridge: {
pilot: {
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
derived_bidirectional_value_flow: valueFlowBundle
}
}
},
dialog_continuation_contract_v2: {
decision: "continue_previous"
}
}
} as any;
const afterNet = evolveAddressNavigationStateWithAssistantItem(base, netItem, 5);
expect(afterNet.session_context.comparison_scope?.counterparty?.label).toBe("Группа СВК");
expect(afterNet.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
valueFlowBundle
);
const docsItem = {
message_id: "msg-vf-docs",
session_id: "asst-vf-bundle",
role: "assistant",
text: "По документам по контрагенту Группа СВК за 2020 полный срез не подтвержден.",
created_at: "2026-04-12T10:15:00.000Z",
debug: {
detected_mode: "chat",
detected_intent: "list_documents_by_counterparty",
extracted_filters: {
counterparty: "Группа СВК",
period_from: "2020-01-01",
period_to: "2020-12-31"
},
anchor_type: "unknown",
mcp_discovery_response_applied: true,
mcp_discovery_selected_chain_id: "document_evidence",
assistant_mcp_discovery_entry_point_v1: {
turn_input: {
turn_meaning_ref: {
asked_domain_family: "documents",
asked_action_family: "list_documents",
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020",
stale_replay_forbidden: true
}
},
bridge: {
pilot: {
pilot_scope: "counterparty_document_evidence_query_documents_v1"
}
}
},
dialog_continuation_contract_v2: {
decision: "continue_previous"
}
}
} as any;
const afterDocs = evolveAddressNavigationStateWithAssistantItem(afterNet, docsItem, 6);
expect(afterDocs.session_context.active_focus_object?.label).toBe("Группа СВК");
expect(afterDocs.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
valueFlowBundle
);
const movementsItem = {
message_id: "msg-vf-movements",
session_id: "asst-vf-bundle",
role: "assistant",
text: "По движениям по контрагенту Группа СВК за 2020 полный срез не подтвержден.",
created_at: "2026-04-12T10:16:00.000Z",
debug: {
detected_mode: "chat",
detected_intent: "inventory_purchase_documents_for_item",
extracted_filters: {
counterparty: "Группа СВК",
period_from: "2020-01-01",
period_to: "2020-12-31"
},
anchor_type: "unknown",
mcp_discovery_response_applied: true,
mcp_discovery_selected_chain_id: "movement_evidence",
assistant_mcp_discovery_entry_point_v1: {
turn_input: {
turn_meaning_ref: {
asked_domain_family: "movements",
asked_action_family: "list_movements",
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020",
stale_replay_forbidden: true
}
},
bridge: {
pilot: {
pilot_scope: "counterparty_movement_evidence_query_movements_v1"
}
}
},
dialog_continuation_contract_v2: {
decision: "continue_previous"
}
}
} as any;
const afterMovements = evolveAddressNavigationStateWithAssistantItem(afterDocs, movementsItem, 7);
expect(afterMovements.result_sets.at(-1)?.intent).toBe("bank_operations_by_counterparty");
expect(afterMovements.result_sets.at(-1)?.type).toBe("bank_operations_list");
expect(afterMovements.session_context.active_focus_object?.label).toBe("Группа СВК");
expect(afterMovements.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
valueFlowBundle
);
});
it("keeps selected counterparty focus for company boundary summaries", () => {
const initial = createEmptyAddressNavigationState("asst-boundary", "2026-04-12T10:00:00.000Z");
const assistantItem = {
@@ -684,6 +684,27 @@ describe("assistantContinuityPolicy organization authority", () => {
});
});
it("carries value-flow temporal scope into follow-up evidence drilldowns", () => {
const filters = applyTemporalCarryoverFilters(
{
counterparty: "Группа СВК"
},
{
as_of_date: null,
period_from: "2020-01-01",
period_to: "2020-12-31"
},
null,
"customer_revenue_and_payments"
);
expect(filters).toEqual({
counterparty: "Группа СВК",
period_from: "2020-01-01",
period_to: "2020-12-31"
});
});
it("resolves inventory root pivots through one shared helper", () => {
const flags = resolveInventoryFollowupPivotFlags(
{ intent: "inventory_on_hand_as_of_date" },
@@ -130,15 +130,14 @@ describe("assistant MCP discovery answer adapter", () => {
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
const answerText = [draft.headline, ...draft.confirmed_lines, ...draft.unknown_lines].join("\n");
const answerText = [draft.headline, ...draft.confirmed_lines, ...draft.inference_lines, ...draft.unknown_lines].join("\n");
expect(draft.answer_mode).toBe("checked_sources_only");
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(answerText).toContain("0 руб.");
expect(answerText).toContain("исходящих платежей/списаний не найдено");
expect(answerText).toContain("not_found");
expect(answerText).toContain("Группа СВК");
expect(answerText).toContain("2020");
expect(draft.next_step_line).toContain("соседних периодах");
expect(draft.next_step_line).toContain("двусторонний денежный поток");
expect(draft.unknown_lines.join("\n")).toContain("outside the checked period");
});
it("turns generic document evidence into a bounded document answer draft", async () => {
@@ -1464,7 +1463,7 @@ describe("assistant MCP discovery answer adapter", () => {
expect(draft.unknown_lines).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot");
});
it("renders zero-row supplier payout as a checked negative with period and counterparty", async () => {
it("renders zero-row supplier payout as a confirmed scoped zero with period and counterparty", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
@@ -1479,18 +1478,18 @@ describe("assistant MCP discovery answer adapter", () => {
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
const confirmedText = draft.confirmed_lines.join("\n");
const inferenceText = draft.inference_lines.join("\n");
const unknownText = draft.unknown_lines.join("\n");
expect(draft.answer_mode).toBe("checked_sources_only");
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(draft.headline).toContain("Группа СВК");
expect(draft.headline).toContain("2020");
expect(draft.headline).toContain("исходящих платежей");
expect(draft.headline).toContain("0 руб.");
expect(confirmedText).toContain("Группа СВК");
expect(confirmedText).toContain("ООО Альтернатива Плюс");
expect(confirmedText).toContain("2020");
expect(confirmedText).toContain("не найдено");
expect(unknownText).toContain("вне периода 2020");
expect(unknownText).toContain("вне доступного банковского контура");
expect(confirmedText).toContain("0 руб.");
expect(confirmedText).toContain("Граница");
expect(inferenceText).toContain("not_found result was treated as zero");
expect(unknownText).toContain("outside the checked period");
});
it("turns bidirectional value-flow evidence into a bounded net cash answer draft", async () => {
@@ -1650,6 +1650,38 @@ describe("assistant MCP discovery pilot executor", () => {
expect(String(call?.query ?? "")).toContain("СписаниеСРасчетногоСчета");
});
it("treats zero-row supplier payout query as confirmed not-found inside checked scope", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "payout",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_payouts_or_outflow"
}
});
const deps = buildDeps([]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("counterparty_supplier_payout_query_movements_v1");
expect(result.evidence.evidence_status).toBe("confirmed");
expect(result.evidence.answer_permission).toBe("confirmed_answer");
expect(result.evidence.confirmed_facts[0]).toContain("not_found");
expect(result.reason_codes).toContain("pilot_derived_value_flow_from_confirmed_rows");
expect(result.evidence.reason_codes).toContain("confirmed_no_match_fact_with_allowed_mcp_evidence");
expect(result.derived_value_flow).toMatchObject({
value_flow_direction: "outgoing_supplier_payout",
counterparty: "SVK",
period_scope: "2020",
rows_matched: 0,
rows_with_amount: 0,
total_amount: 0,
total_amount_human_ru: "0 руб."
});
});
it("marks value-flow coverage as limited when the probe row limit is reached", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
@@ -1602,8 +1602,9 @@ describe("assistant MCP discovery response candidate", () => {
expect(firstLine).toContain("По подтвержденным строкам 1С");
expect(firstLine).toContain("входящие 157 192 981,43 руб.");
expect(firstLine).toContain("исходящие 35 439 044,74 руб.");
expect(firstLine).toContain("не аудиторское заключение");
expect(firstLine).toContain("не подтвержденная чистая прибыль");
expect(firstLine).toContain("не финансовый аудит");
expect(firstLine).toContain("не подтверждение чистой прибыли");
expect(firstLine).not.toContain("аудиторское заключение");
expect(candidate.reply_text).toContain("Интерпретация:");
expect(candidate.reply_text).toContain("Что видно в ограниченном денежном срезе");
expect(candidate.reply_text).toContain("Что не подтверждено в этом срезе");
@@ -753,7 +753,73 @@ describe("assistant MCP discovery response policy", () => {
expect(result.reply_text).toContain("20 653 490");
expect(result.reply_text).toContain("2 129 651");
expect(result.reason_codes).toContain("mcp_discovery_response_policy_value_flow_action_conflict_allows_candidate_override");
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_document_list_address_reply");
});
it("overrides exact document-list replies when they contradict the discovery period scope", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply:
"Контрагент: Группа СВК. Найдено документов: 19.\n" +
"1. 2021-11-10T12:00:07Z | Поступление на расчетный счет 00000000013 от 10.11.2021 12:00:07 | аналитика: Группа СВК",
currentReplySource: "address_query_runtime_v1",
currentReplyType: "factual",
addressRuntimeMeta: {
detected_intent: "list_documents_by_counterparty",
selected_recipe: "address_documents_by_counterparty_v1",
mcp_call_status: "matched_non_empty",
response_type: "FACTUAL_LIST",
truth_mode: "confirmed",
extracted_filters: {
counterparty: "Группа СВК",
as_of_date: "2026-06-15"
},
dialog_continuation_contract_v2: {
target_intent: "list_documents_by_counterparty"
},
assistant_mcp_discovery_entry_point_v1: entryPoint({
turn_input: {
adapter_status: "ready",
should_run_discovery: true,
turn_meaning_ref: {
asked_domain_family: "documents",
asked_action_family: "list_documents",
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "document_evidence"
},
data_need_graph: {
business_fact_family: "document_evidence",
action_family: "list_documents",
subject_candidates: ["Группа СВК"]
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
answer_draft: {
answer_mode: "bounded_inference_only",
headline: "По документам по контрагенту Группа СВК за 2020 полный срез не подтвержден.",
confirmed_lines: [],
inference_lines: ["Подтвержденных строк документов за 2020 этим поиском не найдено."],
unknown_lines: ["Полный исторический срез вне 2020 этим поиском не подтвержден."],
limitation_lines: [],
next_step_line: null
}
}
})
}
});
expect(result.applied).toBe(true);
expect(result.decision).toBe("apply_candidate");
expect(result.reply_text).toContain("за 2020");
expect(result.reply_text).not.toContain("2021-11-10");
expect(result.reason_codes).toContain(
"mcp_discovery_response_policy_document_list_temporal_conflict_allows_candidate_override"
);
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_document_list_address_reply");
});