ARCH: ввести broad business evaluation bridge

This commit is contained in:
2026-04-21 19:37:37 +03:00
parent d323dcd509
commit bda7ca9cc1
29 changed files with 1648 additions and 97 deletions
@@ -3141,5 +3141,113 @@ describe("assistant address follow-up carryover", () => {
expect(calls[0].options?.followupContext?.root_filters?.counterparty).toBeUndefined();
expect(normalizerService.normalize).not.toHaveBeenCalled();
});
it.skip("passes grounded MCP discovery payout context into a short year-switch follow-up", async () => {
const followupMessage = "а теперь за 2021?";
const calls: Array<{ message: string; options?: any }> = [];
const addressQueryService = {
tryHandle: vi.fn(async (message: string, options?: any) => {
calls.push({ message, options });
if (message === followupMessage && options?.followupContext) {
return buildAddressLaneResult({
reply_text: "Подтверждены исходящие платежи по Группа СВК за 2021 год.",
debug: {
...buildAddressLaneResult().debug,
detected_intent: "supplier_payouts_profile",
selected_recipe: "address_supplier_payouts_profile_v1",
extracted_filters: {
counterparty: "Группа СВК",
organization: "ООО Альтернатива Плюс",
period_from: "2021-01-01",
period_to: "2021-12-31"
},
reasons: ["address_action_detected", "address_entity_detected", "address_followup_context_applied"]
}
});
}
return null;
})
} as any;
const normalizerService = {
normalize: vi.fn(async () => ({
assistant_reply: "normalizer_fallback_should_not_be_used",
reply_type: "partial_coverage",
debug: {}
}))
} as any;
const sessions = new AssistantSessionStore();
const service = new AssistantService(
normalizerService,
sessions as any,
{} as any,
{ persistSession: vi.fn() } as any,
addressQueryService
);
const sessionId = `asst-discovery-followup-year-switch-${Date.now()}`;
sessions.appendItem(sessionId, {
message_id: "msg-discovery-payout-seed",
session_id: sessionId,
role: "assistant",
text: "Подтверждены исходящие платежи по Группа СВК за 2020 год.",
reply_type: "partial_coverage",
created_at: "2026-04-20T10:00:00.000Z",
trace_id: "living-discovery-seed",
debug: {
execution_lane: "living_chat",
mcp_discovery_response_applied: true,
assistant_active_organization: "ООО Альтернатива Плюс",
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
asked_action_family: "payout",
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
pilot: {
pilot_scope: "counterparty_supplier_payout_query_movements_v1"
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
}
}
}
}
} as any);
const response = await service.handleMessage({
session_id: sessionId,
user_message: followupMessage,
useMock: true
} as any);
expect(response.ok).toBe(true);
expect(response.reply_type).toBe("factual");
expect(calls).toHaveLength(1);
expect(calls[0].message).toBe(followupMessage);
expect(calls[0].options?.followupContext?.previous_intent).toBe("supplier_payouts_profile");
expect(calls[0].options?.followupContext?.previous_discovery_pilot_scope).toBe(
"counterparty_supplier_payout_query_movements_v1"
);
expect(calls[0].options?.followupContext?.previous_anchor_type).toBe("counterparty");
expect(calls[0].options?.followupContext?.previous_anchor_value).toBe("Группа СВК");
expect(calls[0].options?.followupContext?.previous_filters).toMatchObject({
counterparty: "Группа СВК",
organization: "ООО Альтернатива Плюс",
period_from: "2020-01-01",
period_to: "2020-12-31"
});
expect(normalizerService.normalize).not.toHaveBeenCalled();
});
});
@@ -154,6 +154,97 @@ describe("assistant address orchestration runtime adapter", () => {
);
});
it("passes grounded discovery follow-up carryover into MCP discovery entry point for a short year switch", async () => {
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
entry_status: "bridge_executed",
hot_runtime_wired: false,
discovery_attempted: true
}));
const input = buildInput({
userMessage: "а теперь за 2021?",
runAddressLlmPreDecompose: vi.fn(async () => ({
attempted: true,
applied: false,
effectiveMessage: "а теперь за 2021?",
reason: "raw_kept",
predecomposeContract: {
mode: "unsupported",
intent: "unknown",
period: {
scope: "year",
period_from: "2021-01-01",
period_to: "2021-12-31",
has_explicit_period: true
}
}
})),
resolveAddressFollowupCarryoverContext: vi.fn(() => ({
followupContext: {
previous_intent: "supplier_payouts_profile",
target_intent: "supplier_payouts_profile",
previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1",
previous_anchor_type: "counterparty",
previous_anchor_value: "Группа СВК",
previous_filters: {
counterparty: "Группа СВК",
organization: "ООО Альтернатива Плюс",
period_from: "2020-01-01",
period_to: "2020-12-31"
}
}
})),
resolveAssistantOrchestrationDecision: vi.fn(() => ({
runAddressLane: true,
livingMode: "address_data",
livingReason: "address_lane_triggered",
toolGateDecision: "run_address_lane",
toolGateReason: "followup_context_detected",
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1",
assistant_turn_meaning: {
schema_version: "assistant_turn_meaning_v1",
raw_message: "а теперь за 2021?",
effective_message: "а теперь за 2021?",
explicit_entity_candidates: []
}
}
})),
runMcpDiscoveryRuntimeEntryPoint
});
const output = await buildAssistantAddressOrchestrationRuntime(input);
expect(output.orchestrationDecision.runAddressLane).toBe(true);
expect(runMcpDiscoveryRuntimeEntryPoint).toHaveBeenCalledWith(
expect.objectContaining({
userMessage: "а теперь за 2021?",
effectiveMessage: "а теперь за 2021?",
followupContext: expect.objectContaining({
previous_intent: "supplier_payouts_profile",
target_intent: "supplier_payouts_profile",
previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1",
previous_anchor_type: "counterparty",
previous_anchor_value: "Группа СВК",
previous_filters: expect.objectContaining({
counterparty: "Группа СВК",
organization: "ООО Альтернатива Плюс",
period_from: "2020-01-01",
period_to: "2020-12-31"
})
})
})
);
expect(output.addressRuntimeMeta.mcpDiscoveryRuntimeEntryPoint).toEqual(
expect.objectContaining({
entry_status: "bridge_executed",
discovery_attempted: true,
hot_runtime_wired: false
})
);
});
it("keeps address orchestration alive when MCP discovery entry point fails", async () => {
const input = buildInput({
runMcpDiscoveryRuntimeEntryPoint: vi.fn(async () => {
@@ -7,6 +7,7 @@ import {
applyTemporalCarryoverFilters,
buildRootScopedCarryoverFilters,
hydrateInventoryRootFrameState,
readAddressDebugIntent,
readAddressDebugTemporalScope,
resolveNavigationSessionContextState,
resolveAddressDebugCarryoverFilters,
@@ -147,6 +148,49 @@ describe("assistantContinuityPolicy organization authority", () => {
});
});
it("hydrates intent and carryover filters from grounded MCP discovery payout scope", () => {
const debug = {
execution_lane: "living_chat",
mcp_discovery_response_applied: true,
assistant_active_organization: "ООО Альтернатива Плюс",
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
asked_action_family: "payout",
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
pilot: {
pilot_scope: "counterparty_supplier_payout_query_movements_v1"
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
}
}
}
};
expect(readAddressDebugIntent(debug)).toBe("supplier_payouts_profile");
expect(readAddressDebugTemporalScope(debug)).toEqual({
asOfDate: null,
periodFrom: "2020-01-01",
periodTo: "2020-12-31"
});
expect(resolveAddressDebugCarryoverFilters(debug)).toEqual({
counterparty: "Группа СВК",
organization: "ООО Альтернатива Плюс",
period_from: "2020-01-01",
period_to: "2020-12-31"
});
});
it("resolves navigation session context through one shared helper", () => {
const state = resolveNavigationSessionContextState({
session_context: {
@@ -133,6 +133,91 @@ describe("assistant living chat runtime adapter", () => {
expect(executeLlmChat).toHaveBeenCalledTimes(1);
});
it("builds deterministic broad business evaluation summary from grounded continuity instead of replaying lifecycle noise", async () => {
const executeLlmChat = vi.fn(async () => "raw-llm");
const input = buildRuntimeInput({
userMessage: "Как ты оценишь деятельность компании?",
modeDecision: { mode: "chat", reason: "unsupported_current_turn_meaning_boundary" },
sessionScope: {
knownOrganizations: ["ООО Альтернатива Плюс"],
selectedOrganization: null,
activeOrganization: "ООО Альтернатива Плюс"
},
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "address_query",
answer_grounding_check: {
status: "grounded"
},
detected_intent: "counterparty_activity_lifecycle",
extracted_filters: {
organization: "ООО Альтернатива Плюс"
}
}
},
{
role: "assistant",
debug: {
execution_lane: "living_chat",
mcp_discovery_response_applied: true,
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
},
pilot: {
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
derived_bidirectional_value_flow: {
net_amount_human_ru: "3 865 501,50 руб.",
incoming_customer_revenue: {
total_amount_human_ru: "47 628 853,03 руб."
},
outgoing_supplier_payout: {
total_amount_human_ru: "43 763 351,53 руб."
}
}
}
}
}
}
}
],
addressRuntimeMeta: {
toolGateReason: "unsupported_current_turn_meaning_boundary",
orchestrationContract: {
unsupported_current_turn_meaning_boundary: true,
assistant_turn_meaning: {
unsupported_but_understood_family: "broad_business_evaluation"
}
}
},
executeLlmChat
});
const output = await runAssistantLivingChatRuntime(input);
expect(output.handled).toBe(true);
expect(output.chatText.toLowerCase()).toContain("оценка бизнеса");
expect(output.chatText).toContain("ООО Альтернатива Плюс");
expect(output.chatText).toContain("Группа СВК");
expect(output.chatText).toContain("нетто");
expect(output.debug?.living_chat_response_source).toBe("deterministic_broad_business_evaluation_contract");
expect(executeLlmChat).not.toHaveBeenCalled();
});
it("builds deterministic boundary for unsupported current-turn business meaning", async () => {
const executeLlmChat = vi.fn(async () => "raw-llm");
const input = buildRuntimeInput({
@@ -323,4 +323,50 @@ describe("assistant MCP discovery response policy", () => {
expect(result.reason_codes).toContain("mcp_discovery_response_policy_candidate_not_eligible");
expect(result.reason_codes).toContain("mcp_discovery_response_policy_kept_current_reply");
});
it("keeps deterministic broad business evaluation summary instead of replacing it with a clarification candidate", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply: "Коротко: по уже подтвержденным данным в 1С компания выглядит живой операционно.",
currentReplySource: "deterministic_broad_business_evaluation_contract",
livingChatSource: "deterministic_broad_business_evaluation_contract",
modeDecisionReason: "unsupported_current_turn_meaning_boundary",
addressRuntimeMeta: {
assistant_mcp_discovery_entry_point_v1: entryPoint({
turn_input: {
adapter_status: "ready",
should_run_discovery: true,
turn_meaning_ref: {
asked_domain_family: "business_summary",
asked_action_family: "broad_evaluation",
unsupported_but_understood_family: "broad_business_evaluation",
stale_replay_forbidden: true
}
},
bridge: {
bridge_status: "needs_clarification",
user_facing_response_allowed: true,
business_fact_answer_allowed: false,
requires_user_clarification: true,
answer_draft: {
answer_mode: "needs_clarification",
headline: "Нужно уточнить контекст перед поиском в 1С.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: ["MCP discovery pilot needs more scope before execution"],
limitation_lines: ["MCP discovery pilot needs more scope before execution"],
next_step_line: "Уточните контрагента, период или организацию."
}
}
})
}
});
expect(result.applied).toBe(false);
expect(result.decision).toBe("keep_current_reply");
expect(result.reply_source).toBe("deterministic_broad_business_evaluation_contract");
expect(result.reply_text).toContain("компания выглядит живой операционно");
expect(result.reason_codes).toContain(
"mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate"
);
});
});
@@ -151,6 +151,69 @@ describe("assistant MCP discovery turn input adapter", () => {
expect(result.reason_codes).toContain("mcp_discovery_monthly_aggregation_signal_detected");
});
it("seeds short monthly follow-up from prior bidirectional discovery context", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "а по месяцам?",
followupContext: {
previous_discovery_pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
previous_filters: {
counterparty: "Группа СВК",
organization: "ООО Альтернатива Плюс",
period_from: "2020-01-01",
period_to: "2020-12-31"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "Группа СВК"
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.source_signal).toBe("followup_context");
expect(result.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
asked_aggregation_axis: "month",
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting",
stale_replay_forbidden: true
});
expect(result.reason_codes).toContain("mcp_discovery_seeded_from_followup_context");
expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context");
expect(result.reason_codes).toContain("mcp_discovery_date_scope_from_followup_context");
});
it("switches the checked year on a short payout follow-up while keeping prior discovery counterparty", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "а теперь за 2021?",
followupContext: {
previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1",
previous_filters: {
counterparty: "Группа СВК",
organization: "ООО Альтернатива Плюс",
period_from: "2020-01-01",
period_to: "2020-12-31"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "Группа СВК"
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "payout",
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2021",
unsupported_but_understood_family: "counterparty_payouts_or_outflow",
stale_replay_forbidden: true
});
});
it("does not activate discovery for supported exact current-turn intent", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
assistantTurnMeaning: {
@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import {
buildAddressMemoryRecapReply,
buildBroadBusinessEvaluationReply,
buildSelectedObjectAnswerInspectionReply,
createAssistantMemoryRecapPolicy,
resolveAssistantLivingChatMemoryContext
@@ -385,6 +386,75 @@ describe("assistantMemoryRecapPolicy", () => {
expect(reply).toContain("43 763 351,53 руб.");
});
it("builds deterministic broad business evaluation summary from recent grounded organization facts", () => {
const sessionItems = [
{
role: "assistant",
debug: {
execution_lane: "address_query",
answer_grounding_check: {
status: "grounded"
},
detected_intent: "counterparty_activity_lifecycle",
extracted_filters: {
organization: "ООО Альтернатива Плюс"
}
}
},
{
role: "assistant",
debug: {
execution_lane: "living_chat",
mcp_discovery_response_applied: true,
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
},
pilot: {
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
derived_bidirectional_value_flow: {
net_amount_human_ru: "3 865 501,50 руб.",
incoming_customer_revenue: {
total_amount_human_ru: "47 628 853,03 руб."
},
outgoing_supplier_payout: {
total_amount_human_ru: "43 763 351,53 руб."
}
}
}
}
}
}
}
];
const reply = buildBroadBusinessEvaluationReply({
organization: "ООО Альтернатива Плюс",
addressDebug: sessionItems[1].debug as any,
sessionItems,
toNonEmptyString: (value: unknown) => {
const text = String(value ?? "").trim();
return text.length > 0 ? text : null;
}
});
expect(reply.toLowerCase()).toContain("оценка бизнеса");
expect(reply).toContain("ООО Альтернатива Плюс");
expect(reply).toContain("47 628 853,03");
});
it("builds grounded answer inspection reply for MCP discovery net answer", () => {
const context = resolveAssistantLivingChatMemoryContext({
modeDecisionReason: "answer_inspection_followup_detected",
@@ -606,7 +606,7 @@ describe("assistantRoutePolicy", () => {
expect(decision.orchestrationContract?.organization_scope_switch_detected).not.toBe(true);
});
it("keeps company activity assessment follow-up in address lane when lifecycle intent is resolved from grounded continuity", () => {
it("routes broad business evaluation follow-up to chat instead of replaying lifecycle address intent", () => {
const policy = buildPolicy({
resolveAddressIntent: () => ({ intent: "counterparty_activity_lifecycle", confidence: "high" }),
findLastGroundedAddressAnswerDebug: () => ({
@@ -618,6 +618,15 @@ describe("assistantRoutePolicy", () => {
period_to: "2026-04-18"
}
}),
resolveAssistantTurnMeaning: () => ({
schema_version: "assistant_turn_meaning_v1",
asked_domain_family: "business_summary",
asked_action_family: "broad_evaluation",
explicit_intent_candidate: null,
unsupported_but_understood_family: "broad_business_evaluation",
stale_replay_forbidden: true,
reason_codes: ["broad_business_evaluation_current_turn_signal"]
}),
resolveAddressToolGateDecision: () => ({
runAddressLane: false,
decision: "skip_address_lane",
@@ -666,9 +675,12 @@ describe("assistantRoutePolicy", () => {
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("unsupported_current_turn_meaning_boundary");
expect(decision.livingMode).toBe("chat");
expect(decision.livingReason).toBe("unsupported_current_turn_meaning_boundary");
expect(decision.orchestrationContract?.unsupported_current_turn_family).toBe("broad_business_evaluation");
});
it("recovers an address route from current-turn meaning when L0 resolver is noisy", () => {
@@ -1014,6 +1014,45 @@ describe("assistantTransitionPolicy", () => {
expect(carryover).toBeNull();
});
it("drops carryover for broad business evaluation so lifecycle context does not stick to the new question", () => {
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: "Lifecycle answer",
debug: {
execution_lane: "address_query",
answer_grounding_check: { status: "grounded" },
detected_intent: "counterparty_activity_lifecycle",
extracted_filters: {
organization: 'ООО "Альтернатива Плюс"',
period_to: "2020-12-31"
},
anchor_type: "organization",
anchor_value_resolved: 'ООО "Альтернатива Плюс"'
}
}),
hasAddressFollowupContextSignal: () => true,
resolveAssistantTurnMeaning: () => ({
schema_version: "assistant_turn_meaning_v1",
asked_domain_family: "business_summary",
asked_action_family: "broad_evaluation",
explicit_intent_candidate: null,
unsupported_but_understood_family: "broad_business_evaluation",
explicit_entity_candidates: [],
stale_replay_forbidden: true
})
});
const carryover = policy.resolveAddressFollowupCarryoverContext(
"Как ты оценишь деятельность компании?",
[],
null,
null,
null
);
expect(carryover).toBeNull();
});
it("reuses grounded MCP discovery payout context for a short year-switch follow-up", () => {
const policy = buildPolicy({
findLastAddressAssistantItem: () => null,
@@ -93,4 +93,21 @@ describe("assistantTurnMeaningPolicy", () => {
expect(meaning.asked_action_family).toBe("confirmed_tax_period");
expect(meaning.stale_replay_forbidden).toBe(false);
});
it("marks broad business evaluation as unsupported-but-understood instead of stale lifecycle replay", () => {
const policy = buildPolicy({
resolveAddressIntent: () => ({ intent: "counterparty_activity_lifecycle", confidence: "high" })
});
const meaning = policy.resolveAssistantTurnMeaning({
rawUserMessage: "Как ты оценишь деятельность компании?"
});
expect(meaning.explicit_intent_candidate).toBeNull();
expect(meaning.asked_domain_family).toBe("business_summary");
expect(meaning.asked_action_family).toBe("broad_evaluation");
expect(meaning.unsupported_but_understood_family).toBe("broad_business_evaluation");
expect(meaning.stale_replay_forbidden).toBe(true);
expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
});
});