АДРЕСНЫЙ РЕЖИМ -Step-4: Batch-2 lifecycle закрыт gate 36/36, nightly 102/102 + 25/25 PASS
This commit is contained in:
@@ -1403,6 +1403,41 @@ describe("address intent resolver expansion (M2.3a)", () => {
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
|
||||
it("keeps supplier lifecycle segmentation with operations wording in lifecycle intent", () => {
|
||||
const result = resolveAddressIntent("Раздели поставщиков на регулярных и эпизодических по частоте операций.");
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
|
||||
it("keeps stale supplier operations wording in lifecycle intent", () => {
|
||||
const result = resolveAddressIntent("Какие поставщики давно не использовались в операционной активности?");
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
|
||||
it("keeps slang all-customers-all-time wording in lifecycle intent", () => {
|
||||
const result = resolveAddressIntent("выведи всех заков за все время");
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
|
||||
it("keeps churn wording with year in lifecycle intent", () => {
|
||||
const result = resolveAddressIntent("кто был активен в 2020 и потом отвалился");
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
|
||||
it("keeps one-time-plus-churn wording in lifecycle intent", () => {
|
||||
const result = resolveAddressIntent("кто с нами был ровно один раз и пропал");
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
|
||||
it("keeps oldest-collaboration slang wording in lifecycle intent", () => {
|
||||
const result = resolveAddressIntent("самые старые по сотрудничеству кто");
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
|
||||
it("keeps regular-vs-one-off supplier slang in lifecycle intent (not population)", () => {
|
||||
const result = resolveAddressIntent("разбей поставщиков на регуляр и разовые");
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
|
||||
it("resolves contract usage overview intent", () => {
|
||||
const result = resolveAddressIntent("Сколько всего договоров заведено и сколько из них реально использовались?");
|
||||
expect(result.intent).toBe("contract_usage_overview");
|
||||
@@ -2219,6 +2254,22 @@ describe("address query limited taxonomy and stage diagnostics", () => {
|
||||
});
|
||||
|
||||
describe("address decompose stage follow-up carryover", () => {
|
||||
it("keeps slang all-customers-all-time wording in address lane via resolved intent fallback", () => {
|
||||
const result = runAddressDecomposeStage("выведи всех заков за все время", null);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.mode.mode).toBe("address_query");
|
||||
expect(result?.intent.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
|
||||
it("keeps churn wording with year in address lane via resolved intent fallback", () => {
|
||||
const result = runAddressDecomposeStage("кто был активен в 2020 и потом отвалился", null);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.mode.mode).toBe("address_query");
|
||||
expect(result?.intent.intent).toBe("counterparty_activity_lifecycle");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2020-01-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2020-12-31");
|
||||
});
|
||||
|
||||
it("uses short bare year in follow-up period switch", () => {
|
||||
const result = runAddressDecomposeStage("теперь за 21", {
|
||||
previous_intent: "counterparty_activity_lifecycle",
|
||||
@@ -2235,6 +2286,24 @@ describe("address decompose stage follow-up carryover", () => {
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2021-12-31");
|
||||
});
|
||||
|
||||
it("keeps lifecycle follow-up phrasing with referential pointer and inherits period", () => {
|
||||
const result = runAddressDecomposeStage("А кто из них новые?", {
|
||||
previous_intent: "counterparty_activity_lifecycle",
|
||||
previous_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
previous_anchor_type: "unknown",
|
||||
previous_anchor_value: null
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.mode.mode).toBe("address_query");
|
||||
expect(result?.intent.intent).toBe("counterparty_activity_lifecycle");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2020-01-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2020-12-31");
|
||||
expect(result?.baseReasons).toContain("address_followup_context_applied");
|
||||
});
|
||||
|
||||
it("keeps short period follow-up in address lane and preserves previous counterparty anchor", () => {
|
||||
const result = runAddressDecomposeStage("а теперь только за май 2020", {
|
||||
previous_intent: "list_documents_by_counterparty",
|
||||
|
||||
@@ -192,4 +192,91 @@ describe("assistant address follow-up carryover", () => {
|
||||
expect(String(calls[1].options?.followupContext?.previous_anchor_value ?? "").length).toBeGreaterThan(0);
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reuses last real address context after intermediate clarification fallback", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const lifecycleFollowupMessage = "А кто из них новые?";
|
||||
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||
calls.push({ message, options });
|
||||
if (message === "что там не так?") {
|
||||
return null;
|
||||
}
|
||||
if (message === lifecycleFollowupMessage && !options?.followupContext) {
|
||||
return null;
|
||||
}
|
||||
if (message === lifecycleFollowupMessage && options?.followupContext) {
|
||||
return buildAddressLaneResult({
|
||||
debug: {
|
||||
...buildAddressLaneResult().debug,
|
||||
detected_intent: "counterparty_activity_lifecycle",
|
||||
extracted_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
reasons: ["address_action_detected", "address_followup_context_applied"]
|
||||
}
|
||||
});
|
||||
}
|
||||
return buildAddressLaneResult();
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async (payload: any) => ({
|
||||
assistant_reply: payload?.userQuestion === "что там не так?" ? "Нужно уточнение по фокусу." : "unexpected_normalizer_call",
|
||||
reply_type: payload?.userQuestion === "что там не так?" ? "clarification_required" : "partial_coverage",
|
||||
debug: {
|
||||
prompt_version: "address_query_runtime_v1",
|
||||
detected_mode: null,
|
||||
detected_intent: null
|
||||
}
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const sessionId = `asst-address-followup-clar-${Date.now()}`;
|
||||
const first = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: "покажи документы по свк за 2020",
|
||||
useMock: true
|
||||
} as any);
|
||||
expect(first.ok).toBe(true);
|
||||
expect(first.reply_type).toBe("factual");
|
||||
|
||||
const second = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: "что там не так?",
|
||||
useMock: true
|
||||
} as any);
|
||||
expect(second.ok).toBe(true);
|
||||
expect(second.reply_type).toBe("clarification_required");
|
||||
|
||||
const third = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: lifecycleFollowupMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
expect(third.ok).toBe(true);
|
||||
expect(third.reply_type).toBe("factual");
|
||||
expect(third.debug?.detected_mode).toBe("address_query");
|
||||
expect(third.debug?.detected_intent).toBe("counterparty_activity_lifecycle");
|
||||
|
||||
expect(calls.length).toBeGreaterThanOrEqual(2);
|
||||
const contextualCall = calls.find((item) => item.message === lifecycleFollowupMessage && Boolean(item.options?.followupContext));
|
||||
expect(contextualCall).toBeTruthy();
|
||||
expect(contextualCall?.options?.followupContext?.previous_intent).toBe("list_documents_by_counterparty");
|
||||
expect(contextualCall?.options?.followupContext?.previous_anchor_type).toBe("counterparty");
|
||||
expect(String(contextualCall?.options?.followupContext?.previous_anchor_value ?? "").length).toBeGreaterThan(0);
|
||||
expect(normalizerService.normalize).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user