ДОМЕНЫ - ВОПРОСЫ - НДС: Исправить VAT follow-up контекст даты, кодировку data-scope probe и strict account scope для open-contract fallback
This commit is contained in:
parent
fd159e13ac
commit
3e588ede81
|
|
@ -2293,7 +2293,8 @@ class AddressQueryService {
|
|||
const missingSubcontoFallbackEligible = plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
||||
plan.recipe.recipe_id === "address_movements_payables_v1" ||
|
||||
plan.recipe.recipe_id === "address_payables_confirmed_as_of_date_v1" ||
|
||||
plan.recipe.recipe_id === "address_receivables_confirmed_as_of_date_v1";
|
||||
plan.recipe.recipe_id === "address_receivables_confirmed_as_of_date_v1" ||
|
||||
plan.recipe.recipe_id === "address_open_contracts_candidates_v1";
|
||||
const missingSubcontoErrorDetected = Boolean(mcp.error && missingSubcontoFallbackEligible && isMissingSubcontoFieldError(mcp.error));
|
||||
if (missingSubcontoErrorDetected) {
|
||||
let missingSubcontoResolvedByComposite = false;
|
||||
|
|
|
|||
|
|
@ -408,10 +408,14 @@ function hasFlexibleReceivablesDebtSignal(text: string): boolean {
|
|||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
/(?:кто(?:\s+\S+){0,4}\s+нам(?:\s+\S+){0,4}\s+долж)/iu.test(normalized) ||
|
||||
/(?:нам(?:\s+\S+){0,4}\s+кто(?:\s+\S+){0,4}\s+долж)/iu.test(normalized)
|
||||
);
|
||||
const hasFlexibleWhoOwesUs =
|
||||
/(?:\u043a\u0442\u043e(?:\s+\S+){0,4}\s+\u043d\u0430\u043c(?:\s+\S+){0,4}\s+\u0434\u043e\u043b\u0436)/iu.test(normalized) ||
|
||||
/(?:\u043d\u0430\u043c(?:\s+\S+){0,4}\s+\u043a\u0442\u043e(?:\s+\S+){0,4}\s+\u0434\u043e\u043b\u0436)/iu.test(normalized);
|
||||
const hasTorchatToUsSignal =
|
||||
/(?:\u043d\u0430\u043c(?:\s+\S+){0,3}\s+\u0442\u043e\u0440\u0447(?:\u0430\u0442|\u0438\u0442)|\u0442\u043e\u0440\u0447(?:\u0430\u0442|\u0438\u0442)(?:\s+\S+){0,3}\s+\u043d\u0430\u043c)/iu.test(
|
||||
normalized
|
||||
);
|
||||
return hasFlexibleWhoOwesUs || hasTorchatToUsSignal;
|
||||
}
|
||||
|
||||
function hasFlexiblePayablesDebtSignal(text: string): boolean {
|
||||
|
|
|
|||
|
|
@ -1526,7 +1526,13 @@ function enforceStrictAccountScopeForIntent(
|
|||
plan: AddressRecipeExecutionPlan,
|
||||
intent: AddressIntent
|
||||
): AddressRecipeExecutionPlan {
|
||||
if (intent !== "list_receivables_counterparties" || plan.account_scope_mode === "strict") {
|
||||
const strictScopeIntents: AddressIntent[] = [
|
||||
"list_receivables_counterparties",
|
||||
"list_open_contracts",
|
||||
"open_items_by_counterparty_or_contract"
|
||||
];
|
||||
const shouldEnforceStrictScope = strictScopeIntents.includes(intent);
|
||||
if (!shouldEnforceStrictScope || plan.account_scope_mode === "strict") {
|
||||
return plan;
|
||||
}
|
||||
return {
|
||||
|
|
@ -2829,7 +2835,8 @@ export class AddressQueryService {
|
|||
plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
||||
plan.recipe.recipe_id === "address_movements_payables_v1" ||
|
||||
plan.recipe.recipe_id === "address_payables_confirmed_as_of_date_v1" ||
|
||||
plan.recipe.recipe_id === "address_receivables_confirmed_as_of_date_v1";
|
||||
plan.recipe.recipe_id === "address_receivables_confirmed_as_of_date_v1" ||
|
||||
plan.recipe.recipe_id === "address_open_contracts_candidates_v1";
|
||||
const missingSubcontoErrorDetected = Boolean(
|
||||
mcp.error && missingSubcontoFallbackEligible && isMissingSubcontoFieldError(mcp.error)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ function hasAllTimeHint(text: string): boolean {
|
|||
}
|
||||
|
||||
function hasSameDateHint(text: string): boolean {
|
||||
return /(?:на\s+ту\s+же\s+дат[ауеы]|на\s+эту\s+же\s+дат[ауеы]|та\s+же\s+дата|same\s+date|as\s+of\s+same\s+date|the\s+same\s+date)/iu.test(
|
||||
return /(?:на\s+ту\s+же\s+дат[ауеы]|на\s+эту\s+же\s+дат[ауеы]|на\s+эту\s+дат[ауеы]|эту\s+дат[ауеы]|та\s+же\s+дата|same\s+date|as\s+of\s+same\s+date|the\s+same\s+date)/iu.test(
|
||||
String(text ?? "")
|
||||
);
|
||||
}
|
||||
|
|
@ -651,7 +651,8 @@ function deriveIntentWithFollowupContext(
|
|||
};
|
||||
}
|
||||
|
||||
if (hasOpenItemsHint(normalizedMessage) && hasAnyPartyAnchor) {
|
||||
const allowOpenItemsFollowupFallback = detectedIntent.intent === "unknown" && !isVatFollowup;
|
||||
if (allowOpenItemsFollowupFallback && hasOpenItemsHint(normalizedMessage) && hasAnyPartyAnchor) {
|
||||
return {
|
||||
intent: "open_items_by_counterparty_or_contract",
|
||||
confidence: "low",
|
||||
|
|
|
|||
|
|
@ -5037,14 +5037,13 @@ async function resolveAssistantDataScopeProbe() {
|
|||
};
|
||||
}
|
||||
const catalogQueryCandidates = [
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 20 ПРЕДСТАВЛЕН<D095>?Е(Организации.Ссылка) КАК Организация <20>?З Справочник.Организации КАК Организации",
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.Наименование КАК Организация <20>?З Справочник.Организации КАК Организации",
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.НаименованиеПолное КАК Организация <20>?З Справочник.Организации КАК Организации",
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 100 Организации.Ссылка КАК Организация, ПРЕДСТАВЛЕН<D095>?Е(Организации.Ссылка) КАК ОрганизацияПредставление <20>?З Справочник.Организации КАК Организации"
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.Наименование КАК Организация ИЗ Справочник.Организации КАК Организации",
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.НаименованиеПолное КАК Организация ИЗ Справочник.Организации КАК Организации",
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 100 Организации.Ссылка КАК Организация, ПРЕДСТАВЛЕНИЕ(Организации.Ссылка) КАК ОрганизацияПредставление ИЗ Справочник.Организации КАК Организации"
|
||||
];
|
||||
const movementProbeCandidates = [
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация, ПРЕДСТАВЛЕН<D095>?Е(Движения.Организация) КАК ОрганизацияПредставление <20>?З РегистрБухгалтерии.Хозрасчетный КАК Движения УПОРЯДОЧ<EFBFBD>?ТЬ ПО Движения.Период УБЫВ",
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация <EFBFBD>?З РегистрБухгалтерии.Хозрасчетный КАК Движения"
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация ИЗ РегистрБухгалтерии.Хозрасчетный КАК Движения УПОРЯДОЧИТЬ ПО Движения.Период УБЫВ",
|
||||
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация ИЗ РегистрБухгалтерии.Хозрасчетный КАК Движения"
|
||||
];
|
||||
let lastError = null;
|
||||
const catalogFacts = { names: [], refs: [], pairs: [] };
|
||||
|
|
@ -5175,7 +5174,7 @@ function buildAssistantOperationalBoundaryReply() {
|
|||
return [
|
||||
"Понимаю, что ситуация срочная.",
|
||||
"Я не могу сам настраивать 1С или менять базу/конфигурацию.",
|
||||
"Могу помочь безопасно: разберем симптомы и подготовим точные шаги для вашего 1С/<EFBFBD>?Т-админа."
|
||||
"Могу помочь безопасно: разберем симптомы и подготовим точные шаги для вашего 1С/ИТ-админа."
|
||||
].join(" ");
|
||||
}
|
||||
function buildAssistantSafetyRefusalReply() {
|
||||
|
|
|
|||
|
|
@ -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 года",
|
||||
|
|
|
|||
Loading…
Reference in New Issue