ДОМЕНЫ - ВОПРОСЫ - Исправить обработку коротких debt follow-up и защиту от диагностических LLM rewrite

This commit is contained in:
2026-04-12 22:34:59 +03:00
parent 040a55aaea
commit 98872c2f11
35 changed files with 1745 additions and 195 deletions
@@ -8,7 +8,8 @@ const COMPUTE_EXACT_INTENTS = new Set([
"account_balance_snapshot",
"documents_forming_balance",
"payables_confirmed_as_of_date",
"receivables_confirmed_as_of_date"
"receivables_confirmed_as_of_date",
"vat_payable_confirmed_as_of_date"
]);
const NAVIGATION_INTENTS = new Set([
"list_documents_by_counterparty",
@@ -39,6 +40,9 @@ function defaultCapabilityId(intent) {
if (intent === "receivables_confirmed_as_of_date") {
return "confirmed_receivables_as_of_date";
}
if (intent === "vat_payable_confirmed_as_of_date") {
return "confirmed_vat_payable_as_of_date";
}
if (intent === "list_payables_counterparties") {
return "payables_candidates_list";
}
@@ -74,6 +78,14 @@ function resolveCapabilityEnabled(intent) {
: "receivables_confirmed_route_disabled_by_flag"
};
}
if (intent === "vat_payable_confirmed_as_of_date") {
return {
enabled: config_1.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1,
reason: config_1.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1
? "vat_payable_confirmed_route_enabled"
: "vat_payable_confirmed_route_disabled_by_flag"
};
}
if (intent === "list_payables_counterparties") {
return {
enabled: config_1.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
@@ -574,6 +574,43 @@ function isLowQualityCounterpartyAnchorValue(rawValue) {
if (questionCue && (rankingCue || paymentCue)) {
return true;
}
const hasTemporalCue = /(?:по\s+состоянию|на\s+дат|на\s+конец|за\s+(?:период|месяц|год|квартал)|\b(?:19|20)\d{2}\b|\bянвар|феврал|март|апрел|май|июн|июл|август|сентябр|октябр|ноябр|декабр)/iu.test(value);
const hasGenericEntityCue = /(?:компан|организац|контрагент|поставщик|клиент|покупател|дебитор|кредитор|counterparty|company|supplier|customer)/iu.test(value);
if (hasTemporalCue && hasGenericEntityCue) {
return true;
}
const lowQualityTimeTokens = new Set([
"по",
"состоянию",
"состояние",
"на",
"дату",
"дата",
"конец",
"период",
"месяц",
"году",
"год",
"квартал",
"январь",
"февраль",
"март",
"апрель",
"май",
"июнь",
"июль",
"август",
"сентябрь",
"октябрь",
"ноябрь",
"декабрь"
]);
const meaningfulNonTemporalTokens = tokens.filter((token) => isLikelyCounterpartyToken(token) &&
!lowQualityTimeTokens.has(token) &&
!/^(?:19|20)\d{2}$/.test(token));
if (meaningfulNonTemporalTokens.length === 0 && hasTemporalCue) {
return true;
}
const meaningfulTokens = tokens.filter((token) => isLikelyCounterpartyToken(token));
return meaningfulTokens.length === 0;
}
@@ -751,6 +788,9 @@ function requiredFiltersByIntent(intent) {
if (intent === "receivables_confirmed_as_of_date") {
return ["as_of_date"];
}
if (intent === "vat_payable_confirmed_as_of_date") {
return ["as_of_date"];
}
if (intent === "list_documents_by_counterparty" ||
intent === "bank_operations_by_counterparty" ||
intent === "list_contracts_by_counterparty") {
@@ -765,7 +805,8 @@ function usesAsOfPrimaryWindow(intent) {
return (intent === "open_items_by_counterparty_or_contract" ||
intent === "list_open_contracts" ||
intent === "payables_confirmed_as_of_date" ||
intent === "receivables_confirmed_as_of_date");
intent === "receivables_confirmed_as_of_date" ||
intent === "vat_payable_confirmed_as_of_date");
}
function extractAddressFilters(userMessage, intent) {
const rawText = String(userMessage ?? "").trim();
@@ -928,7 +969,8 @@ function extractAddressFilters(userMessage, intent) {
if ((intent === "account_balance_snapshot" ||
intent === "documents_forming_balance" ||
intent === "payables_confirmed_as_of_date" ||
intent === "receivables_confirmed_as_of_date") &&
intent === "receivables_confirmed_as_of_date" ||
intent === "vat_payable_confirmed_as_of_date") &&
!filters.as_of_date) {
if (filters.period_to) {
filters.as_of_date = filters.period_to;
@@ -535,10 +535,20 @@ function hasAccountBalanceSignal(text) {
}
function hasForecastTaxSignal(text) {
const hasForecastLexeme = /(?:прогноз|forecast|план(?:\s+платежа|\s+оплаты)?|прикин(?:уть|ем|у|ь|ул|ули|усь|усь))/iu.test(text);
const hasVatLexeme = /(?:ндс|vat)/iu.test(text);
const hasTaxLexeme = /(?:ндс|vat|налог)/iu.test(text);
const hasVatPayableEstimatePattern = /(?:(?:сколько|скока|скок).{0,48}(?:ндс|vat).{0,48}(?:надо|нужно|к\s+уплате|заплатить|уплатить|платеж|платежа|платежей|платежку)|(?:ндс|vat).{0,48}(?:к\s+уплате|надо|нужно|заплатить|уплатить)|(?:сколько|скока|скок).{0,32}(?:надо|нужно).{0,32}(?:заплатить|уплатить).{0,32}(?:ндс|vat))/iu.test(text);
return (hasForecastLexeme && hasTaxLexeme) || (hasVatLexeme && hasVatPayableEstimatePattern);
return hasForecastLexeme && hasTaxLexeme;
}
function hasVatPayableConfirmedSignal(text) {
const hasVatLexeme = /(?:ндс|vat)/iu.test(text);
if (!hasVatLexeme) {
return false;
}
const hasPaymentCue = /(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить)/iu.test(text);
if (!hasPaymentCue) {
return false;
}
const hasDateOrPeriodCue = /(?:на\s+дат|по\s+состоянию|на\s+конец|за\s+(?:\d{4}|январ|феврал|март|апрел|май|июн|июл|август|сентябр|октябр|ноябр|декабр)|квартал|месяц|год|период|\b\d{4}[./-]\d{2}[./-]\d{2}\b)/iu.test(text);
return hasDateOrPeriodCue || /(?:сколько|скока|скок)/iu.test(text);
}
function hasPeriodCoverageProfileSignal(text) {
if (hasAny(text, PERIOD_COVERAGE_PROFILE_HINTS)) {
@@ -862,7 +872,7 @@ function hasSupplierTailRiskSignal(text) {
return hasSupplier && hasTail && (hasRisk || hasPeriodCue);
}
function hasPayablesDebtLifecycleSignal(text) {
const hasOweSignal = /(?:кому\s+мы\s+должны|мы\s+должны|кому\s+должны|должн(?:ы|а|о)\s+(?:заплат|оплат|перечис)|к\s+оплате|на\s+оплату|who\s+we\s+owe|owe\s+to|payables?|кредитор(?:ск)?)/iu.test(text);
const hasOweSignal = /(?:кому\s+мы\s+долж(?:ен|ны|эны|эна|эно)?|мы\s+долж(?:ен|ны|эны|эна|эно)?|кому\s+долж(?:ен|ны|эны|эна|эно)?|долж[нэ](?:ы|а|о)?\s+(?:заплат|оплат|перечис)|к\s+оплате|на\s+оплату|who\s+we\s+owe|owe\s+to|payables?|кредитор(?:[а-яё]{0,6})?)/iu.test(text);
if (!hasOweSignal) {
return false;
}
@@ -874,7 +884,7 @@ function hasPayablesDebtLifecycleSignal(text) {
return true;
}
function hasReceivablesDebtLifecycleSignal(text) {
const hasOweUsSignal = /(?:кто\s+нам\s+долж(?:ен|ны)?|кто\s+долж(?:ен|ны)?\s+нам|нам\s+долж(?:ен|ны)|должник(?:и|ов|а)?|дебитор(?:ы|ов|ск)?|задолж|долг(?:и|ов|а|у)?|к\s+получению|на\s+поступление|к\s+взысканию|who\s+owes\s+us|receivables?)/iu.test(text);
const hasOweUsSignal = /(?:кто\s+нам\s+долж(?:ен|ны|эны|эна|эно)?|кто\s+долж(?:ен|ны|эны|эна|эно)?\s+нам|нам\s+долж(?:ен|ны|эны|эна|эно)?|должник(?:[а-яё]{0,6})?|дебитор(?:[а-яё]{0,6})?|дебиторск(?:[а-яё]{0,6})?|задолж|долг(?:и|ов|а|у)?|к\s+получению|на\s+поступление|к\s+взысканию|who\s+owes\s+us|receivables?)/iu.test(text);
if (!hasOweUsSignal) {
return false;
}
@@ -1258,6 +1268,13 @@ function resolveAddressIntent(userMessage) {
reasons: ["forecast_tax_signal_detected"]
};
}
if (hasVatPayableConfirmedSignal(text)) {
return {
intent: "vat_payable_confirmed_as_of_date",
confidence: "high",
reasons: ["vat_payable_confirmed_signal_detected"]
};
}
if (hasAny(text, RECEIVABLES_STRONG)) {
const receivablesDebtLifecycleSignal = hasReceivablesDebtLifecycleSignal(text);
const reasons = ["receivables_signal_detected"];
@@ -29,6 +29,7 @@ const RESULT_SET_TYPE_BY_INTENT = {
supplier_payouts_profile: "counterparty_list",
list_payables_counterparties: "counterparty_list",
payables_confirmed_as_of_date: "balance_snapshot",
vat_payable_confirmed_as_of_date: "balance_snapshot",
receivables_confirmed_as_of_date: "balance_snapshot",
list_receivables_counterparties: "counterparty_list",
list_contracts_by_counterparty: "contract_list",
+52 -21
View File
@@ -648,7 +648,8 @@ function isConfirmedBalanceIntent(intent) {
return (intent === "account_balance_snapshot" ||
intent === "documents_forming_balance" ||
intent === "payables_confirmed_as_of_date" ||
intent === "receivables_confirmed_as_of_date");
intent === "receivables_confirmed_as_of_date" ||
intent === "vat_payable_confirmed_as_of_date");
}
function resolveAsOfDateBasis(filters) {
const asOfDate = normalizeAnalysisDateHint(filters.as_of_date);
@@ -818,7 +819,7 @@ function enforceStrictAccountScopeForIntent(plan, intent) {
account_scope_mode: "strict"
};
}
function resolveExecutionFiltersForPayablesConfirmedBalance(filters, analysisDate) {
function resolveExecutionFiltersForConfirmedBalance(filters, analysisDate) {
const explicitAsOf = normalizeAnalysisDateHint(filters.as_of_date);
const periodTo = normalizeAnalysisDateHint(filters.period_to);
const derivedAsOf = explicitAsOf ?? periodTo ?? analysisDate ?? null;
@@ -1302,6 +1303,9 @@ function buildLimitedOffers(input) {
else if (input.intent === "receivables_confirmed_as_of_date") {
offers.push("показать подтвержденный реестр открытой дебиторской задолженности на дату среза по 62/76");
}
else if (input.intent === "vat_payable_confirmed_as_of_date") {
offers.push("показать подтвержденную сумму НДС к уплате на дату среза по счетам 68*");
}
else if (input.intent === "payables_confirmed_as_of_date") {
offers.push("показать подтвержденный реестр открытых обязательств на дату среза по 60/76");
}
@@ -1348,7 +1352,8 @@ function buildLimitedIntentSignalLine(input) {
list_receivables_counterparties: "Сигнал запроса: нужен ранжированный список должников.",
list_payables_counterparties: "Сигнал запроса: нужен ранжированный список кредиторов.",
receivables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез дебиторской задолженности на дату.",
payables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез обязательств к оплате на дату."
payables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез обязательств к оплате на дату.",
vat_payable_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез НДС к уплате на дату."
};
const byShape = {
AGGREGATE_LOOKUP: "Сигнал запроса: агрегатный вопрос по периоду/срезу.",
@@ -1474,16 +1479,15 @@ function buildLimitedExecutionResult(input) {
});
const requestedResultMode = resolveRequestedResultMode(input.intent.intent, input.filters);
const reasonsWithConfirmedFallback = withConfirmedBalanceFallbackReason(input.reasons, requestedResultMode, undefined, resultSemantics.result_mode);
const reasons = (input.intent.intent === "payables_confirmed_as_of_date" || input.intent.intent === "receivables_confirmed_as_of_date") &&
!reasonsWithConfirmedFallback.includes(input.intent.intent === "payables_confirmed_as_of_date"
? "exact_payables_mode_limited_response"
: "exact_receivables_mode_limited_response")
? [
...reasonsWithConfirmedFallback,
input.intent.intent === "payables_confirmed_as_of_date"
? "exact_payables_mode_limited_response"
: "exact_receivables_mode_limited_response"
]
const exactLimitedReason = input.intent.intent === "payables_confirmed_as_of_date"
? "exact_payables_mode_limited_response"
: input.intent.intent === "receivables_confirmed_as_of_date"
? "exact_receivables_mode_limited_response"
: input.intent.intent === "vat_payable_confirmed_as_of_date"
? "exact_vat_payable_mode_limited_response"
: null;
const reasons = exactLimitedReason && !reasonsWithConfirmedFallback.includes(exactLimitedReason)
? [...reasonsWithConfirmedFallback, exactLimitedReason]
: reasonsWithConfirmedFallback;
const routeExpectationAudit = input.routeExpectationAudit ??
buildRouteExpectationAudit({
@@ -1591,13 +1595,20 @@ class AddressQueryService {
const confirmedBalancePayablesIntent = (intent.intent === "list_payables_counterparties" || intent.intent === "payables_confirmed_as_of_date") &&
requestedResultMode === "confirmed_balance";
const confirmedBalanceReceivablesIntent = intent.intent === "receivables_confirmed_as_of_date" && requestedResultMode === "confirmed_balance";
const confirmedBalanceVatPayableIntent = intent.intent === "vat_payable_confirmed_as_of_date" && requestedResultMode === "confirmed_balance";
const payablesConfirmedExecution = confirmedBalancePayablesIntent
? resolveExecutionFiltersForPayablesConfirmedBalance(filters.extracted_filters, analysisDate)
? resolveExecutionFiltersForConfirmedBalance(filters.extracted_filters, analysisDate)
: null;
const receivablesConfirmedExecution = confirmedBalanceReceivablesIntent
? resolveExecutionFiltersForPayablesConfirmedBalance(filters.extracted_filters, analysisDate)
? resolveExecutionFiltersForConfirmedBalance(filters.extracted_filters, analysisDate)
: null;
const executionFilters = payablesConfirmedExecution?.executionFilters ?? receivablesConfirmedExecution?.executionFilters ?? filters.extracted_filters;
const vatPayableConfirmedExecution = confirmedBalanceVatPayableIntent
? resolveExecutionFiltersForConfirmedBalance(filters.extracted_filters, analysisDate)
: null;
const executionFilters = payablesConfirmedExecution?.executionFilters ??
receivablesConfirmedExecution?.executionFilters ??
vatPayableConfirmedExecution?.executionFilters ??
filters.extracted_filters;
if (payablesConfirmedExecution?.asOfDerived &&
!(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)) {
if (!filters.warnings.includes("as_of_date_derived_for_confirmed_payables")) {
@@ -1616,6 +1627,15 @@ class AddressQueryService {
baseReasons.push("as_of_date_derived_for_confirmed_receivables");
}
}
if (vatPayableConfirmedExecution?.asOfDerived &&
!(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)) {
if (!filters.warnings.includes("as_of_date_derived_for_confirmed_vat_payable")) {
filters.warnings.push("as_of_date_derived_for_confirmed_vat_payable");
}
if (!baseReasons.includes("as_of_date_derived_for_confirmed_vat_payable")) {
baseReasons.push("as_of_date_derived_for_confirmed_vat_payable");
}
}
const capabilityDecision = (0, addressCapabilityPolicy_1.resolveAddressCapabilityRouteDecision)(intent.intent);
const capabilityAudit = buildCapabilityAudit(intent.intent);
const shadowRouteAudit = buildShadowRouteAudit({
@@ -1686,6 +1706,10 @@ class AddressQueryService {
!baseReasons.includes("confirmed_balance_exact_receivables_intent")) {
baseReasons.push("confirmed_balance_exact_receivables_intent");
}
if (intent.intent === "vat_payable_confirmed_as_of_date" &&
!baseReasons.includes("confirmed_balance_exact_vat_payable_intent")) {
baseReasons.push("confirmed_balance_exact_vat_payable_intent");
}
if (requestedResultMode === "confirmed_balance" &&
recipeIntent === "open_items_by_counterparty_or_contract" &&
!baseReasons.includes("confirmed_balance_unavailable_fallback_to_heuristic_candidates")) {
@@ -2658,10 +2682,15 @@ class AddressQueryService {
routeExpectationAudit: finalRouteExpectationAudit
});
}
if (((intent.intent === "payables_confirmed_as_of_date" && composeIntent === "payables_confirmed_as_of_date") ||
(intent.intent === "receivables_confirmed_as_of_date" && composeIntent === "receivables_confirmed_as_of_date")) &&
factualResultSemantics.balance_confirmed !== true) {
const exactModeName = intent.intent === "payables_confirmed_as_of_date" ? "payables" : "receivables";
const exactConfirmedIntent = (intent.intent === "payables_confirmed_as_of_date" && composeIntent === "payables_confirmed_as_of_date") ||
(intent.intent === "receivables_confirmed_as_of_date" && composeIntent === "receivables_confirmed_as_of_date") ||
(intent.intent === "vat_payable_confirmed_as_of_date" && composeIntent === "vat_payable_confirmed_as_of_date");
if (exactConfirmedIntent && factualResultSemantics.balance_confirmed !== true) {
const exactModeName = intent.intent === "payables_confirmed_as_of_date"
? "payables"
: intent.intent === "receivables_confirmed_as_of_date"
? "receivables"
: "vat_payable";
return buildLimitedExecutionResult({
mode,
shape,
@@ -2686,7 +2715,9 @@ class AddressQueryService {
materializationDropReason: rowDiagnostics.materializationDropReason,
category: "recipe_visibility_gap",
reasonText: `exact ${exactModeName} mode: confirmed balance was not proven for the requested as-of slice`,
nextStep: "specify as_of_date/counterparty or enable detailed settlement registers for exact confirmed balance",
nextStep: intent.intent === "vat_payable_confirmed_as_of_date"
? "specify as_of_date/organization or provide VAT settlement registers to prove exact VAT payable balance"
: "specify as_of_date/counterparty or enable detailed settlement registers for exact confirmed balance",
limitations: [`exact_${exactModeName}_mode_unconfirmed_output_blocked`],
reasons: [...baseReasons, `exact_${exactModeName}_mode_unconfirmed_output_blocked`],
capabilityAudit,
+84 -33
View File
@@ -66,6 +66,28 @@ const RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE = `
УПОРЯДОЧИТЬ ПО
Сумма __ORDER_DIRECTION__
`;
const VAT_PAYABLE_CONFIRMED_AS_OF_QUERY_TEMPLATE = `
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
__AS_OF_EXPR__ КАК Период,
"Остатки на дату" КАК Регистратор,
"" КАК СчетДт,
ПРЕДСТАВЛЕНИЕ(Остатки.Счет) КАК СчетКт,
Остатки.СуммаРазвернутыйОстатокКт КАК Сумма,
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто1) КАК СубконтоДт1,
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто2) КАК СубконтоДт2,
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто3) КАК СубконтоДт3,
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто1) КАК СубконтоКт1,
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто2) КАК СубконтоКт2,
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто3) КАК СубконтоКт3,
ПРЕДСТАВЛЕНИЕ(Остатки.Организация) КАК Организация
ИЗ
РегистрБухгалтерии.Хозрасчетный.Остатки(__AS_OF_EXPR__, , , ) КАК Остатки
ГДЕ
Остатки.СуммаРазвернутыйОстатокКт > 0
И (__VAT_PAYABLE_ACCOUNTS_MATCH__)
УПОРЯДОЧИТЬ ПО
Сумма __ORDER_DIRECTION__
`;
const BANK_DOCS_QUERY_TEMPLATE = `
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
БанкСписание.Дата КАК Период,
@@ -549,6 +571,17 @@ const BASE_RECIPES = [
account_scope_mode: "preferred",
query_template: "vat_payable_forecast_profile"
},
{
recipe_id: "address_vat_payable_confirmed_as_of_date_v1",
intent: "vat_payable_confirmed_as_of_date",
purpose: "Build confirmed VAT payable snapshot as-of date from balances on VAT payable accounts",
required_filters: ["as_of_date"],
optional_filters: ["period_from", "period_to", "organization", "limit", "sort"],
default_limit: 200,
account_scope: ["68"],
account_scope_mode: "strict",
query_template: "vat_payable_confirmed_as_of_balance_profile"
},
{
recipe_id: "address_contracts_by_counterparty_v1",
intent: "list_contracts_by_counterparty",
@@ -960,27 +993,27 @@ function buildAddressRecipePlan(recipe, filters) {
.replaceAll("__VAT68_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", config_1.VAT_PAYABLE_68_PREFIXES))
.replaceAll("__VAT19_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", config_1.VAT_PAYABLE_19_PREFIXES))
.replaceAll("__VAT19_KT_MATCH__", buildAccountPrefixPredicate("Движения.СчетКт", config_1.VAT_PAYABLE_19_PREFIXES))
: recipe.query_template === "contracts_by_counterparty_profile"
? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit))
: recipe.query_template === "payables_confirmed_as_of_balance_profile"
? (() => {
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
? toDateTimeExpr(filters.as_of_date, true)
: recipe.query_template === "vat_payable_confirmed_as_of_balance_profile"
? (() => {
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
? toDateTimeExpr(filters.as_of_date, true)
: null) ??
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
? toDateTimeExpr(filters.period_to, true)
: null) ??
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
? toDateTimeExpr(filters.period_to, true)
: null) ??
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return VAT_PAYABLE_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__VAT_PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", config_1.VAT_PAYABLE_68_PREFIXES))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: recipe.query_template === "contracts_by_counterparty_profile"
? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit))
: recipe.query_template === "payables_confirmed_as_of_balance_profile"
? (() => {
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
? toDateTimeExpr(filters.as_of_date, true)
@@ -992,23 +1025,41 @@ function buildAddressRecipePlan(recipe, filters) {
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
return PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: MOVEMENTS_QUERY_TEMPLATE
.replace("__LIMIT__", String(resolvedLimit))
.replace("__WHERE_CLAUSE__", (() => {
const extraConditions = [];
const accountCondition = buildMovementAccountCondition(filters);
if (accountCondition) {
extraConditions.push(accountCondition);
}
return buildWhereClause(filters, "Движения.Период", extraConditions);
})())
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
? (() => {
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
? toDateTimeExpr(filters.as_of_date, true)
: null) ??
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
? toDateTimeExpr(filters.period_to, true)
: null) ??
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: MOVEMENTS_QUERY_TEMPLATE
.replace("__LIMIT__", String(resolvedLimit))
.replace("__WHERE_CLAUSE__", (() => {
const extraConditions = [];
const accountCondition = buildMovementAccountCondition(filters);
if (accountCondition) {
extraConditions.push(accountCondition);
}
return buildWhereClause(filters, "Движения.Период", extraConditions);
})())
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
return {
recipe,
query,
@@ -1969,6 +1969,80 @@ function composeFactualReply(intent, rows, options = {}) {
text: lines.join("\n")
};
}
if (intent === "vat_payable_confirmed_as_of_date") {
const asOfDate = resolvePayablesAsOfDate(options);
const confirmedRows = rows.filter((row) => {
const amount = row.amount ?? 0;
if (!Number.isFinite(amount) || amount <= 0) {
return false;
}
const section = extractAccountSectionCode(row.account_kt);
return section === "68";
});
const byAccount = new Map();
for (const row of confirmedRows) {
const account = String(row.account_kt ?? "").trim() || "68*";
const registrator = String(row.registrator ?? "").trim();
const amount = row.amount ?? 0;
const current = byAccount.get(account);
if (!current) {
byAccount.set(account, {
account,
total: amount,
operations: 1,
lastPeriod: row.period,
refs: registrator ? new Set([registrator]) : new Set()
});
continue;
}
current.total += amount;
current.operations += 1;
if ((row.period ?? "") > (current.lastPeriod ?? "")) {
current.lastPeriod = row.period;
}
if (registrator) {
current.refs.add(registrator);
}
}
const accountRows = Array.from(byAccount.values())
.filter((item) => Number.isFinite(item.total) && item.total > 0)
.sort((a, b) => b.total - a.total || b.operations - a.operations || a.account.localeCompare(b.account, "ru"));
const totalVatPayable = accountRows.reduce((sum, item) => sum + item.total, 0);
const lines = [
`Итого подтвержденный НДС к уплате на ${formatDateRu(asOfDate)}: ${formatMoneyRub(totalVatPayable)}.`,
"",
"Блок 1. Статус результата",
"- Результат: подтвержденный срез НДС к уплате по состоянию на дату.",
"",
"Блок 2. Что учтено",
`- Дата среза: ${formatDateRu(asOfDate)}.`,
"- Контур: остатки по счетам НДС к уплате (68*).",
"",
"Блок 3. Сводка",
`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`,
`- Подтвержденных позиций по НДС: ${formatNumberWithDots(accountRows.length)}.`,
"",
"Блок 4. Подтвержденные позиции"
];
if (accountRows.length > 0) {
lines.push(...accountRows.slice(0, 12).map((item, index) => {
const refs = Array.from(item.refs).slice(0, 2).join("; ");
return `${index + 1}. ${item.account} | остаток НДС к уплате: ${formatMoneyRub(item.total)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${refs ? ` | source refs: ${refs}` : ""}`;
}));
}
else {
lines.push("- Подтвержденный остаток НДС к уплате на дату среза не найден.");
}
return {
responseType: "FACTUAL_LIST",
text: lines.map(emphasizeNumericTokens).join("\n"),
semantics: {
result_mode: "confirmed_balance",
evidence_strength: "strong",
balance_confirmed: true
}
};
}
if (intent === "account_balance_snapshot") {
const movementSum = rows.reduce((sum, row) => sum + (row.amount ?? 0), 0);
const lines = [
@@ -30,6 +30,12 @@ function hasExplicitPeriodLiteral(text) {
function hasOpenItemsHint(text) {
return /(?:open\s+items|unclosed\s+items|хвост|висят|незакрыт|не\s+закрыт|открыт|долг|задолж|позиц)/iu.test(String(text ?? ""));
}
function hasVatCue(text) {
return /(?:^|[\s,.;:!?()\-])(?:ндс|vat)(?=$|[\s,.;:!?()\-])/iu.test(String(text ?? ""));
}
function hasVatForecastCue(text) {
return /(?:прогноз|forecast|прикин|оцен|план)/iu.test(String(text ?? ""));
}
function hasDocumentSignal(text) {
return /(?:док(?:и|умент|ументы|ументов|ументами)|docs?|documents?|doki|docy|doci)/iu.test(String(text ?? ""));
}
@@ -349,11 +355,23 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
reasons.push("as_of_date_from_followup_context");
}
}
if (!sameDateRequested && !hasExplicitPeriodLiteral(userMessage)) {
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
const todayIso = new Date().toISOString().slice(0, 10);
const currentLooksDefaultedToToday = currentAsOfDate === todayIso;
if (inheritedAsOfDate && (!currentAsOfDate || currentLooksDefaultedToToday) && currentAsOfDate !== inheritedAsOfDate) {
merged.as_of_date = inheritedAsOfDate;
reasons.push("as_of_date_from_followup_context");
}
}
}
if (intent === "open_items_by_counterparty_or_contract" ||
intent === "list_open_contracts" ||
intent === "payables_confirmed_as_of_date" ||
intent === "receivables_confirmed_as_of_date") {
intent === "receivables_confirmed_as_of_date" ||
intent === "vat_payable_confirmed_as_of_date") {
const hasFollowupSignalForConfirmed = hasAddressFollowupContextSignal(userMessage);
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
const currentContract = toNonEmptyString(merged.contract);
const shouldInheritContract = !currentContract ||
@@ -380,6 +398,16 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
reasons.push("as_of_date_from_followup_context");
}
}
if (!sameDateRequested && hasFollowupSignalForConfirmed && !hasExplicitPeriodLiteral(userMessage)) {
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
const todayIso = new Date().toISOString().slice(0, 10);
const currentLooksDefaultedToToday = currentAsOfDate === todayIso;
if (inheritedAsOfDate && (!currentAsOfDate || currentLooksDefaultedToToday) && currentAsOfDate !== inheritedAsOfDate) {
merged.as_of_date = inheritedAsOfDate;
reasons.push("as_of_date_from_followup_context");
}
}
}
if (allTimeRequested) {
if (toNonEmptyString(merged.period_from) || toNonEmptyString(merged.period_to)) {
@@ -436,6 +464,7 @@ function resolveMissingRequiredFilters(intent, filters) {
documents_forming_balance: ["account", "as_of_date"],
payables_confirmed_as_of_date: ["as_of_date"],
receivables_confirmed_as_of_date: ["as_of_date"],
vat_payable_confirmed_as_of_date: ["as_of_date"],
list_documents_by_counterparty: ["counterparty"],
bank_operations_by_counterparty: ["counterparty"],
list_contracts_by_counterparty: ["counterparty"],
@@ -466,6 +495,17 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
const hasPreviousContract = Boolean(previousContract ?? previousContractFromAnchor);
const hasPreviousCounterparty = Boolean(previousCounterparty ?? previousCounterpartyFromAnchor);
const hasAnyPartyAnchor = hasPreviousContract || hasPreviousCounterparty;
const isVatFollowup = hasVatCue(normalizedMessage);
if (detectedIntent.intent === "unknown" && isVatFollowup) {
const vatIntent = hasVatForecastCue(normalizedMessage)
? "vat_payable_forecast"
: "vat_payable_confirmed_as_of_date";
return {
intent: vatIntent,
confidence: "low",
reasons: [...detectedIntent.reasons, "intent_adjusted_to_vat_followup_context"]
};
}
if (hasOpenItemsHint(normalizedMessage) && hasAnyPartyAnchor) {
return {
intent: "open_items_by_counterparty_or_contract",
@@ -94,7 +94,8 @@ function inferAggregationProfile(intent, shape) {
if (intent === "account_balance_snapshot" ||
intent === "documents_forming_balance" ||
intent === "payables_confirmed_as_of_date" ||
intent === "receivables_confirmed_as_of_date") {
intent === "receivables_confirmed_as_of_date" ||
intent === "vat_payable_confirmed_as_of_date") {
return "balance_snapshot";
}
if (intent === "open_items_by_counterparty_or_contract" ||
+102 -39
View File
@@ -2033,7 +2033,7 @@ function textMojibakeScoreForAddress(value) {
const source = String(value ?? "");
const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length;
const latin = (source.match(/[A-Za-z]/g) ?? []).length;
const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ‘’“”•–—™љ›њќћџ]/g) ?? []).length;
const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ�?’“”•–—™љ›њќћџ]/g) ?? []).length;
const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length;
const doubleEncodedMarkers = (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length;
return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2;
@@ -2043,7 +2043,7 @@ function looksLikeMojibakeForAddress(value) {
if (!source.trim()) {
return false;
}
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ‘’“”•–—™љ›њќћџ]/.test(source)) {
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ�?’“”•–—™љ›њќћџ]/.test(source)) {
return true;
}
if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) {
@@ -2248,7 +2248,7 @@ function normalizeCounterpartyForFollowupMatch(value) {
return compactWhitespace(repairAddressMojibake(String(value ?? ""))
.toLowerCase()
.replace(/ё/g, "е")
.replace(/[«»"'`“”„’‘]/g, " ")
.replace(/[«»"'`“”„’�?]/g, " ")
.replace(/[^a-zа-я0-9\s._-]+/giu, " "));
}
function normalizeCounterpartyTokenForFollowupMatch(value) {
@@ -2294,7 +2294,7 @@ function extractDisplayedAddressEntityCandidates(replyText, entityType = "unknow
if (parts.length >= 2 && /^\d{4}-\d{2}-\d{2}/.test(parts[0] ?? "")) {
counterpartyCandidate = parts[1] ?? counterpartyCandidate;
}
const cleanedCandidate = compactWhitespace(counterpartyCandidate.replace(/^["'«»“”„`’‘]+|["'«»“”„`’‘]+$/gu, ""));
const cleanedCandidate = compactWhitespace(counterpartyCandidate.replace(/^["'«»“”„`’�?]+|["'«»“”„`’�?]+$/gu, ""));
if (!cleanedCandidate || cleanedCandidate.length < 2) {
continue;
}
@@ -2558,61 +2558,110 @@ function isImplicitAddressContinuationByLlm(userMessage, llmPreDecomposeMeta) {
return tokenCount > 0 && tokenCount <= 4;
}
function hasAddressFollowupContextSignal(userMessage) {
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
const repaired = repairAddressMojibake(String(userMessage ?? ""));
const text = compactWhitespace(repaired.toLowerCase());
if (!text) {
const repairedText = compactWhitespace(repaired.toLowerCase());
const samples = [rawText, repairedText].filter((item) => item.length > 0);
if (samples.length === 0) {
return false;
}
if (hasStandaloneAddressTopicSignal(text)) {
const hasAny = (pattern) => samples.some((sample) => pattern.test(sample));
const hasMarker = () => samples.some((sample) => hasFollowupMarker(sample));
const hasPointer = () => samples.some((sample) => hasReferentialPointer(sample));
const minTokens = samples.reduce((min, sample) => Math.min(min, countTokens(sample)), Number.POSITIVE_INFINITY);
const shortFollowup = minTokens <= 8;
const ultraShortFollowup = minTokens <= 3;
const debtRoleSwapToReceivables = shortFollowup &&
/^(?:\u0430|\u0438)\s+(?:\u043d\u0430\u043c\s+)?\u043a\u0442\u043e\b/iu.test(rawText);
if (debtRoleSwapToReceivables) {
return true;
}
const debtRoleSwapToPayables = shortFollowup &&
/^(?:\u0430|\u0438)\s+(?:\u043c\u044b\s+)?\u043a\u043e\u043c\u0443\b/iu.test(rawText);
if (debtRoleSwapToPayables) {
return true;
}
const shortContinuationCue = ultraShortFollowup &&
/^(?:\u0434\u0430\u0432\u0430\u0439|\u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0439|\u043f\u043e\u043a\u0430\u0437\u044b\u0432\u044b\u0430\u0439|\u0435\u0449[\u0435\u0451]|also|again|go|ok|okay)\b/iu.test(rawText);
if (shortContinuationCue) {
return true;
}
const shortVatCue = ultraShortFollowup &&
/^(?:(?:\u0430|\u0438)\s+)?(?:(?:\u043f\u043e|po)\s+)?(?:\u043d\u0434\u0441|vat)(?=$|[\s,.;:!?])/iu.test(rawText);
if (shortVatCue) {
return true;
}
if (shortFollowup && hasAny(/^(?:а|и)\s+(?:нам\s+)?кто\b/iu)) {
return true;
}
if (shortFollowup && hasAny(/^(?:а|и)\s+(?:мы\s+)?кому\b/iu)) {
return true;
}
if (ultraShortFollowup && hasAny(/^(?:давай|показывай|показывыай|ещ[её]|also|again|go|ok|okay)\b/iu)) {
return true;
}
if (hasStandaloneAddressTopicSignal(rawText || repairedText)) {
return false;
}
if (shouldHandleAsAssistantCapabilityMetaQuery(text)) {
if (shouldHandleAsAssistantCapabilityMetaQuery(rawText || repairedText)) {
return false;
}
if (/(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|за\s+любой\s+период|for\s+all\s+time|all\s+time|for\s+entire\s+period|entire\s+period|for\s+any\s+period|any\s+period)/iu.test(text)) {
if (hasAny(/(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|за\s+любой\s+период|for\s+all\s+time|all\s+time|for\s+entire\s+period|entire\s+period|for\s+any\s+period|any\s+period)/iu)) {
return true;
}
if (hasReferentialPointer(text)) {
if (hasPointer()) {
return true;
}
if (/(?:на\s+ту\s+же\s+дат[ауеы]|на\s+эту\s+же\s+дат[ауеы]|same\s+date|the\s+same\s+date|as\s+of\s+same\s+date)/iu.test(text)) {
if (hasAny(/(?:на\s+ту\s+же\s+дат[ауеы]|на\s+эту\s+же\s+дат[ауеы]|same\s+date|the\s+same\s+date|as\s+of\s+same\s+date)/iu)) {
return true;
}
const shortFollowup = countTokens(text) <= 8;
if (/(?:кроме|помимо)\s+(?:этого|этой|этот|эту|этих|этого\s+документа|этого\s+договора|этого\s+контрагента)/iu.test(text)) {
if (hasAny(/(?:кроме|помимо)\s+(?:этого|этой|этот|эту|этих|этого\s+документа|этого\s+договора|этого\s+контрагента)/iu)) {
return true;
}
if (/(?:есть\s+ещ[её]|что\s+ещ[её]|ещ[её]\s+что|ещ[её]\s+что-?то|остал(?:ось|ось\?)|друг(?:ое|ие))/iu.test(text) && countTokens(text) <= 12) {
if (hasAny(/(?:есть\s+ещ[её]|что\s+ещ[её]|ещ[её]\s+что|ещ[её]\s+что-?то|остал(?:ось|ось\?)|друг(?:ое|ие))/iu) && minTokens <= 12) {
return true;
}
if (shortFollowup && hasFollowupMarker(text)) {
if (shortFollowup && hasMarker()) {
return true;
}
if (shortFollowup && /(?:^|\s)(?:также|тоже|also|same|again|ещ[её]|теперь|then|now)(?=$|[\s,.;:!?])/iu.test(text)) {
if (shortFollowup && hasAny(/(?:^|\s)(?:также|тоже|also|same|again|ещ[её]|теперь|then|now)(?=$|[\s,.;:!?])/iu)) {
return true;
}
if (shortFollowup && hasAny(/(?:кто\s+из\s+(?:них|этих|тех)|кто\s+нов(?:ые|ых|ый)|кто\s+потом\s+исчез|кто\s+был\s+(?:только|ровно)\s+один\s+раз)/iu)) {
return true;
}
if (shortFollowup &&
/(?:кто\s+из\s+(?:них|этих|тех)|кто\s+нов(?:ые|ых|ый)|кто\s+потом\s+исчез|кто\s+был\s+(?:только|ровно)\s+один\s+раз)/iu.test(text)) {
return true;
}
if (shortFollowup && /^(?:а|и)\s+кто\b/iu.test(text)) {
hasAny(/(?:почему|why|из[-\s]?за\s+чего|как\s+так|reason)/iu) &&
hasAny(/(?:ндс|vat|прогноз|к\s+уплате|нул|ноль|\b0(?:[.,]0+)?\b)/iu)) {
return true;
}
if (shortFollowup &&
/(?:почему|why|из[-\s]?за\s+чего|как\s+так|reason)/iu.test(text) &&
/(?:ндс|vat|прогноз|к\s+уплате|нул|ноль|\b0(?:[.,]0+)?\b)/iu.test(text)) {
hasAny(/(?:^|\s)по\s+[a-zа-яё][a-zа-яё0-9._-]{1,}(?=$|[\s,.;:!?])/iu) &&
!hasAny(/(?:по\s+этому|по\s+тому|по\s+нему|по\s+ней|по\s+ним)/iu)) {
return true;
}
if (shortFollowup &&
/(?:^|\s)по\s+[a-zа-яё][a-zа-яё0-9._-]{1,}(?=$|[\s,.;:!?])/iu.test(text) &&
!/(?:по\s+этому|по\s+тому|по\s+нему|по\s+ней|по\s+ним)/iu.test(text)) {
return true;
}
if (shortFollowup && hasPeriodLiteral(text)) {
if (shortFollowup && samples.some((sample) => hasPeriodLiteral(sample))) {
return true;
}
return false;
}
function resolveDebtRoleSwapFollowupIntent(userMessage, previousIntent) {
const normalized = compactWhitespace(String(userMessage ?? "").toLowerCase());
if (!normalized || countTokens(normalized) > 10) {
return null;
}
const hasReceivablesCue = /(?:нам\s+кто\s+долж|кто\s+нам\s+долж|кто\s+долж[а-яё]*\s+нам|дебитор|к\s+получению|к\s+взысканию|receivable)/iu.test(normalized);
const hasPayablesCue = /(?:мы\s+кому\s+долж|кому\s+мы\s+долж|кому\s+долж[а-яё]*\s+мы|кредитор|к\s+уплате|payable)/iu.test(normalized);
if ((previousIntent === "payables_confirmed_as_of_date" || previousIntent === "list_payables_counterparties") &&
hasReceivablesCue) {
return "receivables_confirmed_as_of_date";
}
if ((previousIntent === "receivables_confirmed_as_of_date" || previousIntent === "list_receivables_counterparties") &&
hasPayablesCue) {
return "payables_confirmed_as_of_date";
}
return null;
}
function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMessage = null, llmPreDecomposeMeta = null) {
const previousAddressItem = findLastAddressAssistantItem(items);
const previousAddressDebug = previousAddressItem?.debug ?? null;
@@ -2621,9 +2670,15 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
Boolean(followupOffer?.enabled) &&
(isImplicitAddressContinuationByLlm(userMessage, llmPreDecomposeMeta) ||
(toNonEmptyString(alternateMessage) ? isImplicitAddressContinuationByLlm(alternateMessage, llmPreDecomposeMeta) : false));
const hasPrimaryFollowupSignal = hasAddressFollowupContextSignal(userMessage);
const sourceIntentHint = toNonEmptyString(previousAddressDebug?.detected_intent);
const debtRoleSwapPrimary = sourceIntentHint ? resolveDebtRoleSwapFollowupIntent(userMessage, sourceIntentHint) : null;
const debtRoleSwapAlternate = sourceIntentHint && toNonEmptyString(alternateMessage)
? resolveDebtRoleSwapFollowupIntent(String(alternateMessage ?? ""), sourceIntentHint)
: null;
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
const hasPrimaryFollowupSignal = hasAddressFollowupContextSignal(userMessage) || Boolean(debtRoleSwapPrimary);
const hasAlternateFollowupSignal = toNonEmptyString(alternateMessage)
? hasAddressFollowupContextSignal(alternateMessage)
? hasAddressFollowupContextSignal(alternateMessage) || Boolean(debtRoleSwapAlternate)
: false;
const hasPrimaryIndexReferenceSignal = extractDisplayedEntityIndexMention(userMessage) !== null;
const hasAlternateIndexReferenceSignal = toNonEmptyString(alternateMessage)
@@ -2632,7 +2687,11 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
const hasIndexReferenceSignal = hasPrimaryIndexReferenceSignal || hasAlternateIndexReferenceSignal;
const hasStandaloneAddressTopic = hasStandaloneAddressTopicSignal(userMessage) ||
(toNonEmptyString(alternateMessage) ? hasStandaloneAddressTopicSignal(alternateMessage) : false);
if (hasStandaloneAddressTopic && !hasImplicitContinuationSignal && !hasIndexReferenceSignal) {
if (hasStandaloneAddressTopic &&
!hasPrimaryFollowupSignal &&
!hasAlternateFollowupSignal &&
!hasImplicitContinuationSignal &&
!hasIndexReferenceSignal) {
return null;
}
if (!hasPrimaryFollowupSignal && !hasAlternateFollowupSignal && !hasImplicitContinuationSignal && !hasIndexReferenceSignal) {
@@ -2644,6 +2703,9 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
const sourceIntent = toNonEmptyString(previousAddressDebug.detected_intent);
let previousIntent = sourceIntent;
let followupSelectionMode = "carry_previous_intent";
if (debtRoleSwapIntent) {
previousIntent = debtRoleSwapIntent;
}
if (hasImplicitContinuationSignal) {
const suggestedIntent = Array.isArray(followupOffer?.suggested_intents)
? toNonEmptyString(followupOffer.suggested_intents[0])
@@ -3725,7 +3787,8 @@ const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
"list_contracts_by_counterparty",
"contract_usage_overview",
"contract_usage_and_value",
"vat_payable_forecast"
"vat_payable_forecast",
"vat_payable_confirmed_as_of_date"
]);
function resolveAssistantOrchestrationDecision(input) {
const rawUserMessage = String(input?.rawUserMessage ?? input?.userMessage ?? "");
@@ -4939,14 +5002,14 @@ async function resolveAssistantDataScopeProbe() {
};
}
const catalogQueryCandidates = [
"ВЫБРАТЬ ПЕРВЫЕ 20 ПРЕДСТАВЛЕНИЕ(Организации.Ссылка) КАК Организация ИЗ Справочник.Организации КАК Организации",
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.Наименование КАК Организация ИЗ Справочник.Организации КАК Организации",
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.НаименованиеПолное КАК Организация ИЗ Справочник.Организации КАК Организации",
"ВЫБРАТЬ ПЕРВЫЕ 100 Организации.Ссылка КАК Организация, ПРЕДСТАВЛЕНИЕ(Организации.Ссылка) КАК ОрганизацияПредставление ИЗ Справочник.Организации КАК Организации"
"ВЫБРАТЬ ПЕРВЫЕ 20 ПРЕДСТАВЛЕН�?Е(Организации.Ссылка) КАК Организация �?З Справочник.Организации КАК Организации",
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.Наименование КАК Организация �?З Справочник.Организации КАК Организации",
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.НаименованиеПолное КАК Организация �?З Справочник.Организации КАК Организации",
"ВЫБРАТЬ ПЕРВЫЕ 100 Организации.Ссылка КАК Организация, ПРЕДСТАВЛЕН�?Е(Организации.Ссылка) КАК ОрганизацияПредставление �?З Справочник.Организации КАК Организации"
];
const movementProbeCandidates = [
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация, ПРЕДСТАВЛЕНИЕ(Движения.Организация) КАК ОрганизацияПредставление ИЗ РегистрБухгалтерии.Хозрасчетный КАК Движения УПОРЯДОЧИТЬ ПО Движения.Период УБЫВ",
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация ИЗ РегистрБухгалтерии.Хозрасчетный КАК Движения"
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация, ПРЕДСТАВЛЕН�?Е(Движения.Организация) КАК ОрганизацияПредставление �?З РегистрБухгалтерии.Хозрасчетный КАК Движения УПОРЯДОЧ�?ТЬ ПО Движения.Период УБЫВ",
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация �?З РегистрБухгалтерии.Хозрасчетный КАК Движения"
];
let lastError = null;
const catalogFacts = { names: [], refs: [], pairs: [] };
@@ -5077,7 +5140,7 @@ function buildAssistantOperationalBoundaryReply() {
return [
"Понимаю, что ситуация срочная.",
"Я не могу сам настраивать 1С или менять базу/конфигурацию.",
"Могу помочь безопасно: разберем симптомы и подготовим точные шаги для вашего 1С/ИТ-админа."
"Могу помочь безопасно: разберем симптомы и подготовим точные шаги для вашего 1С/�?Т-админа."
].join(" ");
}
function buildAssistantSafetyRefusalReply() {