ДОМЕНЫ - ВОПРОСЫ - НДС: Исправить роутинг НДС-запросов с формулировкой мы должны и добавить регрессионные тесты
This commit is contained in:
parent
4205c6b3e6
commit
fd159e13ac
|
|
@ -159,7 +159,7 @@ function toIsoDate(year, month, day) {
|
||||||
return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
function extractAsOfDate(text) {
|
function extractAsOfDate(text) {
|
||||||
if (/\b(сегодня|на\s+сегодня|today|as\s+of\s+today)\b/i.test(text)) {
|
if (/\b(сегодня|на\s+сегодня|на\s+текущ(?:ую|ая|ий|ее|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодняшн(?:юю|ий|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+текущ(?:ий|ую)\s+момент|today|as\s+of\s+today|current\s+date|as\s+of\s+current\s+date)\b/i.test(text)) {
|
||||||
return new Date().toISOString().slice(0, 10);
|
return new Date().toISOString().slice(0, 10);
|
||||||
}
|
}
|
||||||
const ymd = text.match(DATE_YMD_PATTERN);
|
const ymd = text.match(DATE_YMD_PATTERN);
|
||||||
|
|
|
||||||
|
|
@ -559,7 +559,7 @@ function hasVatLiabilityConfirmedTaxPeriodSignal(text) {
|
||||||
if (!hasVatLexeme) {
|
if (!hasVatLexeme) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const hasPaymentCue = /(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить)/iu.test(text);
|
const hasPaymentCue = /(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить|мы\s+должн[аы]?|должн[аы]?\s+мы)/iu.test(text);
|
||||||
if (!hasPaymentCue) {
|
if (!hasPaymentCue) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -584,7 +584,7 @@ function hasVatPayableConfirmedSignal(text) {
|
||||||
if (!hasVatLexeme) {
|
if (!hasVatLexeme) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const hasPaymentCue = /(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить)/iu.test(text);
|
const hasPaymentCue = /(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить|мы\s+должн[аы]?|должн[аы]?\s+мы)/iu.test(text);
|
||||||
if (!hasPaymentCue) {
|
if (!hasPaymentCue) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2973,7 +2973,15 @@ class AddressQueryService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (filteredRows.length === 0) {
|
const allowConfirmedAsOfZeroSnapshot = filteredRows.length === 0 &&
|
||||||
|
(composeIntent === "vat_payable_confirmed_as_of_date" ||
|
||||||
|
composeIntent === "payables_confirmed_as_of_date" ||
|
||||||
|
composeIntent === "receivables_confirmed_as_of_date") &&
|
||||||
|
(stageStatus === "no_raw_rows" || stageStatus === "materialized_but_filtered_out_by_recipe") &&
|
||||||
|
!toNonEmptyFilterValue(filters.extracted_filters.counterparty) &&
|
||||||
|
!toNonEmptyFilterValue(filters.extracted_filters.contract) &&
|
||||||
|
!toNonEmptyFilterValue(filters.extracted_filters.document_ref);
|
||||||
|
if (filteredRows.length === 0 && !allowConfirmedAsOfZeroSnapshot) {
|
||||||
const hadBaseRows = normalizedRows.length > 0 || mcp.fetched_rows > 0;
|
const hadBaseRows = normalizedRows.length > 0 || mcp.fetched_rows > 0;
|
||||||
const hadAnchorMatchedRows = filterByAnchors.length > 0;
|
const hadAnchorMatchedRows = filterByAnchors.length > 0;
|
||||||
const isVisibilityGapCandidate = hadBaseRows &&
|
const isVisibilityGapCandidate = hadBaseRows &&
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ function hasSameDateHint(text) {
|
||||||
function hasExplicitPeriodLiteral(text) {
|
function hasExplicitPeriodLiteral(text) {
|
||||||
return /\b(?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?\b/.test(String(text ?? ""));
|
return /\b(?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?\b/.test(String(text ?? ""));
|
||||||
}
|
}
|
||||||
|
function hasExplicitCurrentDateHint(text) {
|
||||||
|
return /(?:на\s+текущ(?:ую|ая|ий|ее|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодняшн(?:юю|ий|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодня|сегодня|на\s+текущ(?:ий|ую)\s+момент|today|as\s+of\s+today|current\s+date|as\s+of\s+current\s+date)/iu.test(String(text ?? ""));
|
||||||
|
}
|
||||||
function hasOpenItemsHint(text) {
|
function hasOpenItemsHint(text) {
|
||||||
return /(?:open\s+items|unclosed\s+items|хвост|висят|незакрыт|не\s+закрыт|открыт|долг|задолж|позиц)/iu.test(String(text ?? ""));
|
return /(?:open\s+items|unclosed\s+items|хвост|висят|незакрыт|не\s+закрыт|открыт|долг|задолж|позиц)/iu.test(String(text ?? ""));
|
||||||
}
|
}
|
||||||
|
|
@ -358,7 +361,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||||
reasons.push("as_of_date_from_followup_context");
|
reasons.push("as_of_date_from_followup_context");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sameDateRequested && !hasExplicitPeriodLiteral(userMessage)) {
|
if (!sameDateRequested && !hasExplicitPeriodLiteral(userMessage) && !hasExplicitCurrentDateHint(userMessage)) {
|
||||||
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
||||||
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
||||||
const todayIso = new Date().toISOString().slice(0, 10);
|
const todayIso = new Date().toISOString().slice(0, 10);
|
||||||
|
|
@ -401,7 +404,10 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||||
reasons.push("as_of_date_from_followup_context");
|
reasons.push("as_of_date_from_followup_context");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sameDateRequested && hasFollowupSignalForConfirmed && !hasExplicitPeriodLiteral(userMessage)) {
|
if (!sameDateRequested &&
|
||||||
|
hasFollowupSignalForConfirmed &&
|
||||||
|
!hasExplicitPeriodLiteral(userMessage) &&
|
||||||
|
!hasExplicitCurrentDateHint(userMessage)) {
|
||||||
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
||||||
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
||||||
const todayIso = new Date().toISOString().slice(0, 10);
|
const todayIso = new Date().toISOString().slice(0, 10);
|
||||||
|
|
@ -433,6 +439,12 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||||
}
|
}
|
||||||
const hasFollowupSignal = hasAddressFollowupContextSignal(userMessage);
|
const hasFollowupSignal = hasAddressFollowupContextSignal(userMessage);
|
||||||
const hasExplicitPeriodInMessage = hasExplicitPeriodLiteral(userMessage);
|
const hasExplicitPeriodInMessage = hasExplicitPeriodLiteral(userMessage);
|
||||||
|
const hasExplicitCurrentDateInMessage = hasExplicitCurrentDateHint(userMessage);
|
||||||
|
const asOfPrimaryIntent = intent === "account_balance_snapshot" ||
|
||||||
|
intent === "documents_forming_balance" ||
|
||||||
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date" ||
|
||||||
|
intent === "vat_payable_confirmed_as_of_date";
|
||||||
const currentHasPeriod = hasExplicitPeriodWindow(merged);
|
const currentHasPeriod = hasExplicitPeriodWindow(merged);
|
||||||
const previousHasPeriod = hasExplicitPeriodWindow(previous);
|
const previousHasPeriod = hasExplicitPeriodWindow(previous);
|
||||||
if ((intent === "vat_payable_forecast" || intent === "vat_liability_confirmed_for_tax_period") &&
|
if ((intent === "vat_payable_forecast" || intent === "vat_liability_confirmed_for_tax_period") &&
|
||||||
|
|
@ -453,7 +465,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||||
reasons.push("period_from_followup_context");
|
reasons.push("period_from_followup_context");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!currentHasPeriod && previousHasPeriod && hasFollowupSignal) {
|
if (!currentHasPeriod && previousHasPeriod && hasFollowupSignal && !(asOfPrimaryIntent && hasExplicitCurrentDateInMessage)) {
|
||||||
if (previousPeriodFrom) {
|
if (previousPeriodFrom) {
|
||||||
merged.period_from = previousPeriodFrom;
|
merged.period_from = previousPeriodFrom;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1044,6 +1044,20 @@ function countTokens(text) {
|
||||||
function hasPeriodLiteral(text) {
|
function hasPeriodLiteral(text) {
|
||||||
return /\b(20\d{2}(?:[-/.](?:0[1-9]|1[0-2]))?)\b/.test(text);
|
return /\b(20\d{2}(?:[-/.](?:0[1-9]|1[0-2]))?)\b/.test(text);
|
||||||
}
|
}
|
||||||
|
function hasShortNamedPeriodFollowupLiteral(text) {
|
||||||
|
const normalized = compactWhitespace(String(text ?? "").toLowerCase());
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /^(?:(?:\u0430|a|\u0438|i)\s+)?(?:\u043d\u0430|\u0437\u0430|\u043f\u043e|na|za|for)\s+(?:(?:\u044f\u043d\u0432(?:\u0430\u0440)?|\u0444\u0435\u0432(?:\u0440\u0430\u043b)?|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440(?:\u0435\u043b)?|\u043c\u0430(?:\u0439|\u044f)|\u0438\u044e\u043d(?:\u044c)?|\u0438\u044e\u043b(?:\u044c)?|\u0430\u0432\u0433(?:\u0443\u0441\u0442)?|\u0441\u0435\u043d\u0442(?:\u044f\u0431\u0440)?|\u043e\u043a\u0442(?:\u044f\u0431\u0440)?|\u043d\u043e\u044f(?:\u0431\u0440)?|\u0434\u0435\u043a(?:\u0430\u0431\u0440)?)(?:[\u0430-\u044f\u0451]*)?|q[1-4]|(?:[1-4]|[ivx]{1,4})\s*(?:-?\u0439)?\s*\u043a\u0432(?:\.|\u0430\u0440\u0442(?:\u0430\u043b(?:\u0430|\u0435|\u0443|\u043e\u043c)?)?)?|(?:20\d{2})\s*(?:\u0433(?:\.|\u043e\u0434(?:\u0430|\u0443|\u043e\u043c)?)?))(?=$|[\s,.;:!?])/iu.test(normalized);
|
||||||
|
}
|
||||||
|
function hasShortCurrentDateFollowupLiteral(text) {
|
||||||
|
const normalized = compactWhitespace(String(text ?? "").toLowerCase());
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /^(?:(?:\u0430|a|\u0438|i)\s+)?(?:(?:\u043d\u0430|na)\s+)?(?:(?:\u0442\u0435\u043a\u0443\u0449(?:[\u0430-\u044f\u0451]+)?\s+\u0434\u0430\u0442(?:[\u0430-\u044f\u0451]+)?|(?:\u043d\u0430\s+)?\u0441\u0435\u0433\u043e\u0434\u043d(?:\u044f|\u0435\u0448\u043d(?:[\u0430-\u044f\u0451]+)?\s+\u0434\u0430\u0442(?:[\u0430-\u044f\u0451]+)?)|(?:\u043d\u0430\s+)?\u0442\u0435\u043a\u0443\u0449(?:[\u0430-\u044f\u0451]+)?\s+\u043c\u043e\u043c\u0435\u043d\u0442|today|current\s+date|current\s+moment|now)|(?:\u043f\u043e\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e\s+\u043d\u0430|as\s+of)\s+(?:\u0441\u0435\u0433\u043e\u0434\u043d\u044f|today|current\s+date))(?=$|[\s,.;:!?])/iu.test(normalized);
|
||||||
|
}
|
||||||
function hasStandaloneAddressTopicSignal(text) {
|
function hasStandaloneAddressTopicSignal(text) {
|
||||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -2594,6 +2608,11 @@ function hasAddressFollowupContextSignal(userMessage) {
|
||||||
if (shortVatCue) {
|
if (shortVatCue) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
const shortCurrentDateCue = shortFollowup &&
|
||||||
|
samples.some((sample) => hasShortCurrentDateFollowupLiteral(sample));
|
||||||
|
if (shortCurrentDateCue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (shortFollowup && hasAny(/^(?:а|a|и|i)\s+(?:нам\s+)?кто(?=$|[\s,.;:!?])/iu)) {
|
if (shortFollowup && hasAny(/^(?:а|a|и|i)\s+(?:нам\s+)?кто(?=$|[\s,.;:!?])/iu)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -2646,6 +2665,9 @@ function hasAddressFollowupContextSignal(userMessage) {
|
||||||
if (shortFollowup && samples.some((sample) => hasPeriodLiteral(sample))) {
|
if (shortFollowup && samples.some((sample) => hasPeriodLiteral(sample))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (shortFollowup && samples.some((sample) => hasShortNamedPeriodFollowupLiteral(sample))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function hasShortDebtMirrorFollowupSignal(userMessage) {
|
function hasShortDebtMirrorFollowupSignal(userMessage) {
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,11 @@ function toIsoDate(year: number, month: number, day: number): string | null {
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractAsOfDate(text: string): string | undefined {
|
function extractAsOfDate(text: string): string | undefined {
|
||||||
if (/\b(сегодня|на\s+сегодня|today|as\s+of\s+today)\b/i.test(text)) {
|
if (
|
||||||
|
/\b(сегодня|на\s+сегодня|на\s+текущ(?:ую|ая|ий|ее|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодняшн(?:юю|ий|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+текущ(?:ий|ую)\s+момент|today|as\s+of\s+today|current\s+date|as\s+of\s+current\s+date)\b/i.test(
|
||||||
|
text
|
||||||
|
)
|
||||||
|
) {
|
||||||
return new Date().toISOString().slice(0, 10);
|
return new Date().toISOString().slice(0, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { AddressIntentResolution } from "../types/addressQuery";
|
import type { AddressIntentResolution } from "../types/addressQuery";
|
||||||
|
|
||||||
const RECEIVABLES_STRONG = [
|
const RECEIVABLES_STRONG = [
|
||||||
"кто должен нам",
|
"кто должен нам",
|
||||||
|
|
@ -608,7 +608,7 @@ function hasVatLiabilityConfirmedTaxPeriodSignal(text: string): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const hasPaymentCue =
|
const hasPaymentCue =
|
||||||
/(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить)/iu.test(
|
/(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить|мы\s+должн[аы]?|должн[аы]?\s+мы)/iu.test(
|
||||||
text
|
text
|
||||||
);
|
);
|
||||||
if (!hasPaymentCue) {
|
if (!hasPaymentCue) {
|
||||||
|
|
@ -646,7 +646,7 @@ function hasVatPayableConfirmedSignal(text: string): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const hasPaymentCue =
|
const hasPaymentCue =
|
||||||
/(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить)/iu.test(
|
/(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить|мы\s+должн[аы]?|должн[аы]?\s+мы)/iu.test(
|
||||||
text
|
text
|
||||||
);
|
);
|
||||||
if (!hasPaymentCue) {
|
if (!hasPaymentCue) {
|
||||||
|
|
|
||||||
|
|
@ -3620,7 +3620,16 @@ export class AddressQueryService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filteredRows.length === 0) {
|
const allowConfirmedAsOfZeroSnapshot =
|
||||||
|
filteredRows.length === 0 &&
|
||||||
|
(composeIntent === "vat_payable_confirmed_as_of_date" ||
|
||||||
|
composeIntent === "payables_confirmed_as_of_date" ||
|
||||||
|
composeIntent === "receivables_confirmed_as_of_date") &&
|
||||||
|
(stageStatus === "no_raw_rows" || stageStatus === "materialized_but_filtered_out_by_recipe") &&
|
||||||
|
!toNonEmptyFilterValue(filters.extracted_filters.counterparty) &&
|
||||||
|
!toNonEmptyFilterValue(filters.extracted_filters.contract) &&
|
||||||
|
!toNonEmptyFilterValue(filters.extracted_filters.document_ref);
|
||||||
|
if (filteredRows.length === 0 && !allowConfirmedAsOfZeroSnapshot) {
|
||||||
const hadBaseRows = normalizedRows.length > 0 || mcp.fetched_rows > 0;
|
const hadBaseRows = normalizedRows.length > 0 || mcp.fetched_rows > 0;
|
||||||
const hadAnchorMatchedRows = filterByAnchors.length > 0;
|
const hadAnchorMatchedRows = filterByAnchors.length > 0;
|
||||||
const isVisibilityGapCandidate =
|
const isVisibilityGapCandidate =
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,12 @@ function hasExplicitPeriodLiteral(text: string): boolean {
|
||||||
return /\b(?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?\b/.test(String(text ?? ""));
|
return /\b(?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?\b/.test(String(text ?? ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasExplicitCurrentDateHint(text: string): boolean {
|
||||||
|
return /(?:на\s+текущ(?:ую|ая|ий|ее|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодняшн(?:юю|ий|ей|ем|его)\s+дат(?:у|а|е|ой|ою)|на\s+сегодня|сегодня|на\s+текущ(?:ий|ую)\s+момент|today|as\s+of\s+today|current\s+date|as\s+of\s+current\s+date)/iu.test(
|
||||||
|
String(text ?? "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function hasOpenItemsHint(text: string): boolean {
|
function hasOpenItemsHint(text: string): boolean {
|
||||||
return /(?:open\s+items|unclosed\s+items|хвост|висят|незакрыт|не\s+закрыт|открыт|долг|задолж|позиц)/iu.test(String(text ?? ""));
|
return /(?:open\s+items|unclosed\s+items|хвост|висят|незакрыт|не\s+закрыт|открыт|долг|задолж|позиц)/iu.test(String(text ?? ""));
|
||||||
}
|
}
|
||||||
|
|
@ -449,7 +455,7 @@ function mergeFollowupFilters(
|
||||||
reasons.push("as_of_date_from_followup_context");
|
reasons.push("as_of_date_from_followup_context");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sameDateRequested && !hasExplicitPeriodLiteral(userMessage)) {
|
if (!sameDateRequested && !hasExplicitPeriodLiteral(userMessage) && !hasExplicitCurrentDateHint(userMessage)) {
|
||||||
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
||||||
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
||||||
const todayIso = new Date().toISOString().slice(0, 10);
|
const todayIso = new Date().toISOString().slice(0, 10);
|
||||||
|
|
@ -498,7 +504,12 @@ function mergeFollowupFilters(
|
||||||
reasons.push("as_of_date_from_followup_context");
|
reasons.push("as_of_date_from_followup_context");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sameDateRequested && hasFollowupSignalForConfirmed && !hasExplicitPeriodLiteral(userMessage)) {
|
if (
|
||||||
|
!sameDateRequested &&
|
||||||
|
hasFollowupSignalForConfirmed &&
|
||||||
|
!hasExplicitPeriodLiteral(userMessage) &&
|
||||||
|
!hasExplicitCurrentDateHint(userMessage)
|
||||||
|
) {
|
||||||
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
||||||
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
||||||
const todayIso = new Date().toISOString().slice(0, 10);
|
const todayIso = new Date().toISOString().slice(0, 10);
|
||||||
|
|
@ -535,6 +546,13 @@ function mergeFollowupFilters(
|
||||||
|
|
||||||
const hasFollowupSignal = hasAddressFollowupContextSignal(userMessage);
|
const hasFollowupSignal = hasAddressFollowupContextSignal(userMessage);
|
||||||
const hasExplicitPeriodInMessage = hasExplicitPeriodLiteral(userMessage);
|
const hasExplicitPeriodInMessage = hasExplicitPeriodLiteral(userMessage);
|
||||||
|
const hasExplicitCurrentDateInMessage = hasExplicitCurrentDateHint(userMessage);
|
||||||
|
const asOfPrimaryIntent =
|
||||||
|
intent === "account_balance_snapshot" ||
|
||||||
|
intent === "documents_forming_balance" ||
|
||||||
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date" ||
|
||||||
|
intent === "vat_payable_confirmed_as_of_date";
|
||||||
const currentHasPeriod = hasExplicitPeriodWindow(merged);
|
const currentHasPeriod = hasExplicitPeriodWindow(merged);
|
||||||
const previousHasPeriod = hasExplicitPeriodWindow(previous);
|
const previousHasPeriod = hasExplicitPeriodWindow(previous);
|
||||||
|
|
||||||
|
|
@ -559,7 +577,7 @@ function mergeFollowupFilters(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentHasPeriod && previousHasPeriod && hasFollowupSignal) {
|
if (!currentHasPeriod && previousHasPeriod && hasFollowupSignal && !(asOfPrimaryIntent && hasExplicitCurrentDateInMessage)) {
|
||||||
if (previousPeriodFrom) {
|
if (previousPeriodFrom) {
|
||||||
merged.period_from = previousPeriodFrom;
|
merged.period_from = previousPeriodFrom;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -998,6 +998,20 @@ function countTokens(text) {
|
||||||
function hasPeriodLiteral(text) {
|
function hasPeriodLiteral(text) {
|
||||||
return /\b(20\d{2}(?:[-/.](?:0[1-9]|1[0-2]))?)\b/.test(text);
|
return /\b(20\d{2}(?:[-/.](?:0[1-9]|1[0-2]))?)\b/.test(text);
|
||||||
}
|
}
|
||||||
|
function hasShortNamedPeriodFollowupLiteral(text) {
|
||||||
|
const normalized = compactWhitespace(String(text ?? "").toLowerCase());
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /^(?:(?:\u0430|a|\u0438|i)\s+)?(?:\u043d\u0430|\u0437\u0430|\u043f\u043e|na|za|for)\s+(?:(?:\u044f\u043d\u0432(?:\u0430\u0440)?|\u0444\u0435\u0432(?:\u0440\u0430\u043b)?|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440(?:\u0435\u043b)?|\u043c\u0430(?:\u0439|\u044f)|\u0438\u044e\u043d(?:\u044c)?|\u0438\u044e\u043b(?:\u044c)?|\u0430\u0432\u0433(?:\u0443\u0441\u0442)?|\u0441\u0435\u043d\u0442(?:\u044f\u0431\u0440)?|\u043e\u043a\u0442(?:\u044f\u0431\u0440)?|\u043d\u043e\u044f(?:\u0431\u0440)?|\u0434\u0435\u043a(?:\u0430\u0431\u0440)?)(?:[\u0430-\u044f\u0451]*)?|q[1-4]|(?:[1-4]|[ivx]{1,4})\s*(?:-?\u0439)?\s*\u043a\u0432(?:\.|\u0430\u0440\u0442(?:\u0430\u043b(?:\u0430|\u0435|\u0443|\u043e\u043c)?)?)?|(?:20\d{2})\s*(?:\u0433(?:\.|\u043e\u0434(?:\u0430|\u0443|\u043e\u043c)?)?))(?=$|[\s,.;:!?])/iu.test(normalized);
|
||||||
|
}
|
||||||
|
function hasShortCurrentDateFollowupLiteral(text) {
|
||||||
|
const normalized = compactWhitespace(String(text ?? "").toLowerCase());
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /^(?:(?:\u0430|a|\u0438|i)\s+)?(?:(?:\u043d\u0430|na)\s+)?(?:(?:\u0442\u0435\u043a\u0443\u0449(?:[\u0430-\u044f\u0451]+)?\s+\u0434\u0430\u0442(?:[\u0430-\u044f\u0451]+)?|(?:\u043d\u0430\s+)?\u0441\u0435\u0433\u043e\u0434\u043d(?:\u044f|\u0435\u0448\u043d(?:[\u0430-\u044f\u0451]+)?\s+\u0434\u0430\u0442(?:[\u0430-\u044f\u0451]+)?)|(?:\u043d\u0430\s+)?\u0442\u0435\u043a\u0443\u0449(?:[\u0430-\u044f\u0451]+)?\s+\u043c\u043e\u043c\u0435\u043d\u0442|today|current\s+date|current\s+moment|now)|(?:\u043f\u043e\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e\s+\u043d\u0430|as\s+of)\s+(?:\u0441\u0435\u0433\u043e\u0434\u043d\u044f|today|current\s+date))(?=$|[\s,.;:!?])/iu.test(normalized);
|
||||||
|
}
|
||||||
function hasStandaloneAddressTopicSignal(text) {
|
function hasStandaloneAddressTopicSignal(text) {
|
||||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -2551,6 +2565,11 @@ function hasAddressFollowupContextSignal(userMessage) {
|
||||||
if (shortVatCue) {
|
if (shortVatCue) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
const shortCurrentDateCue = shortFollowup &&
|
||||||
|
samples.some((sample) => hasShortCurrentDateFollowupLiteral(sample));
|
||||||
|
if (shortCurrentDateCue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (shortFollowup && hasAny(/^(?:а|a|и|i)\s+(?:нам\s+)?кто(?=$|[\s,.;:!?])/iu)) {
|
if (shortFollowup && hasAny(/^(?:а|a|и|i)\s+(?:нам\s+)?кто(?=$|[\s,.;:!?])/iu)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -2603,6 +2622,9 @@ function hasAddressFollowupContextSignal(userMessage) {
|
||||||
if (shortFollowup && samples.some((sample) => hasPeriodLiteral(sample))) {
|
if (shortFollowup && samples.some((sample) => hasPeriodLiteral(sample))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (shortFollowup && samples.some((sample) => hasShortNamedPeriodFollowupLiteral(sample))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function hasShortDebtMirrorFollowupSignal(userMessage) {
|
function hasShortDebtMirrorFollowupSignal(userMessage) {
|
||||||
|
|
|
||||||
|
|
@ -2093,6 +2093,13 @@ describe("address intent resolver expansion (M2.3a)", () => {
|
||||||
expect(result.intent).toBe("contract_usage_and_value");
|
expect(result.intent).toBe("contract_usage_and_value");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("resolves VAT wording with debt-phrase as confirmed VAT payable intent", () => {
|
||||||
|
const result = resolveAddressIntent(
|
||||||
|
"\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"
|
||||||
|
);
|
||||||
|
expect(result.intent).toBe("vat_payable_confirmed_as_of_date");
|
||||||
|
expect(result.reasons).toContain("vat_payable_confirmed_signal_detected");
|
||||||
|
});
|
||||||
it("resolves contracts-by-counterparty intent from list wording", () => {
|
it("resolves contracts-by-counterparty intent from list wording", () => {
|
||||||
const result = resolveAddressIntent("покажи договора все по жуковке 51");
|
const result = resolveAddressIntent("покажи договора все по жуковке 51");
|
||||||
expect(result.intent).toBe("list_contracts_by_counterparty");
|
expect(result.intent).toBe("list_contracts_by_counterparty");
|
||||||
|
|
@ -3335,6 +3342,27 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
|
||||||
expect(result?.debug.mcp_call_status).not.toBe("skipped");
|
expect(result?.debug.mcp_call_status).not.toBe("skipped");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns factual confirmed VAT snapshot instead of partial when payable rows are absent", async () => {
|
||||||
|
const service = new AddressQueryService();
|
||||||
|
const result = await service.tryHandle("скок ндс платить надо на март 2020");
|
||||||
|
expect(result?.handled).toBe(true);
|
||||||
|
expect(result?.debug.detected_intent).toBe("vat_payable_confirmed_as_of_date");
|
||||||
|
expect(["FACTUAL_LIST", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
|
||||||
|
expect(result?.reply_type).not.toBe("partial_coverage");
|
||||||
|
expect(result?.debug.result_mode).toBe("confirmed_balance");
|
||||||
|
expect(result?.debug.balance_confirmed).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps mixed VAT + debt wording in VAT lane (not payables/contracts)", async () => {
|
||||||
|
const service = new AddressQueryService();
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
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.result_mode).toBe("confirmed_balance");
|
||||||
|
});
|
||||||
it("routes contracts-by-counterparty intent into dedicated catalog recipe", async () => {
|
it("routes contracts-by-counterparty intent into dedicated catalog recipe", async () => {
|
||||||
const service = new AddressQueryService();
|
const service = new AddressQueryService();
|
||||||
const result = await service.tryHandle("покажи договора все по жуковке 51");
|
const result = await service.tryHandle("покажи договора все по жуковке 51");
|
||||||
|
|
@ -3755,6 +3783,27 @@ describe("address decompose stage follow-up carryover", () => {
|
||||||
expect(result?.baseReasons).toContain("intent_adjusted_to_vat_followup_context");
|
expect(result?.baseReasons).toContain("intent_adjusted_to_vat_followup_context");
|
||||||
expect(result?.baseReasons).toContain("as_of_date_from_followup_context");
|
expect(result?.baseReasons).toContain("as_of_date_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",
|
||||||
|
previous_filters: {
|
||||||
|
period_from: "2016-03-01",
|
||||||
|
period_to: "2016-03-31",
|
||||||
|
as_of_date: "2016-03-31"
|
||||||
|
},
|
||||||
|
previous_anchor_type: "unknown",
|
||||||
|
previous_anchor_value: null
|
||||||
|
});
|
||||||
|
const todayIso = new Date().toISOString().slice(0, 10);
|
||||||
|
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(todayIso);
|
||||||
|
expect(result?.filters.extracted_filters.as_of_date).not.toBe("2016-03-31");
|
||||||
|
expect(result?.filters.extracted_filters.period_from).toBeUndefined();
|
||||||
|
expect(result?.filters.extracted_filters.period_to).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("address recipe catalog counterparty filtering", () => {
|
describe("address recipe catalog counterparty filtering", () => {
|
||||||
|
|
@ -4033,3 +4082,4 @@ describe("address recipe catalog counterparty filtering", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1415,6 +1415,187 @@ describe("assistant address follow-up carryover", () => {
|
||||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps month-only VAT follow-up phrase in address lane", async () => {
|
||||||
|
const calls: Array<{ message: string; options?: any }> = [];
|
||||||
|
const firstMessage = "\u0441\u043a\u043e\u043a \u043d\u0434\u0441 \u043d\u0430\u0434\u043e \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c \u0432 \u043d\u0430\u043b\u043e\u0433\u043e\u0432\u0443\u044e \u043d\u0430 \u0444\u0435\u0432\u0440\u0430\u043b\u044c 2017";
|
||||||
|
const followupMessage = "\u0430 \u043d\u0430 \u043c\u0430\u0440\u0442";
|
||||||
|
|
||||||
|
const firstVatResult = buildAddressLaneResult({
|
||||||
|
debug: {
|
||||||
|
...buildAddressLaneResult().debug,
|
||||||
|
detected_intent: "vat_liability_confirmed_for_tax_period",
|
||||||
|
extracted_filters: {
|
||||||
|
sort: "period_desc",
|
||||||
|
limit: 20,
|
||||||
|
period_from: "2017-01-01",
|
||||||
|
period_to: "2017-03-31"
|
||||||
|
},
|
||||||
|
selected_recipe: "address_vat_liability_confirmed_tax_period_v1",
|
||||||
|
response_type: "FACTUAL_SUMMARY",
|
||||||
|
requested_result_mode: "confirmed_balance",
|
||||||
|
result_mode: "confirmed_balance",
|
||||||
|
balance_confirmed: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const followupVatResult = buildAddressLaneResult({
|
||||||
|
debug: {
|
||||||
|
...buildAddressLaneResult().debug,
|
||||||
|
detected_intent: "vat_liability_confirmed_for_tax_period",
|
||||||
|
extracted_filters: {
|
||||||
|
sort: "period_desc",
|
||||||
|
limit: 20,
|
||||||
|
period_from: "2017-01-01",
|
||||||
|
period_to: "2017-03-31"
|
||||||
|
},
|
||||||
|
selected_recipe: "address_vat_liability_confirmed_tax_period_v1",
|
||||||
|
response_type: "FACTUAL_SUMMARY",
|
||||||
|
requested_result_mode: "confirmed_balance",
|
||||||
|
result_mode: "confirmed_balance",
|
||||||
|
balance_confirmed: true,
|
||||||
|
reasons: [
|
||||||
|
"address_action_detected",
|
||||||
|
"vat_liability_confirmed_tax_period_signal_detected",
|
||||||
|
"address_followup_context_applied"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const addressQueryService = {
|
||||||
|
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||||
|
calls.push({ message, options });
|
||||||
|
if (message === firstMessage) {
|
||||||
|
return firstVatResult;
|
||||||
|
}
|
||||||
|
if (!options?.followupContext) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return followupVatResult;
|
||||||
|
})
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const normalizerService = {
|
||||||
|
normalize: vi.fn(async () => ({
|
||||||
|
assistant_reply: "normalizer_fallback_should_not_be_used",
|
||||||
|
reply_type: "partial_coverage",
|
||||||
|
debug: {}
|
||||||
|
}))
|
||||||
|
} 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-vat-march-${Date.now()}`;
|
||||||
|
const first = await service.handleMessage({
|
||||||
|
session_id: sessionId,
|
||||||
|
user_message: firstMessage,
|
||||||
|
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: followupMessage,
|
||||||
|
useMock: true
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(second.ok).toBe(true);
|
||||||
|
expect(second.reply_type).toBe("factual");
|
||||||
|
expect(second.debug?.detected_intent).toBe("vat_liability_confirmed_for_tax_period");
|
||||||
|
expect(second.debug?.selected_recipe).toBe("address_vat_liability_confirmed_tax_period_v1");
|
||||||
|
expect(calls).toHaveLength(2);
|
||||||
|
expect(calls[1].message).toBe(followupMessage);
|
||||||
|
expect(calls[1].options?.followupContext?.previous_intent).toBe("vat_liability_confirmed_for_tax_period");
|
||||||
|
expect(calls[1].options?.followupContext?.previous_filters?.period_from).toBe("2017-01-01");
|
||||||
|
expect(calls[1].options?.followupContext?.previous_filters?.period_to).toBe("2017-03-31");
|
||||||
|
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps 'a na tekushuyu datu' VAT follow-up in address lane", async () => {
|
||||||
|
const calls: Array<{ message: string; options?: any }> = [];
|
||||||
|
const firstMessage = "\u0441\u043a\u043e\u043a \u043d\u0430\u0434\u043e \u043d\u0434\u0441 \u043f\u043b\u0430\u0442\u0438\u0442\u044c \u0441 \u0430\u043f\u0440\u0435\u043b\u0435 2017";
|
||||||
|
const followupMessage = "\u0430 \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u0434\u0430\u0442\u0443";
|
||||||
|
|
||||||
|
const vatAsOfResult = buildAddressLaneResult({
|
||||||
|
debug: {
|
||||||
|
...buildAddressLaneResult().debug,
|
||||||
|
detected_intent: "vat_payable_confirmed_as_of_date",
|
||||||
|
extracted_filters: {
|
||||||
|
sort: "period_desc",
|
||||||
|
limit: 20,
|
||||||
|
period_from: "2017-04-01",
|
||||||
|
period_to: "2017-04-30",
|
||||||
|
as_of_date: "2017-04-30"
|
||||||
|
},
|
||||||
|
selected_recipe: "address_vat_payable_confirmed_as_of_date_v1",
|
||||||
|
response_type: "FACTUAL_SUMMARY",
|
||||||
|
requested_result_mode: "confirmed_balance",
|
||||||
|
result_mode: "confirmed_balance",
|
||||||
|
balance_confirmed: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const addressQueryService = {
|
||||||
|
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||||
|
calls.push({ message, options });
|
||||||
|
if (message === firstMessage) {
|
||||||
|
return vatAsOfResult;
|
||||||
|
}
|
||||||
|
if (!options?.followupContext) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return vatAsOfResult;
|
||||||
|
})
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const normalizerService = {
|
||||||
|
normalize: vi.fn(async () => ({
|
||||||
|
assistant_reply: "normalizer_fallback_should_not_be_used",
|
||||||
|
reply_type: "partial_coverage",
|
||||||
|
debug: {}
|
||||||
|
}))
|
||||||
|
} 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-vat-current-date-${Date.now()}`;
|
||||||
|
const first = await service.handleMessage({
|
||||||
|
session_id: sessionId,
|
||||||
|
user_message: firstMessage,
|
||||||
|
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: followupMessage,
|
||||||
|
useMock: true
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(second.ok).toBe(true);
|
||||||
|
expect(second.reply_type).toBe("factual");
|
||||||
|
expect(calls).toHaveLength(2);
|
||||||
|
expect(calls[1].message).toBe(followupMessage);
|
||||||
|
expect(calls[1].options?.followupContext?.previous_intent).toBe("vat_payable_confirmed_as_of_date");
|
||||||
|
expect(calls[1].options?.followupContext?.previous_filters?.as_of_date).toBe("2017-04-30");
|
||||||
|
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("passes active organization scope into address lane follow-up context", async () => {
|
it("passes active organization scope into address lane follow-up context", async () => {
|
||||||
const calls: Array<{ message: string; options?: any }> = [];
|
const calls: Array<{ message: string; options?: any }> = [];
|
||||||
const addressQueryService = {
|
const addressQueryService = {
|
||||||
|
|
|
||||||
|
|
@ -558,6 +558,40 @@ describe("assistant orchestration contract", () => {
|
||||||
expect(decision.livingReason).toBe("address_lane_triggered");
|
expect(decision.livingReason).toBe("address_lane_triggered");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps 'a na tekushuyu datu' follow-up in address lane when previous VAT context exists", () => {
|
||||||
|
const decision = resolveAssistantOrchestrationDecision({
|
||||||
|
rawUserMessage: "а на текущую дату",
|
||||||
|
effectiveAddressUserMessage: "а на текущую дату",
|
||||||
|
followupContext: {
|
||||||
|
previous_intent: "vat_payable_confirmed_as_of_date",
|
||||||
|
previous_filters: {
|
||||||
|
period_from: "2016-03-01",
|
||||||
|
period_to: "2016-03-31",
|
||||||
|
as_of_date: "2016-03-31"
|
||||||
|
},
|
||||||
|
previous_anchor_type: "unknown",
|
||||||
|
previous_anchor_value: null
|
||||||
|
},
|
||||||
|
llmPreDecomposeMeta: {
|
||||||
|
applied: false,
|
||||||
|
reason: "normalized_fragment_rejected_semantic_guard",
|
||||||
|
llmCanonicalCandidateDetected: true,
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
mode_confidence: "low",
|
||||||
|
intent: "unknown",
|
||||||
|
intent_confidence: "low"
|
||||||
|
}
|
||||||
|
} as any,
|
||||||
|
useMock: false
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decision.runAddressLane).toBe(true);
|
||||||
|
expect(decision.toolGateDecision).toBe("run_address_lane");
|
||||||
|
expect(decision.livingMode).toBe("address_data");
|
||||||
|
expect(decision.livingReason).toBe("address_lane_triggered");
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps explicit address-mode unknown-intent data query in address lane", () => {
|
it("keeps explicit address-mode unknown-intent data query in address lane", () => {
|
||||||
const decision = resolveAssistantOrchestrationDecision({
|
const decision = resolveAssistantOrchestrationDecision({
|
||||||
rawUserMessage:
|
rawUserMessage:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue