import { describe, expect, it } from "vitest"; import { createAssistantTurnMeaningPolicy } from "../src/services/assistantTurnMeaningPolicy"; import { resolveAddressIntent } from "../src/services/addressIntentResolver"; function buildPolicy(overrides: Record = {}) { return createAssistantTurnMeaningPolicy({ compactWhitespace: (value: string) => String(value ?? "").replace(/\s+/g, " ").trim(), repairAddressMojibake: (value: string) => value, resolveAddressIntent, toNonEmptyString: (value: unknown) => { if (value === null || value === undefined) { return null; } const text = String(value).trim(); return text.length > 0 ? text : null; }, ...overrides }); } describe("assistantTurnMeaningPolicy", () => { it("recovers a supported receivables intent from light current-turn typo noise", () => { const policy = buildPolicy(); const meaning = policy.resolveAssistantTurnMeaning({ rawUserMessage: "\u043a\u0442\u043e \u043d\u0430\u043c\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0434\u0435\u043d\u0435\u0433 \u043d\u0430 \u0441\u0435\u0433\u043e\u0434\u043d\u044f" }); expect(meaning.schema_version).toBe("assistant_turn_meaning_v1"); expect(meaning.explicit_intent_candidate).toBe("receivables_confirmed_as_of_date"); expect(meaning.asked_domain_family).toBe("receivables"); expect(meaning.carryover_budget).toBe("matching_family_only"); expect(meaning.stale_replay_forbidden).toBe(false); }); it("promotes specific counterparty turnover to the supported revenue intent", () => { const policy = buildPolicy(); const meaning = policy.resolveAssistantTurnMeaning({ rawUserMessage: "\u043a\u0430\u043a\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 \u0431\u044b\u043b \u0441\u0432\u043a" }); expect(meaning.explicit_intent_candidate).toBe("customer_revenue_and_payments"); expect(meaning.asked_domain_family).toBe("counterparty"); expect(meaning.asked_action_family).toBe("counterparty_value_or_turnover"); expect(meaning.unsupported_but_understood_family).toBeNull(); expect(meaning.stale_replay_forbidden).toBe(false); expect(meaning.carryover_budget).toBe("matching_family_only"); expect(meaning.explicit_entity_candidates).toEqual([ { type: "counterparty", value: "\u0441\u0432\u043a", source: "current_turn_loose_entity_tail" } ]); }); it("ignores temporal tail words in all-time revenue ranking questions", () => { const policy = buildPolicy({ resolveAddressIntent: (text: string) => text.includes("\u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0439") ? { intent: "customer_revenue_and_payments", confidence: "high" } : resolveAddressIntent(text) }); const meaning = policy.resolveAssistantTurnMeaning({ rawUserMessage: "\u043a\u0442\u043e \u0443 \u043d\u0430\u0441 \u0441\u0430\u043c\u044b\u0439 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0437\u0430 \u0432\u0441\u0435 \u0432\u0440\u0435\u043c\u044f" }); expect(meaning.explicit_intent_candidate).toBe("customer_revenue_and_payments"); expect(meaning.explicit_entity_candidates).toEqual([]); expect(meaning.stale_replay_forbidden).toBe(false); }); it("treats VAT period questions as supported current-turn intent", () => { const policy = buildPolicy({ resolveAddressIntent: (text: string) => text.includes("\u043d\u0434\u0441") ? { intent: "vat_liability_confirmed_for_tax_period", confidence: "high" } : resolveAddressIntent(text) }); const meaning = policy.resolveAssistantTurnMeaning({ rawUserMessage: "\u0430 \u043a\u0430\u043a\u043e\u0439 \u043d\u0434\u0441 \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c \u0437\u0430 \u044d\u0442\u043e\u0442 \u043f\u0435\u0440\u0438\u043e\u0434" }); expect(meaning.explicit_intent_candidate).toBe("vat_liability_confirmed_for_tax_period"); expect(meaning.asked_domain_family).toBe("vat"); expect(meaning.asked_action_family).toBe("confirmed_tax_period"); expect(meaning.stale_replay_forbidden).toBe(false); }); it("marks broad business evaluation as unsupported-but-understood instead of stale lifecycle replay", () => { const policy = buildPolicy({ resolveAddressIntent: () => ({ intent: "counterparty_activity_lifecycle", confidence: "high" }) }); const meaning = policy.resolveAssistantTurnMeaning({ rawUserMessage: "Как ты оценишь деятельность компании?" }); expect(meaning.explicit_intent_candidate).toBeNull(); expect(meaning.asked_domain_family).toBe("business_summary"); expect(meaning.asked_action_family).toBe("broad_evaluation"); expect(meaning.unsupported_but_understood_family).toBe("broad_business_evaluation"); expect(meaning.stale_replay_forbidden).toBe(true); expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); }); it("recognizes broad company analysis wording as the same bounded business overview ask", () => { const policy = buildPolicy({ resolveAddressIntent: () => ({ intent: "unknown", confidence: "low" }) }); const meaning = policy.resolveAssistantTurnMeaning({ rawUserMessage: "Дай полный анализ компании и LLM-аудит бизнеса" }); expect(meaning.explicit_intent_candidate).toBeNull(); expect(meaning.asked_domain_family).toBe("business_summary"); expect(meaning.asked_action_family).toBe("broad_evaluation"); expect(meaning.unsupported_but_understood_family).toBe("broad_business_evaluation"); expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); }); it("lets explicit business overview wording beat turnover and net-flow cues", () => { const policy = buildPolicy({ resolveAddressIntent: () => ({ intent: "customer_revenue_and_payments", confidence: "high" }) }); const meaning = policy.resolveAssistantTurnMeaning({ rawUserMessage: "\u0414\u0430\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043e\u0431\u0437\u043e\u0440 \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441: \u043e\u0431\u043e\u0440\u043e\u0442\u044b, \u043d\u0435\u0442\u0442\u043e, \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c." }); expect(meaning.explicit_intent_candidate).toBeNull(); expect(meaning.asked_domain_family).toBe("business_summary"); expect(meaning.asked_action_family).toBe("broad_evaluation"); expect(meaning.explicit_entity_candidates).toEqual([]); expect(meaning.unsupported_but_understood_family).toBe("broad_business_evaluation"); expect(meaning.stale_replay_forbidden).toBe(true); expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); }); it("treats organization-level earnings and best-year wording as business overview", () => { const policy = buildPolicy({ resolveAddressIntent: () => ({ intent: "customer_revenue_and_payments", confidence: "high" }) }); const earnings = policy.resolveAssistantTurnMeaning({ rawUserMessage: "сколько вообще денег мы заработали за все время?" }); expect(earnings.explicit_intent_candidate).toBeNull(); expect(earnings.asked_domain_family).toBe("business_summary"); expect(earnings.asked_action_family).toBe("broad_evaluation"); expect(earnings.unsupported_but_understood_family).toBe("broad_business_evaluation"); expect(earnings.stale_replay_forbidden).toBe(true); const bestYear = policy.resolveAssistantTurnMeaning({ rawUserMessage: "какой у нас самый доходный год" }); expect(bestYear.explicit_intent_candidate).toBeNull(); expect(bestYear.asked_domain_family).toBe("business_summary"); expect(bestYear.asked_action_family).toBe("broad_evaluation"); expect(bestYear.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); const profitMargin = policy.resolveAssistantTurnMeaning({ rawUserMessage: "\u043a\u0430\u043a\u0430\u044f \u0443 \u043d\u0430\u0441 \u0447\u0438\u0441\u0442\u0430\u044f \u043f\u0440\u0438\u0431\u044b\u043b\u044c \u0438 \u043c\u0430\u0440\u0436\u0430 \u0437\u0430 2020?" }); expect(profitMargin.explicit_intent_candidate).toBeNull(); expect(profitMargin.asked_domain_family).toBe("business_summary"); expect(profitMargin.asked_action_family).toBe("broad_evaluation"); expect(profitMargin.unsupported_but_understood_family).toBe("broad_business_evaluation"); expect(profitMargin.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); const overdueDebt = policy.resolveAssistantTurnMeaning({ rawUserMessage: "\u043a\u0430\u043a\u0430\u044f \u0443 \u043d\u0430\u0441 \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a\u0430 \u043f\u043e \u0434\u043e\u043b\u0433\u0430\u043c \u0437\u0430 2020?" }); expect(overdueDebt.explicit_intent_candidate).toBeNull(); expect(overdueDebt.asked_domain_family).toBe("business_summary"); expect(overdueDebt.asked_action_family).toBe("broad_evaluation"); expect(overdueDebt.unsupported_but_understood_family).toBe("broad_business_evaluation"); expect(overdueDebt.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); const inventoryReserve = policy.resolveAssistantTurnMeaning({ rawUserMessage: "\u043a\u0430\u043a\u0438\u0435 \u0443 \u043d\u0430\u0441 \u0440\u0438\u0441\u043a\u0438 \u043d\u0435\u043b\u0438\u043a\u0432\u0438\u0434\u0430 \u0438 \u0440\u0435\u0437\u0435\u0440\u0432\u044b \u043f\u043e \u0441\u043a\u043b\u0430\u0434\u0443 \u0437\u0430 2020?" }); expect(inventoryReserve.explicit_intent_candidate).toBeNull(); expect(inventoryReserve.asked_domain_family).toBe("business_summary"); expect(inventoryReserve.asked_action_family).toBe("broad_evaluation"); expect(inventoryReserve.unsupported_but_understood_family).toBe("broad_business_evaluation"); expect(inventoryReserve.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); const supplierQuality = policy.resolveAssistantTurnMeaning({ rawUserMessage: "\u043a\u0430\u043a\u0438\u0435 \u0443 \u043d\u0430\u0441 \u0440\u0438\u0441\u043a\u0438 \u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u0432 \u0437\u0430 2020?" }); expect(supplierQuality.explicit_intent_candidate).toBeNull(); expect(supplierQuality.asked_domain_family).toBe("business_summary"); expect(supplierQuality.asked_action_family).toBe("broad_evaluation"); expect(supplierQuality.unsupported_but_understood_family).toBe("broad_business_evaluation"); expect(supplierQuality.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); }); });