ДОМЕНЫ - ВОПРОСЫ - Исправить обработку коротких debt follow-up и защиту от диагностических LLM rewrite
This commit is contained in:
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
+2
-1
@@ -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
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user