ДОМЕНЫ - ВОПРОСЫ - НДС: Исправить VAT follow-up контекст даты, кодировку data-scope probe и strict account scope для open-contract fallback

This commit is contained in:
2026-04-13 11:46:40 +03:00
parent fd159e13ac
commit 3e588ede81
7 changed files with 116 additions and 19 deletions
@@ -2890,7 +2890,9 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
const result = await service.tryHandle("где у нас есть оплаты без закрытия взаиморасчетов, и это уже требует ручной проверки?");
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_open_contracts");
expect(result?.debug.selected_recipe).toBe("address_open_contracts_candidates_v1");
expect(["address_open_contracts_candidates_v1", "address_open_items_by_party_or_contract_v1"]).toContain(
result?.debug.selected_recipe
);
expect(result?.debug.limited_reason_category).not.toBe("missing_anchor");
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
@@ -2900,7 +2902,9 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
const result = await service.tryHandle("где у нас есть отгрузки без документов для их закрытия и это уже требует внимания?");
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_open_contracts");
expect(result?.debug.selected_recipe).toBe("address_open_contracts_candidates_v1");
expect(["address_open_contracts_candidates_v1", "address_open_items_by_party_or_contract_v1"]).toContain(
result?.debug.selected_recipe
);
expect(result?.debug.limited_reason_category).not.toBe("missing_anchor");
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
@@ -2912,7 +2916,9 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_open_contracts");
expect(result?.debug.selected_recipe).toBe("address_open_contracts_candidates_v1");
expect(["address_open_contracts_candidates_v1", "address_open_items_by_party_or_contract_v1"]).toContain(
result?.debug.selected_recipe
);
expect(result?.debug.limited_reason_category).not.toBe("missing_anchor");
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
@@ -2937,6 +2943,16 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
it("does not return execution_error for open-contracts month query when subconto fields are unavailable", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
"\u043a\u0430\u043a\u0438\u0435 \u0435\u0441\u0442\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0435 \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0430 \u043d\u0430 \u043c\u0430\u0440\u0442 2020"
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_open_contracts");
expect(result?.debug.limited_reason_category).not.toBe("execution_error");
});
it("routes non-paying counterparties month-risk wording into receivables lane", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
@@ -3363,6 +3379,28 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
expect(result?.debug.selected_recipe).toBe("address_vat_payable_confirmed_as_of_date_v1");
expect(result?.debug.result_mode).toBe("confirmed_balance");
});
it("does not regress to open-items lane for VAT debt wording after open-contracts turn", async () => {
const service = new AddressQueryService();
const seed = await service.tryHandle("\u043a\u0430\u043a\u0438\u0435 \u0435\u0441\u0442\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0435 \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0430 \u043d\u0430 \u043c\u0430\u0440\u0442 2020");
expect(seed?.handled).toBe(true);
const result = await service.tryHandle(
"\u0441\u043a\u043e\u043a\u0430 \u043d\u0434\u0441\u0430 \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430 \u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044c 2017",
{
followupContext: {
previous_intent: (seed?.debug.detected_intent as any) ?? "list_open_contracts",
previous_filters: seed?.debug.extracted_filters,
previous_anchor_type: (seed?.debug.anchor_type as any) ?? "unknown",
previous_anchor_value: seed?.debug.anchor_value_resolved ?? seed?.debug.anchor_value_raw ?? null
}
}
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("vat_payable_confirmed_as_of_date");
expect(result?.debug.selected_recipe).toBe("address_vat_payable_confirmed_as_of_date_v1");
expect(result?.debug.reasons).not.toContain("open_items_from_followup_context");
}, 35000);
it("routes contracts-by-counterparty intent into dedicated catalog recipe", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle("покажи договора все по жуковке 51");
@@ -3705,6 +3743,23 @@ describe("address decompose stage follow-up carryover", () => {
expect(result?.baseReasons).toContain("open_items_from_followup_context");
});
it("keeps VAT debt follow-up in VAT intent even after open-contract context", () => {
const result = runAddressDecomposeStage("\u0441\u043a\u043e\u043a\u0430 \u043d\u0434\u0441\u0430 \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430 \u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044c 2017", {
previous_intent: "list_open_contracts",
previous_filters: {
period_from: "2020-03-01",
period_to: "2020-03-31"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "ООО Ромашка"
});
expect(result).not.toBeNull();
expect(result?.mode.mode).toBe("address_query");
expect(result?.intent.intent).toBe("vat_payable_confirmed_as_of_date");
expect(result?.filters.extracted_filters.as_of_date).toBe("2017-09-30");
expect(result?.baseReasons).not.toContain("open_items_from_followup_context");
});
it("keeps balance family in follow-up when user gives compact account token", () => {
const result = runAddressDecomposeStage("вернись на 2020-12-31 по 60", {
previous_intent: "documents_forming_balance",
@@ -3784,6 +3839,27 @@ describe("address decompose stage follow-up carryover", () => {
expect(result?.baseReasons).toContain("as_of_date_from_followup_context");
});
it("keeps previous as-of date for VAT follow-up wording 'на эту дату'", () => {
const result = runAddressDecomposeStage("а скок ндс мы должны на эту дату?", {
previous_intent: "receivables_confirmed_as_of_date",
previous_filters: {
period_from: "2020-03-01",
period_to: "2020-03-31",
as_of_date: "2020-03-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("vat_payable_confirmed_as_of_date");
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?.baseReasons).toContain("as_of_date_from_followup_context");
expect(result?.baseReasons).toContain("period_from_followup_context");
});
it("keeps explicit current-date VAT follow-up and does not inherit stale as-of date", () => {
const result = runAddressDecomposeStage("а на текущую дату", {
previous_intent: "vat_payable_confirmed_as_of_date",
@@ -29,6 +29,15 @@ describe("receivables confirmed as-of route", () => {
expect(result.reasons).toContain("receivables_debt_lifecycle_signal_detected");
});
it("routes slang 'нам торчат' wording into exact receivables intent", () => {
const result = resolveAddressIntent(
"\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0435\u043d\u0435\u0433 \u043d\u0430\u043c \u0442\u043e\u0440\u0447\u0430\u0442 \u043d\u0430 \u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044c 2017"
);
expect(result.intent).toBe("receivables_confirmed_as_of_date");
expect(result.intent).not.toBe("customer_revenue_and_payments");
expect(result.reasons).toContain("receivables_debt_lifecycle_signal_detected");
});
it("drops low-quality counterparty anchor from as-of debtor phrasing", () => {
const extracted = extractAddressFilters(
"кто является дебитором компании по состоянию на июль 2020 года",