АРЧ АП11 - Вынести transition carryover policy из assistantService в отдельный модуль
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
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("Рабочая станция");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user