628 lines
19 KiB
TypeScript
628 lines
19 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
applyHistoricalPartyCarryoverFilters,
|
||
applyOrganizationCarryoverFilters,
|
||
applyReferencedEntityCarryover,
|
||
applySelectedItemCarryover,
|
||
applyTemporalCarryoverFilters,
|
||
buildRootScopedCarryoverFilters,
|
||
hydrateInventoryRootFrameState,
|
||
readAddressDebugCounterparty,
|
||
readAddressDebugIntent,
|
||
readAddressDebugTemporalScope,
|
||
resolveNavigationSessionContextState,
|
||
resolveAddressDebugCarryoverFilters,
|
||
resolveAddressDebugContextFacts,
|
||
resolveAddressDebugAnchorContext,
|
||
resolveOrganizationClarificationContinuation,
|
||
resolveDisplayedEntityFollowupRetarget,
|
||
resolveFollowupTargetIntent,
|
||
resolveInventoryFollowupPivotFlags,
|
||
resolveAssistantOrganizationAuthority
|
||
} from "../src/services/assistantContinuityPolicy";
|
||
|
||
describe("assistantContinuityPolicy organization authority", () => {
|
||
it("prefers explicit assistant organization authority over older grounded continuity and merges known organizations once", () => {
|
||
const authority = resolveAssistantOrganizationAuthority({
|
||
sessionItems: [
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: { status: "grounded" },
|
||
extracted_filters: {
|
||
organization: "Org Grounded",
|
||
as_of_date: "2020-03-31"
|
||
},
|
||
detected_intent: "receivables_confirmed_as_of_date"
|
||
}
|
||
},
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "living_chat",
|
||
living_chat_selected_organization: "Org Selected",
|
||
assistant_active_organization: "Org Selected",
|
||
assistant_known_organizations: ["Org Selected", "Org Known"],
|
||
living_chat_data_scope_probe_organizations: ["Org Probe"]
|
||
}
|
||
}
|
||
],
|
||
sessionKnownOrganizations: ["Org Session"],
|
||
lastOrganizationClarificationDebug: {
|
||
organization_candidates: ["Org Candidate", "Org Selected"]
|
||
}
|
||
});
|
||
|
||
expect(authority.continuitySnapshot.activeOrganization).toBe("Org Grounded");
|
||
expect(authority.continuityActiveOrganization).toBe("Org Selected");
|
||
expect(authority.selectedOrganization).toBe("Org Selected");
|
||
expect(authority.activeOrganization).toBe("Org Selected");
|
||
expect(authority.knownOrganizations).toEqual([
|
||
"Org Session",
|
||
"Org Selected",
|
||
"Org Known",
|
||
"Org Probe",
|
||
"Org Grounded"
|
||
]);
|
||
expect(authority.organizationClarificationCandidates).toEqual([
|
||
"Org Candidate",
|
||
"Org Selected",
|
||
"Org Session",
|
||
"Org Known",
|
||
"Org Probe",
|
||
"Org Grounded"
|
||
]);
|
||
expect(authority.organizationClarificationSelectionFromScope).toBe("Org Selected");
|
||
});
|
||
|
||
it("exposes known organizations as switch candidates even without a prior clarification turn", () => {
|
||
const authority = resolveAssistantOrganizationAuthority({
|
||
sessionItems: [
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "living_chat",
|
||
assistant_active_organization: "РАЙМ",
|
||
assistant_known_organizations: ['ООО "Альтернатива Плюс"', "РАЙМ"]
|
||
}
|
||
}
|
||
],
|
||
sessionKnownOrganizations: ['ООО "Альтернатива Плюс"']
|
||
});
|
||
|
||
expect(authority.activeOrganization).toBe("РАЙМ");
|
||
expect(authority.organizationClarificationCandidates).toEqual([
|
||
'ООО "Альтернатива Плюс"',
|
||
"РАЙМ"
|
||
]);
|
||
});
|
||
|
||
it("reads item, organization and scoped date from root-frame fallback when direct filters are missing", () => {
|
||
const facts = resolveAddressDebugContextFacts({
|
||
anchor_type: "item",
|
||
anchor_value_resolved: "Рабочая станция",
|
||
address_root_frame_context: {
|
||
organization: 'ООО "Альтернатива Плюс"',
|
||
as_of_date: "2020-03-31"
|
||
}
|
||
});
|
||
|
||
expect(facts).toEqual({
|
||
item: "Рабочая станция",
|
||
counterparty: null,
|
||
organization: 'ООО "Альтернатива Плюс"',
|
||
scopedDate: "31.03.2020"
|
||
});
|
||
});
|
||
|
||
it("reads counterparty, organization and period from grounded MCP discovery fallback", () => {
|
||
const facts = resolveAddressDebugContextFacts({
|
||
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: {
|
||
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"
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
expect(facts).toEqual({
|
||
item: null,
|
||
counterparty: "Группа СВК",
|
||
organization: "ООО Альтернатива Плюс",
|
||
scopedDate: "2020"
|
||
});
|
||
});
|
||
|
||
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("prefers the resolved entity from grounded entity-resolution discovery for counterparty carryover", () => {
|
||
const 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: {
|
||
asked_domain_family: "entity_resolution",
|
||
asked_action_family: "search_business_entity",
|
||
explicit_entity_candidates: ["СВК"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
business_fact_answer_allowed: true,
|
||
pilot: {
|
||
pilot_scope: "entity_resolution_search_v1",
|
||
derived_entity_resolution: {
|
||
requested_entity: "СВК",
|
||
resolution_status: "resolved",
|
||
resolved_entity: "Группа СВК",
|
||
ambiguity_candidates: []
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference"
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
expect(readAddressDebugCounterparty(debug)).toBe("Группа СВК");
|
||
expect(resolveAddressDebugCarryoverFilters(debug)).toEqual({
|
||
counterparty: "Группа СВК"
|
||
});
|
||
expect(resolveAddressDebugAnchorContext(debug)).toEqual({
|
||
anchorType: "counterparty",
|
||
anchorValue: "Группа СВК"
|
||
});
|
||
});
|
||
|
||
it("resolves navigation session context through one shared helper", () => {
|
||
const state = resolveNavigationSessionContextState({
|
||
session_context: {
|
||
organization_scope: "Org Alt",
|
||
active_result_set_id: "rs-42",
|
||
date_scope: {
|
||
as_of_date: "2020-03-31",
|
||
period_from: "2020-03-01",
|
||
period_to: "2020-03-31"
|
||
},
|
||
active_focus_object: {
|
||
object_type: "item",
|
||
label: "Workstation",
|
||
provenance_result_set_id: "rs-focus"
|
||
}
|
||
}
|
||
});
|
||
|
||
expect(state).toEqual({
|
||
organization: "Org Alt",
|
||
activeResultSetId: "rs-42",
|
||
dateScope: {
|
||
as_of_date: "2020-03-31",
|
||
period_from: "2020-03-01",
|
||
period_to: "2020-03-31"
|
||
},
|
||
focusObject: {
|
||
objectType: "item",
|
||
label: "Workstation",
|
||
provenanceResultSetId: "rs-focus"
|
||
}
|
||
});
|
||
});
|
||
|
||
it("resolves organization clarification continuation through one shared helper", () => {
|
||
const continuation = resolveOrganizationClarificationContinuation({
|
||
rawMessages: ["Альтернатива Плюс", null, " "],
|
||
organizationClarificationCandidates: ["ООО Альтернатива Плюс", "РАЙМ"],
|
||
organizationClarificationSelectionFromScope: "РАЙМ",
|
||
lastOrganizationClarificationDebug: {
|
||
organization_candidates: ["ООО Альтернатива Плюс", "РАЙМ"]
|
||
},
|
||
resolveOrganizationSelectionFromMessage: (message, candidates) =>
|
||
/альтернатива/i.test(String(message))
|
||
? String(candidates[0] ?? "")
|
||
: null
|
||
});
|
||
|
||
expect(continuation).toEqual({
|
||
explicitSelection: "ООО Альтернатива Плюс",
|
||
selection: "ООО Альтернатива Плюс",
|
||
hasContinuation: true
|
||
});
|
||
});
|
||
|
||
it("resolves carryover temporal scope and inferred anchor from debug filters when explicit anchor fields are absent", () => {
|
||
const debug = {
|
||
extracted_filters: {
|
||
item: "Рабочая станция",
|
||
period_from: "2020-03-01"
|
||
},
|
||
address_root_frame_context: {
|
||
as_of_date: "2020-03-31",
|
||
period_to: "2020-03-31"
|
||
}
|
||
};
|
||
|
||
expect(readAddressDebugTemporalScope(debug)).toEqual({
|
||
asOfDate: "2020-03-31",
|
||
periodFrom: "2020-03-01",
|
||
periodTo: "2020-03-31"
|
||
});
|
||
expect(resolveAddressDebugAnchorContext(debug)).toEqual({
|
||
anchorType: "item",
|
||
anchorValue: "Рабочая станция"
|
||
});
|
||
});
|
||
it("prefers inventory root-frame filters over stale drilldown date scope in carryover filters", () => {
|
||
const filters = resolveAddressDebugCarryoverFilters({
|
||
detected_intent: "inventory_purchase_documents_for_item",
|
||
extracted_filters: {
|
||
item: "Workstation",
|
||
organization: "Org Alt",
|
||
as_of_date: "2021-04-15"
|
||
},
|
||
address_root_frame_context: {
|
||
root_intent: "inventory_on_hand_as_of_date",
|
||
root_filters: {
|
||
organization: "Org Alt",
|
||
warehouse: "Main Warehouse",
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31"
|
||
},
|
||
root_anchor_type: "organization",
|
||
root_anchor_value: "Org Alt",
|
||
current_frame_kind: "inventory_drilldown"
|
||
}
|
||
});
|
||
|
||
expect(filters).toEqual({
|
||
item: "Workstation",
|
||
organization: "Org Alt",
|
||
warehouse: "Main Warehouse",
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31"
|
||
});
|
||
});
|
||
|
||
it("hydrates inventory root-frame state from navigation scope and preserves derived current frame kind", () => {
|
||
const state = hydrateInventoryRootFrameState(
|
||
{
|
||
intent: "inventory_on_hand_as_of_date",
|
||
filters: {
|
||
warehouse: "Main Warehouse"
|
||
},
|
||
anchorType: "organization",
|
||
anchorValue: "Org Alt",
|
||
currentFrameKind: null
|
||
},
|
||
"inventory_purchase_documents_for_item",
|
||
"Org Alt",
|
||
{
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31"
|
||
},
|
||
undefined,
|
||
(intent) => String(intent ?? "") === "inventory_purchase_documents_for_item",
|
||
(intent) => String(intent ?? "") === "inventory_on_hand_as_of_date"
|
||
);
|
||
|
||
expect(state.currentFrameKind).toBe("inventory_drilldown");
|
||
expect(state.inventoryRootFrame).toMatchObject({
|
||
currentFrameKind: "inventory_drilldown",
|
||
filters: {
|
||
organization: "Org Alt",
|
||
warehouse: "Main Warehouse",
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31"
|
||
}
|
||
});
|
||
});
|
||
|
||
it("builds root-scoped carryover filters with previous date precedence over inventory root frame", () => {
|
||
const filters = buildRootScopedCarryoverFilters(
|
||
{
|
||
organization: "Org Alt",
|
||
as_of_date: "2020-03-31",
|
||
period_from: "2020-03-01",
|
||
period_to: "2020-03-31"
|
||
},
|
||
{
|
||
filters: {
|
||
organization: "Org Alt",
|
||
warehouse: "Main Warehouse",
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31"
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(filters).toEqual({
|
||
organization: "Org Alt",
|
||
warehouse: "Main Warehouse",
|
||
as_of_date: "2020-03-31",
|
||
period_from: "2020-03-01",
|
||
period_to: "2020-03-31"
|
||
});
|
||
});
|
||
|
||
it("applies temporal carryover from navigation scope first and falls back to continuity scope", () => {
|
||
const filters = applyTemporalCarryoverFilters(
|
||
{
|
||
organization: "Org Alt"
|
||
},
|
||
{
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01"
|
||
},
|
||
{
|
||
asOfDate: "2020-12-31",
|
||
periodFrom: "2020-12-01",
|
||
periodTo: "2020-12-31"
|
||
},
|
||
"inventory_purchase_documents_for_item"
|
||
);
|
||
|
||
expect(filters).toEqual({
|
||
organization: "Org Alt",
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2020-12-31"
|
||
});
|
||
});
|
||
|
||
it("does not inject temporal carryover for unrelated intent families", () => {
|
||
const filters = applyTemporalCarryoverFilters(
|
||
{
|
||
organization: "Org Alt"
|
||
},
|
||
{
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31"
|
||
},
|
||
{
|
||
asOfDate: "2020-12-31",
|
||
periodFrom: "2020-12-01",
|
||
periodTo: "2020-12-31"
|
||
},
|
||
"list_documents_by_counterparty"
|
||
);
|
||
|
||
expect(filters).toEqual({
|
||
organization: "Org Alt"
|
||
});
|
||
});
|
||
|
||
it("resolves inventory root pivots through one shared helper", () => {
|
||
const flags = resolveInventoryFollowupPivotFlags(
|
||
{ intent: "inventory_on_hand_as_of_date" },
|
||
"inventory_purchase_documents_for_item",
|
||
"inventory_drilldown",
|
||
false,
|
||
false,
|
||
true,
|
||
false,
|
||
false,
|
||
false,
|
||
false,
|
||
false,
|
||
(intent) => String(intent ?? "") === "inventory_purchase_documents_for_item",
|
||
(intent) => String(intent ?? "") === "inventory_on_hand_as_of_date"
|
||
);
|
||
|
||
expect(flags).toEqual({
|
||
rootScopedPivot: true,
|
||
explicitInventorySameDatePivot: false
|
||
});
|
||
});
|
||
|
||
it("resolves follow-up target intent precedence through one shared helper", () => {
|
||
const targetIntent = resolveFollowupTargetIntent(
|
||
false,
|
||
null,
|
||
null,
|
||
"customer_revenue_and_payments",
|
||
"carry_root_context",
|
||
"inventory_on_hand_as_of_date",
|
||
"list_contracts_by_counterparty",
|
||
"customer_revenue_and_payments",
|
||
false
|
||
);
|
||
|
||
expect(targetIntent).toBe("inventory_on_hand_as_of_date");
|
||
});
|
||
|
||
it("applies organization carryover precedence from historical to shared authority to navigation and clarification", () => {
|
||
const filters = applyOrganizationCarryoverFilters(
|
||
{},
|
||
null,
|
||
"Org Authority",
|
||
"Org Continuity",
|
||
"Org Navigation",
|
||
"Org Clarification"
|
||
);
|
||
|
||
expect(filters).toEqual({
|
||
organization: "Org Authority"
|
||
});
|
||
});
|
||
|
||
it("keeps existing organization and does not overwrite it during organization carryover backfill", () => {
|
||
const filters = applyOrganizationCarryoverFilters(
|
||
{
|
||
organization: "Org Existing"
|
||
},
|
||
"Org Historical",
|
||
"Org Authority",
|
||
"Org Continuity",
|
||
"Org Navigation",
|
||
"Org Clarification"
|
||
);
|
||
|
||
expect(filters).toEqual({
|
||
organization: "Org Existing"
|
||
});
|
||
});
|
||
|
||
it("backfills historical contract and counterparty only for party-driven follow-up families", () => {
|
||
const filters = applyHistoricalPartyCarryoverFilters(
|
||
{},
|
||
true,
|
||
"Contract Legacy",
|
||
"Counterparty Legacy"
|
||
);
|
||
|
||
expect(filters).toEqual({
|
||
contract: "Contract Legacy",
|
||
counterparty: "Counterparty Legacy"
|
||
});
|
||
});
|
||
|
||
it("applies referenced entity carryover into filters, anchor, and selection mode", () => {
|
||
const carryover = applyReferencedEntityCarryover(
|
||
{
|
||
organization: "Org Alt"
|
||
},
|
||
null,
|
||
null,
|
||
"carry_previous_intent",
|
||
{
|
||
entityType: "counterparty",
|
||
value: "SVK Group"
|
||
},
|
||
false
|
||
);
|
||
|
||
expect(carryover).toEqual({
|
||
previousFilters: {
|
||
organization: "Org Alt",
|
||
counterparty: "SVK Group"
|
||
},
|
||
previousAnchorType: "counterparty",
|
||
previousAnchorValue: "SVK Group",
|
||
followupSelectionMode: "carry_referenced_entity",
|
||
resolvedCounterpartyFromDisplay: true
|
||
});
|
||
});
|
||
|
||
it("applies selected-item carryover from navigation focus before continuity and explicit labels", () => {
|
||
const carryover = applySelectedItemCarryover(
|
||
{
|
||
organization: "Org Alt"
|
||
},
|
||
null,
|
||
null,
|
||
false,
|
||
true,
|
||
"Workstation Navigation",
|
||
"Workstation Continuity",
|
||
"Workstation User",
|
||
"Workstation Alternate"
|
||
);
|
||
|
||
expect(carryover).toEqual({
|
||
previousFilters: {
|
||
organization: "Org Alt",
|
||
item: "Workstation Navigation"
|
||
},
|
||
previousAnchorType: "item",
|
||
previousAnchorValue: "Workstation Navigation"
|
||
});
|
||
});
|
||
|
||
it("resolves displayed-entity follow-up retarget and carryover through one shared helper", () => {
|
||
const carryover = resolveDisplayedEntityFollowupRetarget(
|
||
"покажи договоры по СВК",
|
||
{
|
||
entityType: "counterparty",
|
||
value: "SVK Group"
|
||
},
|
||
{
|
||
organization: "Org Alt"
|
||
},
|
||
null,
|
||
null,
|
||
"carry_previous_intent",
|
||
false,
|
||
(value) => String(value ?? "").replace(/\s+/g, " ").trim(),
|
||
(value) => value
|
||
);
|
||
|
||
expect(carryover).toEqual({
|
||
displayedEntityTargetIntent: "list_contracts_by_counterparty",
|
||
previousFilters: {
|
||
organization: "Org Alt",
|
||
counterparty: "SVK Group"
|
||
},
|
||
previousAnchorType: "counterparty",
|
||
previousAnchorValue: "SVK Group",
|
||
followupSelectionMode: "carry_referenced_entity",
|
||
resolvedCounterpartyFromDisplay: true
|
||
});
|
||
});
|
||
});
|