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

524 lines
20 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";
import { buildRootScopedCarryoverFiltersForTests } from "../src/services/assistantService";
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>
) => ({
organization:
toNonEmptyString(inventoryRootFrame?.filters?.organization) ?? toNonEmptyString(previousFilters?.organization),
warehouse:
toNonEmptyString(inventoryRootFrame?.filters?.warehouse) ?? toNonEmptyString(previousFilters?.warehouse),
as_of_date:
toNonEmptyString(previousFilters?.as_of_date) ?? toNonEmptyString(inventoryRootFrame?.filters?.as_of_date),
period_from:
toNonEmptyString(previousFilters?.period_from) ?? toNonEmptyString(inventoryRootFrame?.filters?.period_from),
period_to:
toNonEmptyString(previousFilters?.period_to) ?? toNonEmptyString(inventoryRootFrame?.filters?.period_to)
}),
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).toMatchObject({
as_of_date: "2020-03-31",
organization: 'ООО "Альтернатива Плюс"'
});
});
it("promotes same-date inventory restatement after drilldown into root-scoped carryover", () => {
const policy = buildPolicy({
hasInventoryRootTemporalFollowupSignal: () => false
});
const carryover = policy.resolveAddressFollowupCarryoverContext(
"покажи еще раз остатки на эту же дату",
[],
null,
null,
null
);
expect(carryover?.followupSelectionMode).toBe("carry_root_context");
expect(carryover?.followupContext?.root_context_only).toBe(true);
expect(carryover?.followupContext?.previous_intent).toBeUndefined();
expect(carryover?.followupContext?.previous_filters).toMatchObject({
as_of_date: "2020-03-31",
organization: 'ООО "Альтернатива Плюс"'
});
expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date");
});
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("Рабочая станция");
});
it("prefers carryover target intent over llm contract drift in continuation contract", () => {
const policy = buildPolicy();
const contract = policy.buildAddressDialogContinuationContractV2(
"покажи договор по гамме",
"покажи договор по гамме",
{
followupContext: {
previous_intent: "customer_revenue_and_payments",
target_intent: "list_contracts_by_counterparty",
previous_anchor_type: "counterparty",
previous_anchor_value: "Гамма-мебель, ООО"
},
previousSourceIntent: "customer_revenue_and_payments",
previousAddressIntent: "customer_revenue_and_payments",
followupSelectionMode: "carry_referenced_entity",
hasImplicitContinuationSignal: false
},
{
predecomposeContract: {
intent: "unknown"
}
}
);
expect(contract.target_intent).toBe("list_contracts_by_counterparty");
expect(contract.decision).toBe("continue_previous");
});
it("retargets same-date inventory follow-up away from receivables intent", () => {
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: "Подтвержденная дебиторская задолженность на 31.03.2020 собрана.",
debug: {
detected_intent: "receivables_confirmed_as_of_date",
extracted_filters: {
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2020-03-31",
period_from: "2020-03-01",
period_to: "2020-03-31"
},
anchor_type: "organization",
anchor_value_resolved: 'ООО "Альтернатива Плюс"'
}
}),
hasAddressFollowupContextSignal: () => true,
hasReferentialPointer: () => true,
findRecentInventoryRootFrame: () => null,
resolveAddressIntent: () => ({ intent: "unknown" })
});
const carryover = policy.resolveAddressFollowupCarryoverContext(
"остатки по складу на эту же дату",
[],
null,
null,
null
);
expect(carryover?.followupSelectionMode).toBe("carry_previous_intent");
expect(carryover?.followupContext?.previous_intent).toBe("receivables_confirmed_as_of_date");
expect(carryover?.followupContext?.target_intent).toBe("inventory_on_hand_as_of_date");
expect(carryover?.followupContext?.previous_filters).toMatchObject({
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2020-03-31",
period_from: "2020-03-01",
period_to: "2020-03-31"
});
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
});
it("bridges selected-item purchase provenance into a VAT period follow-up", () => {
const item = "Рабочая станция универсального специалиста";
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: [
`По позиции ${item} однозначный поставщик не подтвержден.`,
"Подтверждение:",
"- Первая найденная дата закупки: 05.02.2015.",
"- Последняя найденная дата закупки: 22.07.2015."
].join("\n"),
debug: {
detected_intent: "inventory_purchase_provenance_for_item",
extracted_filters: {
item,
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2016-03-31"
},
anchor_type: "item",
anchor_value_resolved: item
}
}),
hasAddressFollowupContextSignal: () => false,
findRecentInventoryRootFrame: () => null,
resolveAddressIntent: () => ({ intent: "unknown" })
});
const carryover = policy.resolveAddressFollowupCarryoverContext(
"ндс можешь прикинуть на дату покупки рабочей станции?",
[],
null,
null,
{
session_context: {
active_focus_object: {
object_type: "item",
label: item,
provenance_result_set_id: "rs-provenance"
},
active_result_set_id: "rs-provenance"
},
result_sets: [
{
result_set_id: "rs-provenance",
intent: "inventory_purchase_provenance_for_item",
entity_refs: [
{
index: 1,
entity_type: "item",
value: "Поступление товаров и услуг 00000000023 от 05.02.2015 0:00:00"
}
]
}
]
}
);
expect(carryover?.followupContext?.previous_intent).toBe("inventory_purchase_provenance_for_item");
expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period");
expect(carryover?.followupContext?.previous_filters).toMatchObject({
item,
organization: 'ООО "Альтернатива Плюс"',
period_from: "2015-02-01",
period_to: "2015-02-28"
});
});
it("keeps selected-object continuity for purchase-date VAT bridge even when an inventory root frame exists", () => {
const item = "Рабочая станция универсального специалиста";
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: [
`По позиции ${item} однозначный поставщик не подтвержден.`,
"Подтверждение:",
"- Первая найденная дата закупки: 05.02.2015."
].join("\n"),
debug: {
detected_intent: "inventory_purchase_provenance_for_item",
extracted_filters: {
item,
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2016-03-31"
},
anchor_type: "item",
anchor_value_resolved: item
}
}),
hasAddressFollowupContextSignal: () => false,
hasForeignAccountingPivotOverInventoryMessage: () => true,
resolveAddressIntent: () => ({ intent: "unknown" })
});
const carryover = policy.resolveAddressFollowupCarryoverContext(
"ндс можешь прикинуть на дату покупки рабочей станции?",
[],
null,
null,
{
session_context: {
active_focus_object: {
object_type: "item",
label: item,
provenance_result_set_id: "rs-provenance"
},
active_result_set_id: "rs-provenance"
},
result_sets: [
{
result_set_id: "rs-provenance",
intent: "inventory_purchase_provenance_for_item",
entity_refs: [
{
index: 1,
entity_type: "item",
value: "Поступление товаров и услуг 00000000023 от 05.02.2015 0:00:00"
}
]
}
]
}
);
expect(carryover?.followupSelectionMode).toBe("carry_previous_intent");
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
expect(carryover?.followupContext?.previous_intent).toBe("inventory_purchase_provenance_for_item");
expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period");
});
it("drops stale carryover for a fresh standalone topic from another intent family", () => {
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: "Прогноз НДС на март 2020 собран.",
debug: {
detected_intent: "vat_payable_forecast",
extracted_filters: {
period_from: "2020-03-01",
period_to: "2020-03-31"
}
}
}),
hasAddressFollowupContextSignal: () => true,
hasStandaloneAddressTopicSignal: () => true,
resolveAddressIntent: () => ({ intent: "inventory_on_hand_as_of_date" }),
resolveAddressIntentFamily: (intent: unknown) => {
if (String(intent ?? "").startsWith("vat_")) return "vat";
if (String(intent ?? "").startsWith("inventory_")) return "inventory";
return null;
}
});
const carryover = policy.resolveAddressFollowupCarryoverContext(
"остаток на складе за май 2020",
[],
null,
null,
null
);
expect(carryover).toBeNull();
});
it("keeps document intent for short counterparty retarget wording with action verb", () => {
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: "Собран список документов по контрагенту Чапурнов.",
debug: {
detected_intent: "list_documents_by_counterparty",
extracted_filters: {
counterparty: "Чапурнов"
},
anchor_type: "counterparty",
anchor_value_resolved: "Чапурнов"
}
}),
buildAddressFollowupOffer: () => ({
enabled: true,
source_intent: "list_documents_by_counterparty",
suggested_intents: ["bank_operations_by_counterparty"]
}),
isImplicitAddressContinuationByLlm: () => true
});
const carryover = policy.resolveAddressFollowupCarryoverContext("покажи по свк", [], null, null, null);
expect(carryover?.followupContext?.previous_intent).toBe("list_documents_by_counterparty");
expect(carryover?.followupSelectionMode).toBe("carry_previous_intent");
});
it("keeps root-scoped carryover for foreign accounting pivot over inventory drilldown", () => {
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: "Собран sale trace по позиции.",
debug: {
detected_intent: "inventory_sale_trace_for_item",
extracted_filters: {
item: "Кромка с клеем 33 дуб ниагара 137 м",
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2021-03-31"
},
anchor_type: "item",
anchor_value_resolved: "Кромка с клеем 33 дуб ниагара 137 м"
}
}),
hasAddressFollowupContextSignal: () => true,
resolveAddressIntent: () => ({ intent: "vat_payable_confirmed_as_of_date" }),
resolveAddressIntentFamily: (intent: unknown) => {
if (String(intent ?? "").startsWith("vat_")) return "vat";
if (String(intent ?? "").startsWith("inventory_")) return "inventory";
return null;
},
hasForeignAccountingPivotOverInventoryMessage: () => true,
findRecentInventoryRootFrame: () => ({
intent: "inventory_on_hand_as_of_date",
filters: {
organization: 'ООО "Альтернатива Плюс"',
warehouse: "Основной склад",
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
},
anchorType: "organization",
anchorValue: 'ООО "Альтернатива Плюс"'
})
});
const carryover = policy.resolveAddressFollowupCarryoverContext("а ндс?", [], null, null, null);
expect(carryover?.followupSelectionMode).toBe("carry_root_context");
expect(carryover?.followupContext?.root_context_only).toBe(true);
expect(carryover?.followupContext?.previous_intent).toBeUndefined();
expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date");
expect(carryover?.followupContext?.previous_filters).toEqual({
organization: 'ООО "Альтернатива Плюс"',
warehouse: "Основной склад",
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
});
});
it("prefers the freshest previous date scope over a stale inventory root frame during same-date pivot", () => {
const filters = buildRootScopedCarryoverFiltersForTests(
{
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2020-03-31",
period_from: "2020-03-01",
period_to: "2020-03-31"
},
{
filters: {
organization: 'ООО "Альтернатива Плюс"',
warehouse: "Основной склад",
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
}
}
);
expect(filters).toEqual({
organization: 'ООО "Альтернатива Плюс"',
warehouse: "Основной склад",
as_of_date: "2020-03-31",
period_from: "2020-03-01",
period_to: "2020-03-31"
});
});
});