NODEDC_1C/llm_normalizer/backend/tests/assistantAddressLaneRespons...

575 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it, vi } from "vitest";
import { runAssistantAddressLaneResponseRuntime } from "../src/services/assistantAddressLaneResponseRuntimeAdapter";
describe("assistant address lane response runtime adapter", () => {
it("builds debug payload and finalizes address turn", () => {
const finalizeAddressTurn = vi.fn(() => ({
response: {
ok: true
}
}));
const runtime = runAssistantAddressLaneResponseRuntime({
sessionId: "asst-1",
userMessage: "raw",
effectiveAddressUserMessage: "canon",
addressLane: {
handled: true,
reply_text: "answer",
reply_type: "factual",
debug: {
extracted_filters: {
organization: "ООО Ромашка"
}
}
},
carryoverMeta: {
followupContext: {
previous_intent: "list_documents"
}
},
llmPreDecomposeMeta: {
attempted: true
},
knownOrganizations: ["ООО Ромашка", "ООО Лютик"],
activeOrganization: "ООО Ромашка",
sanitizeOutgoingAssistantText: (text) => String(text ?? "").trim(),
buildAddressDebugPayload: (addressDebug) => ({ ...(addressDebug as Record<string, unknown>) }),
buildAddressFollowupOffer: () => ({ suggestion: "continue_previous" }),
mergeKnownOrganizations: (items) => Array.from(new Set(items)),
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
appendItem: () => {},
getSession: () => ({ session_id: "asst-1", updated_at: "", items: [], investigation_state: null } as any),
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-1",
finalizeAddressTurn
});
expect(finalizeAddressTurn).toHaveBeenCalledWith(
expect.objectContaining({
assistantReply: "answer",
replyType: "factual",
llmPreDecomposeMeta: {
attempted: true
}
})
);
expect(runtime.response).toEqual({ ok: true });
expect(runtime.debug).toEqual(
expect.objectContaining({
assistant_known_organizations: ["ООО Ромашка", "ООО Лютик"],
assistant_active_organization: "ООО Ромашка",
address_followup_offer: { suggestion: "continue_previous" },
assistant_runtime_contract_v1: expect.objectContaining({
schema_version: "assistant_runtime_contracts_v1"
}),
assistant_truth_answer_policy_v1: expect.objectContaining({
schema_version: "assistant_truth_answer_policy_runtime_v1",
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter"
}),
coverage_gate_contract: expect.objectContaining({
schema_version: "assistant_truth_answer_policy_runtime_v1"
}),
answer_shape_contract: expect.objectContaining({
reply_type: "factual"
}),
assistant_state_transition_v1: expect.objectContaining({
schema_version: "assistant_state_transition_runtime_v1",
state_owner: "assistantStateTransitionRuntimeAdapter"
}),
state_transition_contract: expect.objectContaining({
schema_version: "assistant_state_transition_runtime_v1"
}),
assistant_capability_binding_v1: expect.objectContaining({
schema_version: "assistant_capability_runtime_binding_v1",
binding_owner: "assistantCapabilityRuntimeBindingAdapter"
}),
capability_binding_contract: expect.objectContaining({
schema_version: "assistant_capability_runtime_binding_v1"
}),
capability_binding_response_guard: expect.objectContaining({
schema_version: "assistant_capability_binding_response_guard_v1",
applied: false
}),
assistant_mcp_discovery_entry_point_v1: null,
mcp_discovery_attempted: false,
mcp_discovery_hot_runtime_wired: false
})
);
});
it("attaches MCP discovery summary from predecompose runtime meta without changing the reply", () => {
const runtime = runAssistantAddressLaneResponseRuntime({
sessionId: "asst-mcp",
userMessage: "raw",
effectiveAddressUserMessage: "raw",
addressLane: {
handled: true,
reply_text: "answer",
reply_type: "partial_coverage",
debug: {}
},
llmPreDecomposeMeta: {
mcpDiscoveryRuntimeEntryPoint: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
entry_status: "bridge_executed",
hot_runtime_wired: false,
discovery_attempted: true,
turn_input: { adapter_status: "ready" },
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
answer_draft: { answer_mode: "confirmed_with_bounded_inference" }
},
reason_codes: ["runtime_entry_point_bridge_executed"]
}
},
knownOrganizations: [],
activeOrganization: null,
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
buildAddressDebugPayload: () => ({}),
buildAddressFollowupOffer: () => null,
mergeKnownOrganizations: (items) => items,
toNonEmptyString: () => null,
appendItem: () => {},
getSession: () => ({ session_id: "asst-mcp", updated_at: "", items: [], investigation_state: null } as any),
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-mcp",
finalizeAddressTurn: () => ({
response: {
ok: true
}
})
});
expect(runtime.response).toEqual({ ok: true });
expect(runtime.debug).toEqual(
expect.objectContaining({
mcp_discovery_entry_status: "bridge_executed",
mcp_discovery_attempted: true,
mcp_discovery_bridge_status: "answer_draft_ready",
mcp_discovery_answer_mode: "confirmed_with_bounded_inference",
mcp_discovery_business_fact_answer_allowed: true,
mcp_discovery_hot_runtime_wired: false
})
);
});
it("can replace a stale address exact answer with a guarded MCP discovery candidate", () => {
const finalizeAddressTurn = vi.fn((input) => ({
response: {
ok: true,
assistant_reply: input.assistantReply,
reply_type: input.replyType,
debug: input.debug
}
}));
const runtime = runAssistantAddressLaneResponseRuntime({
sessionId: "asst-mcp-address",
userMessage: "сколько мы заплатили СВК за 2020?",
effectiveAddressUserMessage: "сколько мы заплатили СВК за 2020?",
addressLane: {
handled: true,
reply_text: "stale documents answer",
reply_type: "factual",
debug: {}
},
llmPreDecomposeMeta: {
mcpDiscoveryRuntimeEntryPoint: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
entry_status: "bridge_executed",
hot_runtime_wired: false,
discovery_attempted: true,
turn_input: { adapter_status: "ready", should_run_discovery: true },
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Discovery payout answer",
confirmed_lines: ["1C supplier-payout rows were found for counterparty SVK"],
inference_lines: ["Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows"],
unknown_lines: ["Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot"],
limitation_lines: [],
next_step_line: null
}
},
reason_codes: ["runtime_entry_point_bridge_executed"]
}
},
knownOrganizations: [],
activeOrganization: null,
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
buildAddressDebugPayload: () => ({}),
buildAddressFollowupOffer: () => null,
mergeKnownOrganizations: (items) => items,
toNonEmptyString: () => null,
appendItem: () => {},
getSession: () => ({ session_id: "asst-mcp-address", updated_at: "", items: [], investigation_state: null } as any),
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-mcp-address",
finalizeAddressTurn
});
expect(finalizeAddressTurn).toHaveBeenCalledWith(
expect.objectContaining({
assistantReply: expect.stringContaining("Discovery payout answer"),
replyType: "partial_coverage",
debug: expect.objectContaining({
mcp_discovery_response_applied: true
})
})
);
expect(String((runtime.response as any).assistant_reply)).toContain("исходящих платежей/списаний");
});
it("aligns final route metadata when a business-overview discovery candidate replaces a stale exact reply", () => {
const finalizeAddressTurn = vi.fn((input) => ({
response: {
ok: true,
assistant_reply: input.assistantReply,
reply_type: input.replyType,
debug: input.debug
}
}));
const runtime = runAssistantAddressLaneResponseRuntime({
sessionId: "asst-business-overview-address",
userMessage: "Собери короткий бизнес-итог по компании и отдельно по СВК.",
effectiveAddressUserMessage: "Собери короткий бизнес-итог по компании и отдельно по СВК.",
addressLane: {
handled: true,
reply_text: "Контрагент: Группа СВК. Найдено документов: 19.",
reply_type: "factual",
debug: {
detected_intent: "list_documents_by_counterparty",
selected_recipe: "address_documents_by_counterparty_v1",
response_type: "FACTUAL_LIST"
}
},
llmPreDecomposeMeta: {
mcpDiscoveryRuntimeEntryPoint: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
entry_status: "bridge_executed",
hot_runtime_wired: false,
discovery_attempted: true,
turn_input: {
adapter_status: "ready",
should_run_discovery: true,
turn_meaning_ref: {
asked_domain_family: "business_overview",
asked_action_family: "broad_evaluation",
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020",
raw_message: "Собери короткий бизнес-итог по компании и отдельно по СВК."
},
data_need_graph: {
business_fact_family: "business_overview",
reason_codes: ["data_need_graph_family_business_overview"]
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
planner: {
selected_chain_id: "business_overview"
},
pilot: {
pilot_scope: "business_overview_route_template_v1",
derived_business_overview: {
period_scope: "2020",
incoming_customer_revenue: {
total_amount_human_ru: "47 628 853,03 руб."
},
outgoing_supplier_payout: {
total_amount_human_ru: "43 763 351,53 руб."
},
net_amount_human_ru: "3 865 501,50 руб.",
net_direction: "net_incoming"
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Business overview answer",
confirmed_lines: ["Confirmed company business overview facts"],
inference_lines: [],
unknown_lines: ["Pure profit is not proven"],
limitation_lines: [],
next_step_line: null
}
},
reason_codes: ["runtime_entry_point_bridge_executed"]
}
},
knownOrganizations: [],
activeOrganization: null,
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
buildAddressDebugPayload: (debug) => ({ ...(debug as Record<string, unknown>) }),
buildAddressFollowupOffer: () => null,
mergeKnownOrganizations: (items) => items,
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
appendItem: () => {},
getSession: () => ({ session_id: "asst-business-overview-address", updated_at: "", items: [], investigation_state: null } as any),
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-business-overview-address",
finalizeAddressTurn
});
expect((runtime.response as any).debug).toEqual(
expect.objectContaining({
mcp_discovery_response_applied: true,
detected_intent: "business_overview",
selected_recipe: "business_overview",
mcp_discovery_effective_response_route: "business_overview"
})
);
expect(String((runtime.response as any).assistant_reply)).toContain("47 628 853,03");
});
it("keeps comparison-scope proof augmentation business-facing for organization clarification", () => {
const finalizeAddressTurn = vi.fn((input) => ({
response: {
ok: true,
assistant_reply: input.assistantReply,
reply_type: input.replyType,
debug: input.debug
}
}));
const runtime = runAssistantAddressLaneResponseRuntime({
sessionId: "asst-comparison-proof",
userMessage: "ООО Альтернатива Плюс",
effectiveAddressUserMessage: "ООО Альтернатива Плюс",
addressLane: {
handled: true,
reply_text:
"Коротко: по компании Альтернатива Плюс подтвержден company-level денежный срез: входящие 285 819 547,57 руб.",
reply_type: "partial_coverage",
debug: {}
},
llmPreDecomposeMeta: {
mcpDiscoveryRuntimeEntryPoint: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
entry_status: "bridge_executed",
hot_runtime_wired: true,
discovery_attempted: true,
turn_input: {
turn_meaning_ref: {
asked_domain_family: "business_overview",
business_overview_separate_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "Альтернатива Плюс"
}
},
bridge: {
bridge_status: "not_applicable",
user_facing_response_allowed: false,
business_fact_answer_allowed: false,
requires_user_clarification: false
},
reason_codes: []
}
},
knownOrganizations: [],
activeOrganization: null,
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
buildAddressDebugPayload: () => ({}),
buildAddressFollowupOffer: () => null,
mergeKnownOrganizations: (items) => items,
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
appendItem: () => {},
getSession: () =>
({
session_id: "asst-comparison-proof",
updated_at: "",
items: [],
investigation_state: null,
address_navigation_state: {
session_context: {
comparison_scope: {
organization: { label: "Альтернатива Плюс" },
counterparty: { label: "Группа СВК" },
proof_bundles: {
counterparty_value_flow_bundle: {
counterparty: "Группа СВК",
incoming_customer_revenue: { total_amount_human_ru: "20 653 490 руб." },
outgoing_supplier_payout: { total_amount_human_ru: "2 129 651 руб." },
net_amount_human_ru: "18 523 839 руб.",
net_direction: "net_incoming"
},
counterparty_document_bundle: {
document_count: 19
}
}
}
}
}
}) as any,
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-comparison-proof",
finalizeAddressTurn
});
const reply = String((runtime.response as any).assistant_reply);
expect(reply).toContain("по компании ООО Альтернатива Плюс");
expect(reply).toContain("получили 20 653 490 руб.");
expect(reply).toContain("документы: 19");
expect(reply).not.toContain("company-level");
expect(reply).not.toContain("reusable bundle");
expect((runtime.response as any).debug.comparison_scope_response_augmentation_v1).toEqual(
expect.objectContaining({
applied: true,
organization: "ООО Альтернатива Плюс",
counterparty: "Группа СВК",
document_count: 19
})
);
});
it("keeps debug bounded to shadow contracts when optional enrichment is absent", () => {
const runtime = runAssistantAddressLaneResponseRuntime({
sessionId: "asst-2",
userMessage: "raw",
effectiveAddressUserMessage: "raw",
addressLane: {
handled: true,
reply_text: "answer",
reply_type: "partial_coverage",
debug: {}
},
knownOrganizations: [],
activeOrganization: null,
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
buildAddressDebugPayload: () => ({}),
buildAddressFollowupOffer: () => null,
mergeKnownOrganizations: (items) => items,
toNonEmptyString: () => null,
appendItem: () => {},
getSession: () => ({ session_id: "asst-2", updated_at: "", items: [], investigation_state: null } as any),
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-2",
finalizeAddressTurn: () => ({
response: {
ok: true
}
})
});
expect(runtime.debug).toEqual(
expect.objectContaining({
assistant_runtime_contract_v1: expect.objectContaining({
transition_contract_id: null,
capability_contract_id: null,
truth_gate_contract_status: "unknown"
}),
assistant_truth_answer_policy_v1: expect.objectContaining({
schema_version: "assistant_truth_answer_policy_runtime_v1"
}),
coverage_gate_contract: expect.objectContaining({
coverage_status: "blocked",
truth_mode: "unsupported"
}),
answer_shape_contract: expect.objectContaining({
answer_shape: "blocked_no_answer",
reply_type: "partial_coverage"
}),
assistant_state_transition_v1: expect.objectContaining({
application_status: "unresolved",
effective_carryover_depth: "none"
}),
transition_contract_id: null,
capability_contract_id: null,
truth_gate_contract_status: "unknown",
carryover_eligibility: "none",
state_transition_id: null,
state_transition_status: "unresolved",
effective_carryover_depth: "none",
capability_binding_status: "not_applicable",
capability_binding_action: "observe_only",
capability_binding_violations: [],
capability_binding_response_guard: expect.objectContaining({
applied: false,
action: "observe_only"
})
})
);
expect(runtime.response).toEqual({ ok: true });
});
it("guards blocked capability binding responses before finalizing the turn", () => {
const finalizeAddressTurn = vi.fn(() => ({
response: {
ok: true
}
}));
runAssistantAddressLaneResponseRuntime({
sessionId: "asst-3",
userMessage: "кто поставщик?",
effectiveAddressUserMessage: "кто поставщик?",
addressLane: {
handled: true,
reply_text: "unsafe factual answer",
reply_type: "factual",
debug: {
capability_id: "inventory_inventory_purchase_provenance_for_item",
limited_reason_category: "missing_anchor",
missing_required_filters: ["item"]
}
},
knownOrganizations: [],
activeOrganization: null,
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
buildAddressDebugPayload: (addressDebug) => ({ ...(addressDebug as Record<string, unknown>) }),
buildAddressFollowupOffer: () => null,
mergeKnownOrganizations: (items) => items,
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
appendItem: () => {},
getSession: () => ({ session_id: "asst-3", updated_at: "", items: [], investigation_state: null } as any),
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-3",
finalizeAddressTurn
});
expect(finalizeAddressTurn).toHaveBeenCalledWith(
expect.objectContaining({
assistantReply: expect.stringContaining("Нужно уточнение"),
replyType: "partial_coverage",
debug: expect.objectContaining({
capability_binding_status: "blocked",
capability_binding_action: "clarify",
capability_binding_response_guard: expect.objectContaining({
applied: true,
action: "clarify",
guarded_reply_type: "partial_coverage"
})
})
})
);
});
});