ДОМЕНЫ - ВОПРОСЫ - fix: переведен exact-маршрут кому должны на дату на Хозрасчетный.Остатки (без Движения.СубконтоДт1/Кт1)
This commit is contained in:
@@ -298,12 +298,19 @@ const CONTRACT_USAGE_OVERVIEW_HINTS = [
|
||||
const CUSTOMER_REVENUE_AND_PAYMENTS_HINTS = [
|
||||
"самые доходные клиенты",
|
||||
"самые доходные заказчики",
|
||||
"самые ликвидные клиенты",
|
||||
"самые ликвидные заказчики",
|
||||
"самых ликвидних заказчиков",
|
||||
"топ клиентов по сумме поступлений",
|
||||
"топ заказчиков по сумме поступлений",
|
||||
"кто больше всего принес денег",
|
||||
"кто больше всего принёс денег",
|
||||
"кто принес больше всего денег",
|
||||
"кто принёс больше всего денег",
|
||||
"кто нам больше денег принес",
|
||||
"кто нам больше денег принёс",
|
||||
"кто нам принес больше денег",
|
||||
"кто нам принёс больше денег",
|
||||
"кто нам больше всего занес",
|
||||
"кто нам больше всего занёс",
|
||||
"кто нам принес больше всего",
|
||||
@@ -690,12 +697,18 @@ function hasCustomerRevenueAndPaymentsSignal(text) {
|
||||
const asksCounterpartySource = /(?:с\s+каких|от\s+каких|от\s+кого|from\s+which|from\s+who)/iu.test(text);
|
||||
const asksIncomingFlow = /(?:приход|поступлен|входящ|зачислен|inflow|incoming)/iu.test(text);
|
||||
const asksWhoBringsMostMoney = /(?:кто\s+(?:нам\s+)?(?:больше\s+всего|сам(?:ый|ая|ое|ые)|наибольш(?:ий|ая|ее|ие))\s+(?:прин[её]с|зан[её]с).*(?:деньг|денег))/iu.test(text);
|
||||
const asksWhoBringsMoneyLoose = /(?:кто\s+(?:нам\s+)?(?:больше|больше\s+всех|больше\s+всего).*(?:деньг|денег|доход|выручк).*(?:прин[её]с|зан[её]с))/iu.test(text) ||
|
||||
/(?:кто\s+(?:нам\s+)?(?:прин[её]с|зан[её]с).*(?:больше|больше\s+всех|больше\s+всего).*(?:деньг|денег|доход|выручк))/iu.test(text);
|
||||
const asksLiquidityRanking = /(?:ликвидн|liquid)/iu.test(text) &&
|
||||
(asksCustomerGroup || hasCounterpartyLexeme || /(?:клиент|заказчик|контрагент|customer|client|counterpart)/iu.test(text));
|
||||
const asksProfitableYears = /(?:доходн|выручк|оборот|прибыл|revenue|turnover).*(?:год|года|годы|year|years)/iu.test(text) &&
|
||||
/(?:сам(?:ый|ая|ое|ые)|топ|луч|max|best|наибольш|больше)/iu.test(text);
|
||||
const asksDealBudgetRanking = /(?:сделк|deal|бюджет)/iu.test(text) &&
|
||||
/(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн|минимальн)/iu.test(text);
|
||||
const asksRevenueTotal = /(?:сколько|скока|скок).*(?:денег|выручк|доход|заработ|оборот)/iu.test(text);
|
||||
const asksOverallTurnover = /(?:общ(?:ий|ие|ая)\s+оборот|общ(?:ая|ий)\s+выручк|total\s+turnover|turnover\s+total)/iu.test(text);
|
||||
const asksMajorShare = /(?:основн(?:ую|ая|ые|ой)\s+част|больш(?:ую|ая|ие)\s+част|львин(?:ая|ую)\s+дол[яю]|ключев(?:ую|ая)\s+част)/iu.test(text);
|
||||
const asksValue = /(?:доходн|выручк|приход|поступлен|входящ|зачислен|оплат|плат(?:еж|ёж|ежн|ежей|ежа|ит|ят)|деньг|денег|заработ|оборот|чек|сделк|бюджет|занес|занёс|принес|принёс|revenue|inflow|deal|turnover)/iu.test(text);
|
||||
const asksValue = /(?:доходн|выручк|приход|поступлен|входящ|зачислен|оплат|плат(?:еж|ёж|ежн|ежей|ежа|ит|ят)|деньг|денег|заработ|оборот|чек|сделк|бюджет|занес|занёс|принес|принёс|ликвидн|revenue|inflow|deal|turnover|liquid)/iu.test(text);
|
||||
const asksRankOrTop = /(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн)/iu.test(text);
|
||||
const asksCountOnly = /(?:сколько|скока|скок)\s+/iu.test(text) && !asksValue;
|
||||
if (asksCountOnly) {
|
||||
@@ -716,6 +729,15 @@ function hasCustomerRevenueAndPaymentsSignal(text) {
|
||||
if (!hasFuzzySupplierLexeme && asksWhoBringsMostMoney) {
|
||||
return true;
|
||||
}
|
||||
if (!hasFuzzySupplierLexeme && asksWhoBringsMoneyLoose) {
|
||||
return true;
|
||||
}
|
||||
if (!hasFuzzySupplierLexeme && asksLiquidityRanking) {
|
||||
return true;
|
||||
}
|
||||
if (!hasFuzzySupplierLexeme && asksProfitableYears) {
|
||||
return true;
|
||||
}
|
||||
if (!hasFuzzySupplierLexeme && (asksRevenueTotal || asksOverallTurnover)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+126
-40
@@ -859,10 +859,61 @@ function isMissingSubcontoFieldError(errorText) {
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return (normalized.includes("поле не найдено") &&
|
||||
(normalized.includes("субконтодт1") ||
|
||||
normalized.includes("subcontodt1") ||
|
||||
normalized.includes("subconto_dt1")));
|
||||
const ruMissingField = "\u043f\u043e\u043b\u0435 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e";
|
||||
const hasMissingFieldSignal = normalized.includes(ruMissingField) || normalized.includes("field not found");
|
||||
if (!hasMissingFieldSignal) {
|
||||
return false;
|
||||
}
|
||||
const hasAnySubcontoSignal = /(?:\u0441\u0443\u0431\u043a\u043e\u043d\u0442\u043e(?:\u0434\u0442|\u043a\u0442)?\d*|subconto(?:_)?(?:dt|kt)?\d*)/iu.test(normalized) ||
|
||||
normalized.includes("subcontodt") ||
|
||||
normalized.includes("subcontokt");
|
||||
return hasAnySubcontoSignal;
|
||||
}
|
||||
function buildCompositeSubcontoFallbackQuery(queryText) {
|
||||
const source = String(queryText ?? "");
|
||||
if (!source.trim()) {
|
||||
return null;
|
||||
}
|
||||
const dt1Pattern = /ПРЕДСТАВЛЕНИЕ\(\s*Движения\.СубконтоДт1\s*\)\s+КАК\s+СубконтоДт1\s*,?/iu;
|
||||
const dt2Pattern = /ПРЕДСТАВЛЕНИЕ\(\s*Движения\.СубконтоДт2\s*\)\s+КАК\s+СубконтоДт2\s*,?/iu;
|
||||
const dt3Pattern = /ПРЕДСТАВЛЕНИЕ\(\s*Движения\.СубконтоДт3\s*\)\s+КАК\s+СубконтоДт3\s*,?/iu;
|
||||
const kt1Pattern = /ПРЕДСТАВЛЕНИЕ\(\s*Движения\.СубконтоКт1\s*\)\s+КАК\s+СубконтоКт1\s*,?/iu;
|
||||
const kt2Pattern = /ПРЕДСТАВЛЕНИЕ\(\s*Движения\.СубконтоКт2\s*\)\s+КАК\s+СубконтоКт2\s*,?/iu;
|
||||
const kt3Pattern = /ПРЕДСТАВЛЕНИЕ\(\s*Движения\.СубконтоКт3\s*\)\s+КАК\s+СубконтоКт3\s*,?/iu;
|
||||
const lines = source.split(/\r?\n/);
|
||||
let replaced = false;
|
||||
const rewrittenLines = lines.map((line) => {
|
||||
const indent = line.match(/^\s*/)?.[0] ?? "";
|
||||
if (dt1Pattern.test(line)) {
|
||||
replaced = true;
|
||||
return `${indent}ПРЕДСТАВЛЕНИЕ(Движения.СубконтоДт) КАК СубконтоДт1,`;
|
||||
}
|
||||
if (dt2Pattern.test(line)) {
|
||||
replaced = true;
|
||||
return `${indent}"" КАК СубконтоДт2,`;
|
||||
}
|
||||
if (dt3Pattern.test(line)) {
|
||||
replaced = true;
|
||||
return `${indent}"" КАК СубконтоДт3,`;
|
||||
}
|
||||
if (kt1Pattern.test(line)) {
|
||||
replaced = true;
|
||||
return `${indent}ПРЕДСТАВЛЕНИЕ(Движения.СубконтоКт) КАК СубконтоКт1,`;
|
||||
}
|
||||
if (kt2Pattern.test(line)) {
|
||||
replaced = true;
|
||||
return `${indent}"" КАК СубконтоКт2,`;
|
||||
}
|
||||
if (kt3Pattern.test(line)) {
|
||||
replaced = true;
|
||||
return `${indent}"" КАК СубконтоКт3,`;
|
||||
}
|
||||
return line;
|
||||
});
|
||||
if (!replaced) {
|
||||
return null;
|
||||
}
|
||||
return rewrittenLines.join("\n");
|
||||
}
|
||||
function applyFutureDatedRowsGuard(rows, intent, referenceDate) {
|
||||
if (!isCounterpartyRiskIntent(intent) || rows.length === 0) {
|
||||
@@ -1729,55 +1780,90 @@ class AddressQueryService {
|
||||
}
|
||||
}
|
||||
let effectiveRecipeId = recipeSelection.selected_recipe.recipe_id;
|
||||
let composeIntent = intent.intent;
|
||||
let routeExpectationIntent = intent.intent;
|
||||
let plan = enforceStrictAccountScopeForIntent((0, addressRecipeCatalog_1.buildAddressRecipePlan)(recipeSelection.selected_recipe, executionFilters), intent.intent);
|
||||
let mcp = await (0, addressMcpClient_1.executeAddressMcpQuery)({
|
||||
query: plan.query,
|
||||
limit: plan.limit
|
||||
});
|
||||
const allowOpenItemsFallbackForMissingSubconto = intent.intent !== "payables_confirmed_as_of_date";
|
||||
if (mcp.error &&
|
||||
(plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
||||
plan.recipe.recipe_id === "address_movements_payables_v1") &&
|
||||
isMissingSubcontoFieldError(mcp.error) &&
|
||||
allowOpenItemsFallbackForMissingSubconto) {
|
||||
const fallbackSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("open_items_by_counterparty_or_contract", executionFilters);
|
||||
if (fallbackSelection.selected_recipe && fallbackSelection.missing_required_filters.length === 0) {
|
||||
const fallbackPlan = enforceStrictAccountScopeForIntent((0, addressRecipeCatalog_1.buildAddressRecipePlan)(fallbackSelection.selected_recipe, executionFilters), intent.intent);
|
||||
const fallbackMcp = await (0, addressMcpClient_1.executeAddressMcpQuery)({
|
||||
query: fallbackPlan.query,
|
||||
limit: fallbackPlan.limit
|
||||
const missingSubcontoFallbackEligible = plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
||||
plan.recipe.recipe_id === "address_movements_payables_v1" ||
|
||||
plan.recipe.recipe_id === "address_payables_confirmed_as_of_date_v1";
|
||||
const missingSubcontoErrorDetected = Boolean(mcp.error && missingSubcontoFallbackEligible && isMissingSubcontoFieldError(mcp.error));
|
||||
if (missingSubcontoErrorDetected) {
|
||||
let missingSubcontoResolvedByComposite = false;
|
||||
const compositeSubcontoQuery = buildCompositeSubcontoFallbackQuery(plan.query);
|
||||
if (compositeSubcontoQuery) {
|
||||
const compositeMcp = await (0, addressMcpClient_1.executeAddressMcpQuery)({
|
||||
query: compositeSubcontoQuery,
|
||||
limit: plan.limit
|
||||
});
|
||||
if (!fallbackMcp.error) {
|
||||
plan = fallbackPlan;
|
||||
mcp = fallbackMcp;
|
||||
if (intent.intent === "list_payables_counterparties") {
|
||||
if (!compositeMcp.error) {
|
||||
plan = {
|
||||
...plan,
|
||||
query: compositeSubcontoQuery
|
||||
};
|
||||
mcp = compositeMcp;
|
||||
missingSubcontoResolvedByComposite = true;
|
||||
if (!baseReasons.includes("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto")) {
|
||||
baseReasons.push("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto");
|
||||
}
|
||||
if (intent.intent === "payables_confirmed_as_of_date") {
|
||||
if (!baseReasons.includes("confirmed_payables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto")) {
|
||||
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!baseReasons.includes("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed")) {
|
||||
baseReasons.push("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed");
|
||||
}
|
||||
}
|
||||
else if (!baseReasons.includes("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_unavailable")) {
|
||||
baseReasons.push("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_unavailable");
|
||||
}
|
||||
if (!missingSubcontoResolvedByComposite) {
|
||||
const fallbackSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("open_items_by_counterparty_or_contract", executionFilters);
|
||||
if (fallbackSelection.selected_recipe && fallbackSelection.missing_required_filters.length === 0) {
|
||||
const fallbackPlan = enforceStrictAccountScopeForIntent((0, addressRecipeCatalog_1.buildAddressRecipePlan)(fallbackSelection.selected_recipe, executionFilters), intent.intent);
|
||||
const fallbackMcp = await (0, addressMcpClient_1.executeAddressMcpQuery)({
|
||||
query: fallbackPlan.query,
|
||||
limit: fallbackPlan.limit
|
||||
});
|
||||
if (!fallbackMcp.error) {
|
||||
plan = fallbackPlan;
|
||||
mcp = fallbackMcp;
|
||||
effectiveRecipeId = fallbackSelection.selected_recipe.recipe_id;
|
||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_to_open_items")) {
|
||||
baseReasons.push("mcp_missing_subconto_field_auto_fallback_to_open_items");
|
||||
}
|
||||
if (!baseReasons.includes("fallback_recipe_switched_to_open_items")) {
|
||||
baseReasons.push("fallback_recipe_switched_to_open_items");
|
||||
}
|
||||
if (intent.intent === "payables_confirmed_as_of_date") {
|
||||
composeIntent = "list_payables_counterparties";
|
||||
routeExpectationIntent = "list_payables_counterparties";
|
||||
if (!baseReasons.includes("confirmed_payables_exact_mode_missing_subconto_fallback_to_open_items")) {
|
||||
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_fallback_to_open_items");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_to_open_items")) {
|
||||
baseReasons.push("mcp_missing_subconto_field_auto_fallback_to_open_items");
|
||||
}
|
||||
if (intent.intent === "list_payables_counterparties" &&
|
||||
!baseReasons.includes("fallback_recipe_switched_to_open_items")) {
|
||||
baseReasons.push("fallback_recipe_switched_to_open_items");
|
||||
else {
|
||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_failed")) {
|
||||
baseReasons.push("mcp_missing_subconto_field_auto_fallback_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_failed")) {
|
||||
baseReasons.push("mcp_missing_subconto_field_auto_fallback_failed");
|
||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_unavailable")) {
|
||||
baseReasons.push("mcp_missing_subconto_field_auto_fallback_unavailable");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_unavailable")) {
|
||||
baseReasons.push("mcp_missing_subconto_field_auto_fallback_unavailable");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mcp.error &&
|
||||
(plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
||||
plan.recipe.recipe_id === "address_movements_payables_v1") &&
|
||||
missingSubcontoFallbackEligible &&
|
||||
isMissingSubcontoFieldError(mcp.error) &&
|
||||
!allowOpenItemsFallbackForMissingSubconto &&
|
||||
!baseReasons.includes("confirmed_payables_exact_mode_missing_subconto_no_heuristic_fallback")) {
|
||||
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_no_heuristic_fallback");
|
||||
}
|
||||
@@ -2480,16 +2566,16 @@ class AddressQueryService {
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
const factual = (0, composeStage_1.composeFactualReply)(intent.intent, filteredRows, composeOptionsFromFilters(executionFilters));
|
||||
const factual = (0, composeStage_1.composeFactualReply)(composeIntent, filteredRows, composeOptionsFromFilters(executionFilters));
|
||||
const factualResultSemantics = mergeAddressResultSemantics(deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
intent: composeIntent,
|
||||
selectedRecipe: effectiveRecipeId,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: factual.responseType,
|
||||
rowsMatched: filteredRows.length
|
||||
}), factual.semantics);
|
||||
const finalRouteExpectationAudit = buildRouteExpectationAudit({
|
||||
intent: intent.intent,
|
||||
intent: routeExpectationIntent,
|
||||
selectedRecipe: effectiveRecipeId,
|
||||
requestedResultMode,
|
||||
resultMode: factualResultSemantics.result_mode
|
||||
@@ -2527,7 +2613,7 @@ class AddressQueryService {
|
||||
routeExpectationAudit: finalRouteExpectationAudit
|
||||
});
|
||||
}
|
||||
if (intent.intent === "payables_confirmed_as_of_date" && factualResultSemantics.balance_confirmed !== true) {
|
||||
if (intent.intent === "payables_confirmed_as_of_date" && composeIntent === "payables_confirmed_as_of_date" && factualResultSemantics.balance_confirmed !== true) {
|
||||
return buildLimitedExecutionResult({
|
||||
mode,
|
||||
shape,
|
||||
|
||||
+53
-12
@@ -22,6 +22,28 @@ __WHERE_CLAUSE__
|
||||
УПОРЯДОЧИТЬ ПО
|
||||
Движения.Период __ORDER_DIRECTION__
|
||||
`;
|
||||
const PAYABLES_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
|
||||
И (__PAYABLE_ACCOUNTS_MATCH__)
|
||||
УПОРЯДОЧИТЬ ПО
|
||||
Сумма __ORDER_DIRECTION__
|
||||
`;
|
||||
const BANK_DOCS_QUERY_TEMPLATE = `
|
||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||
БанкСписание.Дата КАК Период,
|
||||
@@ -533,7 +555,8 @@ const BASE_RECIPES = [
|
||||
optional_filters: ["period_from", "period_to", "organization", "counterparty", "contract", "limit", "sort"],
|
||||
default_limit: 200,
|
||||
account_scope: ["60", "76"],
|
||||
account_scope_mode: "strict"
|
||||
account_scope_mode: "strict",
|
||||
query_template: "payables_confirmed_as_of_balance_profile"
|
||||
},
|
||||
{
|
||||
recipe_id: "address_movements_receivables_v1",
|
||||
@@ -906,17 +929,35 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
.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))
|
||||
: 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 === "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)
|
||||
: 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));
|
||||
})()
|
||||
: 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,
|
||||
|
||||
@@ -380,6 +380,12 @@ function detectValueRankingFocus(userMessage) {
|
||||
if (!text) {
|
||||
return "top_by_total";
|
||||
}
|
||||
const asksYearlyRevenueRanking = /(?:доходн|выручк|оборот|прибыл|деньг|денег|revenue|turnover|income)/iu.test(text) &&
|
||||
/(?:год|года|годы|year|years|по\s+годам)/iu.test(text) &&
|
||||
/(?:сам(?:ый|ая|ое|ые)|топ|луч|best|max|наибольш|больше)/iu.test(text);
|
||||
if (asksYearlyRevenueRanking) {
|
||||
return "top_years_by_total";
|
||||
}
|
||||
if (/(?:сам(?:ый|ая|ое|ые)\s+высок[а-яё]*|highest|largest)\s+чек|(?:max\s+check|чек\s+макс)/iu.test(text)) {
|
||||
return "top_by_max_single";
|
||||
}
|
||||
@@ -1420,6 +1426,7 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
const minOpsForAvgCheck = detectMinOpsForAvgCheck(options.userMessage);
|
||||
const normalizedQuestion = normalizeQuestionText(options.userMessage);
|
||||
const byCounterparty = new Map();
|
||||
const byYear = new Map();
|
||||
const deals = [];
|
||||
for (const row of rows) {
|
||||
const counterparty = extractCounterpartyName(row);
|
||||
@@ -1453,9 +1460,30 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
counterparty,
|
||||
amount
|
||||
});
|
||||
const year = extractYearFromIso(row.period);
|
||||
if (year !== null) {
|
||||
const yearBucket = byYear.get(year);
|
||||
if (!yearBucket) {
|
||||
byYear.set(year, {
|
||||
year,
|
||||
total: amount,
|
||||
ops: 1,
|
||||
maxSingle: amount,
|
||||
counterparties: new Set([counterparty])
|
||||
});
|
||||
}
|
||||
else {
|
||||
yearBucket.total += amount;
|
||||
yearBucket.ops += 1;
|
||||
yearBucket.maxSingle = Math.max(yearBucket.maxSingle, amount);
|
||||
yearBucket.counterparties.add(counterparty);
|
||||
}
|
||||
}
|
||||
}
|
||||
const profileRows = Array.from(byCounterparty.values());
|
||||
const yearRows = Array.from(byYear.values());
|
||||
const rankedByTotal = [...profileRows].sort((a, b) => b.total - a.total || b.ops - a.ops || a.name.localeCompare(b.name));
|
||||
const rankedByYearTotal = [...yearRows].sort((a, b) => b.total - a.total || b.ops - a.ops || a.year - b.year);
|
||||
const rankedByOps = [...profileRows].sort((a, b) => b.ops - a.ops || b.total - a.total || a.name.localeCompare(b.name));
|
||||
const rankedByMaxSingle = [...profileRows].sort((a, b) => b.maxSingle - a.maxSingle || b.total - a.total || a.name.localeCompare(b.name));
|
||||
const rankedByAvgCheck = [...profileRows]
|
||||
@@ -1485,6 +1513,23 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
text: lines.join("\n")
|
||||
};
|
||||
}
|
||||
if (focus === "top_years_by_total") {
|
||||
const visible = rankedByYearTotal.slice(0, limit);
|
||||
const heading = isSupplier
|
||||
? `Топ-${visible.length} лет по сумме выплат:`
|
||||
: `Топ-${visible.length} лет по сумме поступлений:`;
|
||||
lines.unshift(heading);
|
||||
if (visible.length === 0) {
|
||||
lines.push("По доступному окну не удалось собрать годовые агрегаты по суммам.");
|
||||
}
|
||||
else {
|
||||
lines.push(...visible.map((item, index) => `${index + 1}. ${item.year} | сумма: ${item.total} | операций: ${item.ops} | контрагентов: ${item.counterparties.size} | макс: ${item.maxSingle}`));
|
||||
}
|
||||
return {
|
||||
responseType: "FACTUAL_LIST",
|
||||
text: lines.join("\n")
|
||||
};
|
||||
}
|
||||
if (focus === "top_by_ops") {
|
||||
const visible = rankedByOps.slice(0, limit);
|
||||
const heading = isSupplier
|
||||
|
||||
@@ -3706,6 +3706,12 @@ function hasOpenContractsAddressSignal(text) {
|
||||
return hasRequestCue || hasTemporalCue;
|
||||
}
|
||||
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
||||
"period_coverage_profile",
|
||||
"document_type_and_account_section_profile",
|
||||
"counterparty_population_and_roles",
|
||||
"counterparty_activity_lifecycle",
|
||||
"customer_revenue_and_payments",
|
||||
"supplier_payouts_profile",
|
||||
"list_open_contracts",
|
||||
"open_items_by_counterparty_or_contract",
|
||||
"payables_confirmed_as_of_date",
|
||||
|
||||
Reference in New Issue
Block a user