Open-World: добавить торговый margin proxy в бизнес-обзор
This commit is contained in:
+54
-40
@@ -833,6 +833,17 @@ const BASE_RECIPES = [
|
||||
account_scope_mode: "strict",
|
||||
query_template: "inventory_sale_trace_profile"
|
||||
},
|
||||
{
|
||||
recipe_id: "address_inventory_trading_margin_proxy_for_organization_v1",
|
||||
intent: "inventory_trading_margin_proxy_for_organization",
|
||||
purpose: "Trace organization-period purchase and sale document rows and derive bounded trading revenue, purchase-cost proxy, spread, and margin proxy",
|
||||
required_filters: [],
|
||||
optional_filters: ["period_from", "period_to", "organization", "warehouse", "limit", "sort"],
|
||||
default_limit: 600,
|
||||
account_scope: ["41.01"],
|
||||
account_scope_mode: "strict",
|
||||
query_template: "inventory_trading_margin_proxy_profile"
|
||||
},
|
||||
{
|
||||
recipe_id: "address_inventory_purchase_to_sale_chain_v1",
|
||||
intent: "inventory_purchase_to_sale_chain",
|
||||
@@ -1315,6 +1326,7 @@ function maxLimitForIntent(intent) {
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_trading_margin_proxy_for_organization" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date" ||
|
||||
@@ -1498,31 +1510,15 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
? buildInventorySaleDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_profitability_profile"
|
||||
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_purchase_to_sale_chain_profile"
|
||||
: recipe.query_template === "inventory_trading_margin_proxy_profile"
|
||||
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_aging_by_purchase_date_profile"
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
|
||||
: recipe.query_template === "contracts_by_counterparty_profile"
|
||||
? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
: recipe.query_template === "open_contracts_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 OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replaceAll("__AS_OF_EXPR__", asOfExpr)
|
||||
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "62", "76"]))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
})()
|
||||
: recipe.query_template === "payables_confirmed_as_of_balance_profile"
|
||||
: recipe.query_template === "inventory_purchase_to_sale_chain_profile"
|
||||
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_aging_by_purchase_date_profile"
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
|
||||
: recipe.query_template === "contracts_by_counterparty_profile"
|
||||
? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
: recipe.query_template === "open_contracts_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)
|
||||
@@ -1534,13 +1530,13 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
? toDateTimeExpr(filters.period_from, true)
|
||||
: null) ??
|
||||
"ТЕКУЩАЯДАТА()";
|
||||
return PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||
return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replaceAll("__AS_OF_EXPR__", asOfExpr)
|
||||
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
|
||||
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "62", "76"]))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
})()
|
||||
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
|
||||
: 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)
|
||||
@@ -1552,23 +1548,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,
|
||||
|
||||
+20
-1
@@ -349,6 +349,9 @@ function headlineFor(mode, pilot) {
|
||||
if (overview.tax_position) {
|
||||
families.push("НДС-позиция");
|
||||
}
|
||||
if (overview.trading_margin_proxy) {
|
||||
families.push("торговый margin proxy");
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
families.push("долговой срез на дату");
|
||||
}
|
||||
@@ -361,7 +364,7 @@ function headlineFor(mode, pilot) {
|
||||
if (overview.inventory_position) {
|
||||
families.push("складской срез на дату");
|
||||
}
|
||||
const unknownFamilies = ["прибыль/маржа"];
|
||||
const unknownFamilies = [overview.trading_margin_proxy ? "чистая прибыль/точная маржа" : "прибыль/маржа"];
|
||||
if (!overview.tax_position) {
|
||||
unknownFamilies.push("НДС");
|
||||
}
|
||||
@@ -547,6 +550,7 @@ function buildMustNotClaim(pilot) {
|
||||
}
|
||||
if (isBusinessOverviewPilot(pilot)) {
|
||||
claims.push("Do not present business overview cash-flow spread as profit or margin.");
|
||||
claims.push("Do not present business overview trading-margin proxy as clean profit, accounting financial result, or exact cost-of-sales margin.");
|
||||
claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked.");
|
||||
claims.push("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis.");
|
||||
claims.push("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt.");
|
||||
@@ -865,6 +869,11 @@ function derivedBusinessOverviewConfirmedLines(pilot) {
|
||||
: "сбалансирован";
|
||||
lines.push(`НДС-позиция за ${overview.tax_position.period_scope}: книга продаж ${overview.tax_position.sales_vat_amount_human_ru}, книга покупок/вычеты ${overview.tax_position.purchase_vat_amount_human_ru}, нетто ${taxDirection} ${overview.tax_position.net_vat_amount_human_ru}.`);
|
||||
}
|
||||
if (overview.trading_margin_proxy) {
|
||||
const proxy = overview.trading_margin_proxy;
|
||||
const marginText = proxy.margin_to_revenue_pct === null ? "не рассчитана" : `${proxy.margin_to_revenue_pct}%`;
|
||||
lines.push(`Торговый margin proxy за ${proxy.period_scope}: выручка продаж ${proxy.sales_revenue_human_ru}, закупочный документный след ${proxy.purchase_cost_proxy_human_ru}, валовый спред proxy ${proxy.gross_spread_proxy_human_ru}, маржинальность к выручке ${marginText}. Это не чистая прибыль и не бухгалтерский финрезультат.`);
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
const debtDirection = overview.debt_position.net_debt_position_direction === "net_receivable"
|
||||
? "в пользу дебиторки"
|
||||
@@ -934,6 +943,12 @@ function businessOverviewRiskSynthesisLine(overview) {
|
||||
: "НДС-позиция сбалансирована";
|
||||
signals.push(taxDirection);
|
||||
}
|
||||
if (overview.trading_margin_proxy) {
|
||||
const marginText = overview.trading_margin_proxy.margin_to_revenue_pct === null
|
||||
? "маржинальность не рассчитана"
|
||||
: `маржинальность proxy ${overview.trading_margin_proxy.margin_to_revenue_pct}%`;
|
||||
signals.push(`торговый спред proxy ${overview.trading_margin_proxy.gross_spread_proxy_human_ru}, ${marginText}`);
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
const debtDirection = overview.debt_position.net_debt_position_direction === "net_receivable"
|
||||
? `дебиторка больше кредиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
|
||||
@@ -962,6 +977,7 @@ function businessOverviewRiskSynthesisLine(overview) {
|
||||
function businessOverviewExecutiveVerdictLine(overview) {
|
||||
const hasCash = overview.incoming_customer_revenue.rows_with_amount > 0 || overview.outgoing_supplier_payout.rows_with_amount > 0;
|
||||
const hasExtraSignals = Boolean(overview.tax_position ||
|
||||
overview.trading_margin_proxy ||
|
||||
overview.debt_position ||
|
||||
overview.debt_open_settlement_quality ||
|
||||
overview.inventory_position);
|
||||
@@ -1038,6 +1054,9 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||
if (pilot.derived_business_overview?.tax_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_tax_position");
|
||||
}
|
||||
if (pilot.derived_business_overview?.trading_margin_proxy) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_trading_margin_proxy");
|
||||
}
|
||||
if (pilot.derived_business_overview?.debt_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_debt_position");
|
||||
}
|
||||
|
||||
+157
-1
@@ -205,6 +205,21 @@ function buildBusinessOverviewInventoryFilters(planner) {
|
||||
sort: "period_asc"
|
||||
};
|
||||
}
|
||||
function buildBusinessOverviewTradingMarginFilters(planner) {
|
||||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||||
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
|
||||
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
|
||||
const periodFilters = dateScopeToFilters(dateScope);
|
||||
if (!periodFilters.period_from || !periodFilters.period_to) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...periodFilters,
|
||||
...(organization ? { organization } : {}),
|
||||
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||||
sort: "period_asc"
|
||||
};
|
||||
}
|
||||
function buildInventoryExactFilters(planner) {
|
||||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||||
const subject = firstEntityCandidate(planner);
|
||||
@@ -1454,6 +1469,16 @@ function rowDocumentValue(row) {
|
||||
function rowAccountValue(row) {
|
||||
return rowTextValue(row, ["СчетДт", "AccountDt", "account_dt", "Счет", "Account", "account"]);
|
||||
}
|
||||
function rowDebitAccountValue(row) {
|
||||
return rowTextValue(row, ["СчетДт", "AccountDt", "account_dt", "DebitAccount", "debit_account"]);
|
||||
}
|
||||
function rowCreditAccountValue(row) {
|
||||
return rowTextValue(row, ["СчетКт", "AccountKt", "account_kt", "CreditAccount", "credit_account"]);
|
||||
}
|
||||
function accountTextMatchesPrefix(account, prefixes) {
|
||||
const normalized = String(account ?? "").replace(/\s+/g, "");
|
||||
return prefixes.some((prefix) => normalized.startsWith(prefix));
|
||||
}
|
||||
function rowQuantityValue(row) {
|
||||
return rowNumberValue(row, ["Количество", "Quantity", "quantity", "Qty", "qty", "Остаток", "Balance", "balance"]);
|
||||
}
|
||||
@@ -1961,6 +1986,87 @@ function deriveBusinessOverviewTaxPosition(result, periodScope) {
|
||||
inference_basis: "sales_book_minus_purchase_book_confirmed_1c_vat_rows"
|
||||
};
|
||||
}
|
||||
function deriveBusinessOverviewTradingMarginProxy(result, periodScope) {
|
||||
if (!result || result.error || result.matched_rows <= 0 || !periodScope) {
|
||||
return null;
|
||||
}
|
||||
const itemBuckets = new Map();
|
||||
let salesRowsWithAmount = 0;
|
||||
let purchaseRowsWithAmount = 0;
|
||||
let salesRevenue = 0;
|
||||
let purchaseCostProxy = 0;
|
||||
for (const row of result.rows) {
|
||||
const amount = rowAmountValue(row);
|
||||
if (amount === null || amount <= 0) {
|
||||
continue;
|
||||
}
|
||||
const isSale = accountTextMatchesPrefix(rowCreditAccountValue(row), ["41.01"]);
|
||||
const isPurchase = accountTextMatchesPrefix(rowDebitAccountValue(row), ["41.01"]);
|
||||
if (!isSale && !isPurchase) {
|
||||
continue;
|
||||
}
|
||||
const item = rowInventoryItemValue(row) ?? "unknown_item";
|
||||
const quantity = rowQuantityValue(row) ?? 0;
|
||||
const bucket = itemBuckets.get(item) ?? {
|
||||
sales_revenue: 0,
|
||||
purchase_cost_proxy: 0,
|
||||
sales_quantity: 0,
|
||||
purchase_quantity: 0
|
||||
};
|
||||
if (isSale) {
|
||||
salesRowsWithAmount += 1;
|
||||
salesRevenue += amount;
|
||||
bucket.sales_revenue += amount;
|
||||
bucket.sales_quantity += quantity;
|
||||
}
|
||||
if (isPurchase) {
|
||||
purchaseRowsWithAmount += 1;
|
||||
purchaseCostProxy += amount;
|
||||
bucket.purchase_cost_proxy += amount;
|
||||
bucket.purchase_quantity += quantity;
|
||||
}
|
||||
itemBuckets.set(item, bucket);
|
||||
}
|
||||
if (salesRowsWithAmount <= 0 && purchaseRowsWithAmount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const grossSpreadProxy = salesRevenue - purchaseCostProxy;
|
||||
const marginToRevenuePct = salesRevenue > 0 ? percentageOfTotal(grossSpreadProxy, salesRevenue) : null;
|
||||
const markupToPurchasePct = purchaseCostProxy > 0 ? percentageOfTotal(grossSpreadProxy, purchaseCostProxy) : null;
|
||||
const topItemsBySales = Array.from(itemBuckets.entries())
|
||||
.map(([item, bucket]) => ({
|
||||
item,
|
||||
sales_revenue: bucket.sales_revenue,
|
||||
sales_revenue_human_ru: formatAmountHumanRu(bucket.sales_revenue),
|
||||
purchase_cost_proxy: bucket.purchase_cost_proxy,
|
||||
purchase_cost_proxy_human_ru: formatAmountHumanRu(bucket.purchase_cost_proxy),
|
||||
gross_spread_proxy: bucket.sales_revenue - bucket.purchase_cost_proxy,
|
||||
gross_spread_proxy_human_ru: formatAmountHumanRu(bucket.sales_revenue - bucket.purchase_cost_proxy),
|
||||
sales_quantity: bucket.sales_quantity,
|
||||
purchase_quantity: bucket.purchase_quantity
|
||||
}))
|
||||
.sort((left, right) => {
|
||||
const salesDelta = right.sales_revenue - left.sales_revenue;
|
||||
return salesDelta !== 0 ? salesDelta : left.item.localeCompare(right.item, "ru");
|
||||
})
|
||||
.slice(0, 5);
|
||||
return {
|
||||
period_scope: periodScope,
|
||||
rows_matched: result.matched_rows,
|
||||
sales_rows_with_amount: salesRowsWithAmount,
|
||||
purchase_rows_with_amount: purchaseRowsWithAmount,
|
||||
sales_revenue: salesRevenue,
|
||||
sales_revenue_human_ru: formatAmountHumanRu(salesRevenue),
|
||||
purchase_cost_proxy: purchaseCostProxy,
|
||||
purchase_cost_proxy_human_ru: formatAmountHumanRu(purchaseCostProxy),
|
||||
gross_spread_proxy: grossSpreadProxy,
|
||||
gross_spread_proxy_human_ru: formatAmountHumanRu(grossSpreadProxy),
|
||||
margin_to_revenue_pct: marginToRevenuePct,
|
||||
markup_to_purchase_pct: markupToPurchasePct,
|
||||
top_items_by_sales: topItemsBySales,
|
||||
inference_basis: "sales_documents_minus_purchase_documents_confirmed_1c_rows"
|
||||
};
|
||||
}
|
||||
function deriveBusinessOverviewDebtSide(result) {
|
||||
if (!result || result.error || result.matched_rows <= 0) {
|
||||
return {
|
||||
@@ -2303,6 +2409,7 @@ function deriveBusinessOverview(input) {
|
||||
});
|
||||
const activityPeriod = deriveActivityPeriod(input.lifecycleResult);
|
||||
const taxPosition = deriveBusinessOverviewTaxPosition(input.taxResult, input.periodScope);
|
||||
const tradingMarginProxy = deriveBusinessOverviewTradingMarginProxy(input.tradingMarginResult, input.periodScope);
|
||||
const debtPosition = deriveBusinessOverviewDebtPosition({
|
||||
receivablesResult: input.receivablesResult,
|
||||
payablesResult: input.payablesResult,
|
||||
@@ -2322,6 +2429,7 @@ function deriveBusinessOverview(input) {
|
||||
outgoing.rows_with_amount > 0,
|
||||
Boolean(activityPeriod),
|
||||
Boolean(taxPosition),
|
||||
Boolean(tradingMarginProxy),
|
||||
Boolean(debtPosition),
|
||||
Boolean(debtOpenSettlementQuality),
|
||||
Boolean(inventoryPosition)
|
||||
@@ -2341,13 +2449,14 @@ function deriveBusinessOverview(input) {
|
||||
top_customers: rankedIncoming?.ranked_values ?? [],
|
||||
activity_period: activityPeriod,
|
||||
tax_position: taxPosition,
|
||||
trading_margin_proxy: tradingMarginProxy,
|
||||
debt_position: debtPosition,
|
||||
debt_open_settlement_quality: debtOpenSettlementQuality,
|
||||
inventory_position: inventoryPosition,
|
||||
coverage_limited_by_probe_limit: incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
|
||||
checked_signal_count: checkedSignalCount,
|
||||
missing_signal_families: [
|
||||
"profit_margin",
|
||||
tradingMarginProxy ? "accounting_profit_margin" : "profit_margin",
|
||||
debtPosition ? null : "debt_position",
|
||||
debtOpenSettlementQuality ? "debt_due_date_aging_quality" : "debt_open_settlement_quality",
|
||||
taxPosition ? null : "tax_position",
|
||||
@@ -2381,6 +2490,9 @@ function summarizeBusinessOverviewRows(input) {
|
||||
if (input.taxResult && !input.taxResult.error) {
|
||||
parts.push(`${input.taxResult.fetched_rows} VAT/tax rows fetched, ${input.taxResult.matched_rows} matched`);
|
||||
}
|
||||
if (input.tradingMarginResult && !input.tradingMarginResult.error) {
|
||||
parts.push(`${input.tradingMarginResult.fetched_rows} trading-margin document rows fetched, ${input.tradingMarginResult.matched_rows} matched`);
|
||||
}
|
||||
if (input.receivablesResult && !input.receivablesResult.error) {
|
||||
parts.push(`${input.receivablesResult.fetched_rows} receivables rows fetched, ${input.receivablesResult.matched_rows} matched`);
|
||||
}
|
||||
@@ -2426,6 +2538,11 @@ function buildBusinessOverviewConfirmedFacts(derived) {
|
||||
: "сбалансирован";
|
||||
facts.push(`НДС-позиция за ${derived.tax_position.period_scope} подтверждена по книгам продаж/покупок: продажи ${derived.tax_position.sales_vat_amount_human_ru}, покупки/вычеты ${derived.tax_position.purchase_vat_amount_human_ru}, нетто ${taxDirection} ${derived.tax_position.net_vat_amount_human_ru}.`);
|
||||
}
|
||||
if (derived.trading_margin_proxy) {
|
||||
const proxy = derived.trading_margin_proxy;
|
||||
const marginText = proxy.margin_to_revenue_pct === null ? "не рассчитана" : `${proxy.margin_to_revenue_pct}%`;
|
||||
facts.push(`Торговый margin proxy за ${proxy.period_scope} подтвержден по товарным документам продаж/поступлений: выручка продаж ${proxy.sales_revenue_human_ru}, закупочный документный след ${proxy.purchase_cost_proxy_human_ru}, валовый спред proxy ${proxy.gross_spread_proxy_human_ru}, маржинальность к выручке ${marginText}. Это не чистая прибыль и не бухгалтерский финрезультат.`);
|
||||
}
|
||||
if (derived.debt_position) {
|
||||
const debtDirection = derived.debt_position.net_debt_position_direction === "net_receivable"
|
||||
? "в пользу дебиторки"
|
||||
@@ -2495,6 +2612,9 @@ function buildBusinessOverviewUnknownFacts(derived) {
|
||||
missing.has("profit_margin")
|
||||
? "Прибыль и маржа этим бизнес-обзором не подтверждены: нужны себестоимость, расходы и закрывающие документы."
|
||||
: null,
|
||||
missing.has("accounting_profit_margin")
|
||||
? "Чистая прибыль, бухгалтерская маржа и финрезультат этим бизнес-обзором не подтверждены: торговый proxy показывает только документную разницу продаж и поступлений без расходов, закрытия периода и точной себестоимости продаж."
|
||||
: null,
|
||||
missing.has("debt_quality")
|
||||
? "Качество дебиторки/кредиторки этим бизнес-обзором не подтверждено: нужен отдельный долговой срез."
|
||||
: null,
|
||||
@@ -3123,6 +3243,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
let outgoingResult = null;
|
||||
let lifecycleResult = null;
|
||||
let taxResult = null;
|
||||
let tradingMarginResult = null;
|
||||
let receivablesResult = null;
|
||||
let payablesResult = null;
|
||||
let openContractsResult = null;
|
||||
@@ -3131,6 +3252,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
const valueFilters = buildValueFlowFilters(planner);
|
||||
const lifecycleFilters = buildLifecycleFilters(planner);
|
||||
const taxFilters = buildBusinessOverviewTaxFilters(planner);
|
||||
const tradingMarginFilters = buildBusinessOverviewTradingMarginFilters(planner);
|
||||
const debtFilters = buildBusinessOverviewDebtFilters(planner);
|
||||
const inventoryFilters = buildBusinessOverviewInventoryFilters(planner);
|
||||
const debtAsOfDate = toNonEmptyString(debtFilters?.as_of_date);
|
||||
@@ -3141,6 +3263,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
const taxSelection = taxFilters
|
||||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("vat_liability_confirmed_for_tax_period", taxFilters)
|
||||
: null;
|
||||
const tradingMarginSelection = tradingMarginFilters
|
||||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("inventory_trading_margin_proxy_for_organization", tradingMarginFilters)
|
||||
: null;
|
||||
const receivablesSelection = debtFilters
|
||||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("receivables_confirmed_as_of_date", debtFilters)
|
||||
: null;
|
||||
@@ -3196,6 +3321,16 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
pushReason(reasonCodes, "pilot_business_overview_tax_recipe_not_available");
|
||||
pushUnique(queryLimitations, "Business overview VAT/tax probe requires an executable tax-period recipe");
|
||||
}
|
||||
if (tradingMarginSelection?.selected_recipe) {
|
||||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_recipe_selected");
|
||||
}
|
||||
else if (!tradingMarginFilters) {
|
||||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_probe_skipped_without_explicit_period");
|
||||
}
|
||||
else {
|
||||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_recipe_not_available");
|
||||
pushUnique(queryLimitations, "Business overview trading-margin proxy requires an executable explicit-period purchase/sale document recipe");
|
||||
}
|
||||
if (receivablesSelection?.selected_recipe && payablesSelection?.selected_recipe) {
|
||||
pushReason(reasonCodes, "pilot_business_overview_debt_recipes_selected");
|
||||
}
|
||||
@@ -3390,6 +3525,15 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
});
|
||||
pushUnique(executedPrimitives, step.primitive_id);
|
||||
probeResults.push(queryResultToProbeResult(step.primitive_id, lifecycleResult));
|
||||
if (tradingMarginSelection?.selected_recipe) {
|
||||
const tradingMarginPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(tradingMarginSelection.selected_recipe, tradingMarginFilters);
|
||||
tradingMarginResult = await runtimeDeps.executeAddressMcpQuery({
|
||||
query: tradingMarginPlan.query,
|
||||
limit: tradingMarginPlan.limit,
|
||||
account_scope: tradingMarginPlan.account_scope
|
||||
});
|
||||
probeResults.push(queryResultToProbeResult(step.primitive_id, tradingMarginResult));
|
||||
}
|
||||
if (lifecycleResult.error) {
|
||||
pushUnique(queryLimitations, lifecycleResult.error);
|
||||
pushReason(reasonCodes, "pilot_business_overview_query_documents_mcp_error");
|
||||
@@ -3397,6 +3541,13 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
else {
|
||||
pushReason(reasonCodes, "pilot_business_overview_query_documents_mcp_executed");
|
||||
}
|
||||
if (tradingMarginResult?.error) {
|
||||
pushUnique(queryLimitations, tradingMarginResult.error);
|
||||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_query_mcp_error");
|
||||
}
|
||||
else if (tradingMarginResult) {
|
||||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_query_mcp_executed");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
skippedPrimitives.push(step.primitive_id);
|
||||
@@ -3407,6 +3558,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
outgoingResult,
|
||||
lifecycleResult,
|
||||
taxResult,
|
||||
tradingMarginResult,
|
||||
receivablesResult,
|
||||
payablesResult,
|
||||
openContractsResult,
|
||||
@@ -3428,6 +3580,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
if (derivedBusinessOverview.tax_position) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_tax_position_from_confirmed_rows");
|
||||
}
|
||||
if (derivedBusinessOverview.trading_margin_proxy) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_trading_margin_proxy_from_confirmed_rows");
|
||||
}
|
||||
if (derivedBusinessOverview.debt_position) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_debt_position_from_confirmed_rows");
|
||||
}
|
||||
@@ -3446,6 +3601,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
outgoingResult,
|
||||
lifecycleResult,
|
||||
taxResult,
|
||||
tradingMarginResult,
|
||||
receivablesResult,
|
||||
payablesResult,
|
||||
openContractsResult,
|
||||
|
||||
Reference in New Issue
Block a user