NODEDC_1C/llm_normalizer/backend/tests/assistantTransitionPolicy.t...

150 lines
6.0 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 } from "vitest";
import { createAssistantTransitionPolicy } from "../src/services/assistantTransitionPolicy";
function toNonEmptyString(value: unknown): string | null {
if (value === null || value === undefined) {
return null;
}
const text = String(value).trim();
return text.length > 0 ? text : null;
}
function buildPolicy(overrides: Record<string, unknown> = {}) {
return createAssistantTransitionPolicy({
compactWhitespace: (value: string) => String(value ?? "").replace(/\s+/g, " ").trim(),
repairAddressMojibake: (value: string) => value,
countTokens: (value: string) => String(value ?? "").split(/\s+/).filter(Boolean).length,
findLastAddressAssistantItem: () => ({
text: "1. Рабочая станция",
debug: {
detected_intent: "inventory_purchase_documents_for_item",
extracted_filters: {
item: "Рабочая станция"
},
anchor_type: "item",
anchor_value_resolved: "Рабочая станция"
}
}),
findLastOrganizationClarificationAddressDebug: () => null,
mergeKnownOrganizations: (values: unknown[]) => values,
resolveOrganizationSelectionFromMessage: () => null,
toNonEmptyString,
buildAddressFollowupOffer: () => null,
isImplicitAddressContinuationByLlm: () => false,
isInventorySelectedObjectIntent: (intent: unknown) =>
[
"inventory_purchase_provenance_for_item",
"inventory_purchase_documents_for_item",
"inventory_sale_trace_for_item",
"inventory_profitability_for_item",
"inventory_purchase_to_sale_chain",
"inventory_aging_by_purchase_date"
].includes(String(intent ?? "")),
hasShortInventoryObjectFollowupSignal: () => false,
resolveDebtRoleSwapFollowupIntent: () => null,
hasAddressFollowupContextSignal: () => false,
extractDisplayedEntityIndexMention: () => null,
findRecentInventoryRootFrame: () => ({
intent: "inventory_on_hand_as_of_date",
filters: {
as_of_date: "2020-03-31",
organization: 'ООО "Альтернатива Плюс"'
},
anchorType: "organization",
anchorValue: 'ООО "Альтернатива Плюс"'
}),
hasInventoryRootTemporalFollowupSignal: (message: string) => /март 2020/i.test(message),
hasFollowupMarker: () => false,
hasReferentialPointer: () => false,
hasStandaloneAddressTopicSignal: () => false,
resolveAddressIntent: () => ({ intent: "unknown" }),
resolveAddressIntentFamily: (intent: unknown) => (intent ? String(intent) : null),
readAddressFilterString: (debug: Record<string, unknown>, key: string) =>
debug?.extracted_filters && typeof debug.extracted_filters === "object"
? toNonEmptyString((debug.extracted_filters as Record<string, unknown>)[key])
: null,
normalizeOrganizationScopeValue: (value: unknown) => toNonEmptyString(value),
isInventoryDrilldownFrameIntent: (intent: unknown) =>
[
"inventory_purchase_provenance_for_item",
"inventory_purchase_documents_for_item",
"inventory_sale_trace_for_item",
"inventory_profitability_for_item",
"inventory_purchase_to_sale_chain",
"inventory_aging_by_purchase_date"
].includes(String(intent ?? "")),
isInventoryRootFrameIntent: (intent: unknown) => String(intent ?? "") === "inventory_on_hand_as_of_date",
findRecentAddressFilterValue: () => null,
hasForeignAccountingPivotOverInventoryMessage: () => false,
buildRootScopedCarryoverFilters: (_previousFilters: Record<string, unknown>, inventoryRootFrame: Record<string, unknown>) => ({
...(inventoryRootFrame?.filters ?? {})
}),
inferDisplayedEntityTypeFromIntent: () => "item",
extractDisplayedAddressEntityCandidates: () => [],
resolveDisplayedAddressEntityMention: () => null,
...overrides
});
}
describe("assistantTransitionPolicy", () => {
it("promotes inventory temporal follow-up into root-scoped carryover", () => {
const policy = buildPolicy();
const carryover = policy.resolveAddressFollowupCarryoverContext(
"остатки на март 2020",
[],
null,
null,
{
session_context: {
active_focus_object: {
object_type: "item",
label: "Рабочая станция"
}
}
}
);
expect(carryover?.followupSelectionMode).toBe("carry_root_context");
expect(carryover?.followupContext?.root_context_only).toBe(true);
expect(carryover?.followupContext?.target_intent).toBe("inventory_on_hand_as_of_date");
expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date");
expect(carryover?.followupContext?.previous_filters).toEqual({
as_of_date: "2020-03-31",
organization: 'ООО "Альтернатива Плюс"'
});
});
it("builds continuation contract from extracted root carryover", () => {
const policy = buildPolicy();
const contract = policy.buildAddressDialogContinuationContractV2(
"остатки на эту дату",
"остатки на эту дату",
{
followupContext: {
root_intent: "inventory_on_hand_as_of_date",
previous_anchor_type: "item",
previous_anchor_value: "Рабочая станция"
},
previousSourceIntent: "inventory_purchase_documents_for_item",
previousAddressIntent: null,
followupSelectionMode: "carry_root_context",
hasImplicitContinuationSignal: true
},
{
predecomposeContract: {
intent: "unknown"
}
}
);
expect(contract.decision).toBe("continue_previous");
expect(contract.target_intent).toBe("inventory_on_hand_as_of_date");
expect(contract.decision_reasons).toContain("root_context_only_carryover");
expect(contract.decision_reasons).toContain("implicit_continuation_by_llm");
expect(contract.anchor_type).toBe("item");
expect(contract.anchor_value).toBe("Рабочая станция");
});
});