318 lines
15 KiB
TypeScript
318 lines
15 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import { runAddressDecomposeStage } from "../src/services/address_runtime/decomposeStage";
|
||
|
||
describe("address follow-up temporal regressions", () => {
|
||
it("replaces VAT month-only follow-up with the previous follow-up year", () => {
|
||
const result = runAddressDecomposeStage("а на март", {
|
||
previous_intent: "vat_payable_confirmed_as_of_date",
|
||
previous_filters: {
|
||
period_from: "2019-05-01",
|
||
period_to: "2019-05-31",
|
||
as_of_date: "2019-05-31"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("vat_payable_confirmed_as_of_date");
|
||
expect(result?.filters.extracted_filters.period_from).toBe("2019-03-01");
|
||
expect(result?.filters.extracted_filters.period_to).toBe("2019-03-31");
|
||
expect(result?.filters.extracted_filters.as_of_date).toBe("2019-03-31");
|
||
expect(result?.baseReasons).toContain("period_derived_from_followup_context_year");
|
||
});
|
||
|
||
it("replaces VAT month-only follow-up with a later month from the same inherited year", () => {
|
||
const result = runAddressDecomposeStage("на сентябрь", {
|
||
previous_intent: "vat_payable_confirmed_as_of_date",
|
||
previous_filters: {
|
||
period_from: "2019-05-01",
|
||
period_to: "2019-05-31",
|
||
as_of_date: "2019-05-31"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("vat_payable_confirmed_as_of_date");
|
||
expect(result?.filters.extracted_filters.period_from).toBe("2019-09-01");
|
||
expect(result?.filters.extracted_filters.period_to).toBe("2019-09-30");
|
||
expect(result?.filters.extracted_filters.as_of_date).toBe("2019-09-30");
|
||
expect(result?.baseReasons).toContain("period_derived_from_followup_context_year");
|
||
});
|
||
|
||
it("inherits the previous VAT period for 'за этот период' follow-up", () => {
|
||
const result = runAddressDecomposeStage("какой НДС мы должны примерно заплатить за этот период", {
|
||
previous_intent: "vat_payable_confirmed_as_of_date",
|
||
previous_filters: {
|
||
period_from: "2017-05-01",
|
||
period_to: "2017-05-31",
|
||
as_of_date: "2017-05-31"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("vat_liability_confirmed_for_tax_period");
|
||
expect(result?.filters.extracted_filters.period_from).toBe("2017-05-01");
|
||
expect(result?.filters.extracted_filters.period_to).toBe("2017-05-31");
|
||
expect(result?.filters.extracted_filters.as_of_date).toBe("2017-05-31");
|
||
expect(result?.baseReasons).toContain("period_from_from_followup_context");
|
||
});
|
||
|
||
it("keeps inherited period when a follow-up retargets from receivables into VAT for the same period", () => {
|
||
const result = runAddressDecomposeStage("а какой НДС мы должны примерно заплатить за этот период", {
|
||
previous_intent: "receivables_confirmed_as_of_date",
|
||
target_intent: "vat_payable_confirmed_as_of_date",
|
||
previous_filters: {
|
||
period_from: "2017-05-01",
|
||
period_to: "2017-05-31",
|
||
as_of_date: "2017-05-31"
|
||
},
|
||
previous_anchor_type: "organization",
|
||
previous_anchor_value: 'ООО "Альтернатива Плюс"'
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("vat_liability_confirmed_for_tax_period");
|
||
expect(result?.filters.extracted_filters.period_from).toBe("2017-05-01");
|
||
expect(result?.filters.extracted_filters.period_to).toBe("2017-05-31");
|
||
expect(result?.filters.extracted_filters.as_of_date).toBe("2017-05-31");
|
||
});
|
||
|
||
it("uses follow-up target intent for short debt mirror like 'а нам?'", () => {
|
||
const result = runAddressDecomposeStage("а нам?", {
|
||
previous_intent: "payables_confirmed_as_of_date",
|
||
target_intent: "receivables_confirmed_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2026-04-16"
|
||
},
|
||
previous_anchor_type: "organization",
|
||
previous_anchor_value: 'ООО "Альтернатива Плюс"'
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("receivables_confirmed_as_of_date");
|
||
expect(result?.filters.extracted_filters.as_of_date).toBe("2026-04-16");
|
||
});
|
||
|
||
it("keeps same-date inventory pivot anchored to the previous VAT date", () => {
|
||
const result = runAddressDecomposeStage("какие остатки по складу на эту же дату", {
|
||
previous_intent: "vat_payable_confirmed_as_of_date",
|
||
previous_filters: {
|
||
period_from: "2019-09-01",
|
||
period_to: "2019-09-30",
|
||
as_of_date: "2019-09-30"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
|
||
expect(result?.filters.extracted_filters.as_of_date).toBe("2019-09-30");
|
||
expect(result?.filters.extracted_filters.warehouse).toBeUndefined();
|
||
expect(result?.baseReasons).toContain("as_of_date_from_followup_context");
|
||
});
|
||
|
||
it("keeps period window on inventory same-date follow-up phrased as 'по этой же дате'", () => {
|
||
const result = runAddressDecomposeStage(
|
||
"\u043f\u043e\u043a\u0430\u0436\u0438 \u043e\u0441\u0442\u0430\u0442\u043a\u0438 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 \u043f\u043e \u044d\u0442\u043e\u0439 \u0436\u0435 \u0434\u0430\u0442\u0435",
|
||
{
|
||
previous_intent: "receivables_confirmed_as_of_date",
|
||
previous_filters: {
|
||
organization: "\u0420\u0410\u0419\u041c",
|
||
period_from: "2020-03-01",
|
||
period_to: "2020-03-31",
|
||
as_of_date: "2020-03-31"
|
||
},
|
||
previous_anchor_type: "organization",
|
||
previous_anchor_value: "\u0420\u0410\u0419\u041c"
|
||
}
|
||
);
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
|
||
expect(result?.filters.extracted_filters.organization).toBe("\u0420\u0410\u0419\u041c");
|
||
expect(result?.filters.extracted_filters.as_of_date).toBe("2020-03-31");
|
||
expect(result?.filters.extracted_filters.period_from).toBe("2020-03-01");
|
||
expect(result?.filters.extracted_filters.period_to).toBe("2020-03-31");
|
||
expect(result?.filters.extracted_filters.warehouse).toBeUndefined();
|
||
expect(result?.baseReasons).toContain("period_from_from_followup_context");
|
||
expect(result?.baseReasons).toContain("period_to_from_followup_context");
|
||
});
|
||
|
||
it("does not inherit stale inventory as-of date over an explicit fresh snapshot date", () => {
|
||
const result = runAddressDecomposeStage(
|
||
"Покажи складской срез Альтернативы Плюс на 2026-04-16: что есть в остатках, какие самые заметные позиции, и что это говорит о бизнесе.",
|
||
{
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
target_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
organization: "ООО Альтернатива Плюс",
|
||
period_from: "2020-01-01",
|
||
period_to: "2020-12-31",
|
||
as_of_date: "2020-12-31"
|
||
},
|
||
previous_anchor_type: "organization",
|
||
previous_anchor_value: "ООО Альтернатива Плюс"
|
||
}
|
||
);
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
|
||
expect(result?.filters.extracted_filters.as_of_date).toBe("2026-04-16");
|
||
expect(result?.filters.extracted_filters.period_from).toBe("2026-04-01");
|
||
expect(result?.filters.extracted_filters.period_to).toBe("2026-04-30");
|
||
expect(result?.baseReasons).not.toContain("as_of_date_from_open_items_followup_context");
|
||
});
|
||
|
||
it("retargets inventory purchase-date VAT bridge into confirmed VAT period with inherited purchase month", () => {
|
||
const result = runAddressDecomposeStage("ндс можешь прикинуть на дату покупки рабочей станции?", {
|
||
previous_intent: "inventory_purchase_provenance_for_item",
|
||
target_intent: "vat_liability_confirmed_for_tax_period",
|
||
previous_filters: {
|
||
item: "Рабочая станция универсального специалиста",
|
||
organization: 'ООО "Альтернатива Плюс"',
|
||
period_from: "2015-02-01",
|
||
period_to: "2015-02-28",
|
||
as_of_date: "2016-03-31"
|
||
},
|
||
previous_anchor_type: "item",
|
||
previous_anchor_value: "Рабочая станция универсального специалиста"
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("vat_liability_confirmed_for_tax_period");
|
||
expect(result?.filters.extracted_filters.period_from).toBe("2015-02-01");
|
||
expect(result?.filters.extracted_filters.period_to).toBe("2015-02-28");
|
||
expect(result?.baseReasons).toContain("period_from_followup_context");
|
||
});
|
||
it("clears counterparty noise from bare organization clarification selections that resume inventory", () => {
|
||
const result = runAddressDecomposeStage("АЛЬТЕРНАТИВА", {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
target_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
organization: "ООО Альтернатива Плюс",
|
||
as_of_date: "2026-04-19"
|
||
},
|
||
previous_anchor_type: "organization",
|
||
previous_anchor_value: "ООО Альтернатива Плюс"
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
|
||
expect(result?.filters.extracted_filters.organization).toBe("ООО Альтернатива Плюс");
|
||
expect(result?.filters.extracted_filters.as_of_date).toBe("2026-04-19");
|
||
expect(result?.filters.extracted_filters.counterparty).toBeUndefined();
|
||
});
|
||
|
||
it("does not re-inherit organization alias as counterparty into historical inventory follow-up", () => {
|
||
const result = runAddressDecomposeStage("давай на июль 2017", {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
target_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
organization: "ООО Альтернатива Плюс",
|
||
counterparty: "АЛЬТЕРНАТИВА",
|
||
as_of_date: "2026-04-19"
|
||
},
|
||
previous_anchor_type: "counterparty",
|
||
previous_anchor_value: "АЛЬТЕРНАТИВА",
|
||
root_intent: "inventory_on_hand_as_of_date",
|
||
root_filters: {
|
||
organization: "ООО Альтернатива Плюс",
|
||
as_of_date: "2026-04-19"
|
||
},
|
||
root_anchor_type: "organization",
|
||
root_anchor_value: "ООО Альтернатива Плюс",
|
||
current_frame_kind: "inventory_root"
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
|
||
expect(result?.filters.extracted_filters.organization).toBe("ООО Альтернатива Плюс");
|
||
expect(result?.filters.extracted_filters.period_from).toBe("2017-07-01");
|
||
expect(result?.filters.extracted_filters.period_to).toBe("2017-07-31");
|
||
expect(result?.filters.extracted_filters.counterparty).toBeUndefined();
|
||
expect(result?.baseReasons).toContain("counterparty_cleared_as_organization_scope_alias");
|
||
});
|
||
|
||
it("does not inherit stale historical period into a fresh counterparty document root", () => {
|
||
const result = runAddressDecomposeStage("по чепурнову покажи все доки", {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
target_intent: "list_documents_by_counterparty",
|
||
previous_filters: {
|
||
organization: "ООО Альтернатива Плюс",
|
||
period_from: "2016-03-01",
|
||
period_to: "2016-03-31",
|
||
as_of_date: "2016-03-31"
|
||
},
|
||
previous_anchor_type: "organization",
|
||
previous_anchor_value: "ООО Альтернатива Плюс"
|
||
}, {
|
||
scope_target_kind: "counterparty",
|
||
scope_target_text: "Чепурнов",
|
||
date_scope_kind: "missing",
|
||
self_scope_detected: false,
|
||
selected_object_scope_detected: false
|
||
});
|
||
|
||
expect(result).not.toBeNull();
|
||
expect(result?.intent.intent).toBe("list_documents_by_counterparty");
|
||
expect(result?.filters.extracted_filters.counterparty).toBeTruthy();
|
||
expect(result?.filters.extracted_filters.period_from).toBeUndefined();
|
||
expect(result?.filters.extracted_filters.period_to).toBeUndefined();
|
||
});
|
||
|
||
it("replaces document and movement service-word anchors from counterparty follow-up context", () => {
|
||
const followupContext = {
|
||
previous_intent: "customer_revenue_and_payments" as const,
|
||
target_intent: "list_documents_by_counterparty" as const,
|
||
previous_filters: {
|
||
organization: "ООО Альтернатива Плюс",
|
||
counterparty: "Группа СВК",
|
||
period_from: "2020-01-01",
|
||
period_to: "2020-12-31"
|
||
},
|
||
previous_anchor_type: "counterparty" as const,
|
||
previous_anchor_value: "Группа СВК",
|
||
resolved_counterparty_from_display: true
|
||
};
|
||
|
||
const documents = runAddressDecomposeStage("документы по контрагенту документам", followupContext);
|
||
const movements = runAddressDecomposeStage("Проверить движение по счетам или документам", followupContext);
|
||
|
||
expect(documents?.intent.intent).toBe("list_documents_by_counterparty");
|
||
expect(documents?.filters.extracted_filters.counterparty).toBe("Группа СВК");
|
||
expect(documents?.baseReasons).toContain("counterparty_from_followup_context");
|
||
expect(movements?.intent.intent).toBe("list_documents_by_counterparty");
|
||
expect(movements?.filters.extracted_filters.counterparty).toBe("Группа СВК");
|
||
expect(movements?.baseReasons).toContain("counterparty_from_followup_context");
|
||
});
|
||
|
||
it("replaces pronoun chain anchors from counterparty follow-up context", () => {
|
||
const followupContext = {
|
||
previous_intent: "customer_revenue_and_payments" as const,
|
||
target_intent: "list_documents_by_counterparty" as const,
|
||
previous_filters: {
|
||
organization: "ООО Альтернатива Плюс",
|
||
counterparty: "Группа СВК"
|
||
},
|
||
previous_anchor_type: "counterparty" as const,
|
||
previous_anchor_value: "Группа СВК",
|
||
resolved_counterparty_from_display: true
|
||
};
|
||
|
||
const documents = runAddressDecomposeStage(
|
||
"покажи документы по этой цепочке и не смешивай Группа СВК с организацией ООО Альтернатива Плюс",
|
||
followupContext
|
||
);
|
||
|
||
expect(documents?.intent.intent).toBe("list_documents_by_counterparty");
|
||
expect(documents?.filters.extracted_filters.counterparty).toBe("Группа СВК");
|
||
expect(documents?.baseReasons).toContain("counterparty_from_followup_context");
|
||
});
|
||
});
|