Open-World: добавить sales-to-stock proxy в бизнес-обзор
This commit is contained in:
+25
-1
@@ -364,6 +364,9 @@ function headlineFor(mode, pilot) {
|
||||
if (overview.inventory_position) {
|
||||
families.push("складской срез на дату");
|
||||
}
|
||||
if (overview.inventory_turnover_proxy) {
|
||||
families.push("оборотный proxy склада");
|
||||
}
|
||||
const unknownFamilies = [overview.trading_margin_proxy ? "чистая прибыль/точная маржа" : "прибыль/маржа"];
|
||||
if (!overview.tax_position) {
|
||||
unknownFamilies.push("НДС");
|
||||
@@ -555,6 +558,7 @@ function buildMustNotClaim(pilot) {
|
||||
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.");
|
||||
claims.push("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health.");
|
||||
claims.push("Do not present business overview inventory turnover proxy as full inventory liquidity, FIFO turnover, obsolescence analysis, or liquidation value.");
|
||||
claims.push("Do not expose business_overview_route_template_v1 or MCP primitive names in the user answer.");
|
||||
}
|
||||
if (pilot.derived_ranked_value_flow) {
|
||||
@@ -909,6 +913,16 @@ function derivedBusinessOverviewConfirmedLines(pilot) {
|
||||
lines.push(`Возрастной сигнал склада: самая ранняя найденная дата закупки ${overview.inventory_position.aging_signal.oldest_purchase_date}${ageText}.`);
|
||||
}
|
||||
}
|
||||
if (overview.inventory_turnover_proxy) {
|
||||
const proxy = overview.inventory_turnover_proxy;
|
||||
const ratioText = proxy.sales_to_stock_amount_ratio === null
|
||||
? "не рассчитано"
|
||||
: `${proxy.sales_to_stock_amount_ratio}x`;
|
||||
const stockShareText = proxy.stock_to_sales_revenue_pct === null
|
||||
? "не рассчитана"
|
||||
: `${proxy.stock_to_sales_revenue_pct}%`;
|
||||
lines.push(`Оборотный proxy склада за ${proxy.period_scope}: продажи ${proxy.sales_revenue_human_ru}, остаток на ${proxy.as_of_date} ${proxy.inventory_amount_human_ru}, sales-to-stock ratio ${ratioText}, остаток к продажам ${stockShareText}. Это не полноценная складская ликвидность, не FIFO-оборачиваемость и не анализ устаревания.`);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
function businessOverviewCashSynthesisLine(overview) {
|
||||
@@ -970,6 +984,12 @@ function businessOverviewRiskSynthesisLine(overview) {
|
||||
signals.push(`самый старый складской purchase-date сигнал ${overview.inventory_position.aging_signal.max_age_days} дн.`);
|
||||
}
|
||||
}
|
||||
if (overview.inventory_turnover_proxy) {
|
||||
const ratioText = overview.inventory_turnover_proxy.sales_to_stock_amount_ratio === null
|
||||
? "sales-to-stock не рассчитан"
|
||||
: `sales-to-stock ${overview.inventory_turnover_proxy.sales_to_stock_amount_ratio}x`;
|
||||
signals.push(`оборотный proxy склада: ${ratioText}`);
|
||||
}
|
||||
return signals.length > 0
|
||||
? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.`
|
||||
: null;
|
||||
@@ -980,7 +1000,8 @@ function businessOverviewExecutiveVerdictLine(overview) {
|
||||
overview.trading_margin_proxy ||
|
||||
overview.debt_position ||
|
||||
overview.debt_open_settlement_quality ||
|
||||
overview.inventory_position);
|
||||
overview.inventory_position ||
|
||||
overview.inventory_turnover_proxy);
|
||||
if (!hasCash && !hasExtraSignals) {
|
||||
return null;
|
||||
}
|
||||
@@ -1069,6 +1090,9 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||
if (pilot.derived_business_overview?.inventory_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_inventory_position");
|
||||
}
|
||||
if (pilot.derived_business_overview?.inventory_turnover_proxy) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_inventory_turnover_proxy");
|
||||
}
|
||||
const confirmedLines = businessOverviewLines.length > 0
|
||||
? businessOverviewLines
|
||||
: pilot.derived_ranked_value_flow && derivedValueLine
|
||||
|
||||
+50
-2
@@ -2144,6 +2144,12 @@ function percentageOfTotal(part, total) {
|
||||
}
|
||||
return Math.round((part / total) * 10_000) / 100;
|
||||
}
|
||||
function ratioOfTotal(part, total) {
|
||||
if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.round((part / total) * 100) / 100;
|
||||
}
|
||||
function deriveBusinessOverviewDebtOpenSettlementQuality(input) {
|
||||
if (!input.debtAsOfDate || !input.openContractsResult || input.openContractsResult.error || input.openContractsResult.matched_rows <= 0) {
|
||||
return null;
|
||||
@@ -2398,6 +2404,26 @@ function deriveBusinessOverviewInventoryPosition(input) {
|
||||
inference_basis: "inventory_on_hand_confirmed_1c_balance_rows"
|
||||
};
|
||||
}
|
||||
function deriveBusinessOverviewInventoryTurnoverProxy(input) {
|
||||
const { inventoryPosition, tradingMarginProxy } = input;
|
||||
if (!inventoryPosition || !tradingMarginProxy) {
|
||||
return null;
|
||||
}
|
||||
if (inventoryPosition.total_amount <= 0 || tradingMarginProxy.sales_revenue <= 0) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
period_scope: tradingMarginProxy.period_scope,
|
||||
as_of_date: inventoryPosition.as_of_date,
|
||||
sales_revenue: tradingMarginProxy.sales_revenue,
|
||||
sales_revenue_human_ru: tradingMarginProxy.sales_revenue_human_ru,
|
||||
inventory_amount: inventoryPosition.total_amount,
|
||||
inventory_amount_human_ru: inventoryPosition.total_amount_human_ru,
|
||||
sales_to_stock_amount_ratio: ratioOfTotal(tradingMarginProxy.sales_revenue, inventoryPosition.total_amount),
|
||||
stock_to_sales_revenue_pct: percentageOfTotal(inventoryPosition.total_amount, tradingMarginProxy.sales_revenue),
|
||||
inference_basis: "sales_document_revenue_vs_inventory_balance_confirmed_1c_rows"
|
||||
};
|
||||
}
|
||||
function deriveBusinessOverview(input) {
|
||||
const incoming = deriveValueFlowSideSummary(input.incomingResult);
|
||||
const outgoing = deriveValueFlowSideSummary(input.outgoingResult);
|
||||
@@ -2424,6 +2450,10 @@ function deriveBusinessOverview(input) {
|
||||
inventoryAgingResult: input.inventoryAgingResult,
|
||||
inventoryAsOfDate: input.inventoryAsOfDate
|
||||
});
|
||||
const inventoryTurnoverProxy = deriveBusinessOverviewInventoryTurnoverProxy({
|
||||
inventoryPosition,
|
||||
tradingMarginProxy
|
||||
});
|
||||
const checkedSignalCount = [
|
||||
incoming.rows_with_amount > 0,
|
||||
outgoing.rows_with_amount > 0,
|
||||
@@ -2432,7 +2462,8 @@ function deriveBusinessOverview(input) {
|
||||
Boolean(tradingMarginProxy),
|
||||
Boolean(debtPosition),
|
||||
Boolean(debtOpenSettlementQuality),
|
||||
Boolean(inventoryPosition)
|
||||
Boolean(inventoryPosition),
|
||||
Boolean(inventoryTurnoverProxy)
|
||||
].filter(Boolean).length;
|
||||
if (checkedSignalCount <= 0) {
|
||||
return null;
|
||||
@@ -2453,6 +2484,7 @@ function deriveBusinessOverview(input) {
|
||||
debt_position: debtPosition,
|
||||
debt_open_settlement_quality: debtOpenSettlementQuality,
|
||||
inventory_position: inventoryPosition,
|
||||
inventory_turnover_proxy: inventoryTurnoverProxy,
|
||||
coverage_limited_by_probe_limit: incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
|
||||
checked_signal_count: checkedSignalCount,
|
||||
missing_signal_families: [
|
||||
@@ -2460,7 +2492,7 @@ function deriveBusinessOverview(input) {
|
||||
debtPosition ? null : "debt_position",
|
||||
debtOpenSettlementQuality ? "debt_due_date_aging_quality" : "debt_open_settlement_quality",
|
||||
taxPosition ? null : "tax_position",
|
||||
inventoryPosition ? "inventory_turnover_quality" : "inventory_position",
|
||||
inventoryPosition ? (inventoryTurnoverProxy ? "inventory_liquidity_quality" : "inventory_turnover_quality") : "inventory_position",
|
||||
inventoryPosition?.aging_signal ? null : "inventory_aging_quality"
|
||||
].filter((item) => Boolean(item)),
|
||||
inference_basis: inventoryPosition
|
||||
@@ -2581,6 +2613,16 @@ function buildBusinessOverviewConfirmedFacts(derived) {
|
||||
facts.push(`Возрастной сигнал склада подтвержден по найденным строкам закупок: самая ранняя дата ${derived.inventory_position.aging_signal.oldest_purchase_date}${ageText}.`);
|
||||
}
|
||||
}
|
||||
if (derived.inventory_turnover_proxy) {
|
||||
const proxy = derived.inventory_turnover_proxy;
|
||||
const ratioText = proxy.sales_to_stock_amount_ratio === null
|
||||
? "не рассчитано"
|
||||
: `${proxy.sales_to_stock_amount_ratio}x`;
|
||||
const stockShareText = proxy.stock_to_sales_revenue_pct === null
|
||||
? "не рассчитана"
|
||||
: `${proxy.stock_to_sales_revenue_pct}%`;
|
||||
facts.push(`Оборотный proxy склада за ${proxy.period_scope} подтвержден по продажным документам и складскому остатку: продажи ${proxy.sales_revenue_human_ru}, остаток на ${proxy.as_of_date} ${proxy.inventory_amount_human_ru}, sales-to-stock ratio ${ratioText}, остаток к продажам ${stockShareText}. Это не полноценная складская ликвидность, не FIFO-оборачиваемость и не анализ устаревания.`);
|
||||
}
|
||||
return facts;
|
||||
}
|
||||
function buildBusinessOverviewInferredFacts(derived) {
|
||||
@@ -2644,6 +2686,9 @@ function buildBusinessOverviewUnknownFacts(derived) {
|
||||
: null,
|
||||
missing.has("inventory_turnover_quality")
|
||||
? "Скорость продаж, оборачиваемость и ликвидность склада этим бизнес-обзором не подтверждены: нужен отдельный inventory/продажный анализ, а не только остаток на дату."
|
||||
: null,
|
||||
missing.has("inventory_liquidity_quality")
|
||||
? "Полная складская ликвидность этим бизнес-обзором не подтверждена: sales-to-stock proxy показывает только соотношение продажных документов и остатка на дату, без FIFO-оборачиваемости, устаревания, резервов и ликвидационной стоимости."
|
||||
: null
|
||||
].filter((item) => Boolean(item));
|
||||
if (derived?.coverage_limited_by_probe_limit) {
|
||||
@@ -3595,6 +3640,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
if (derivedBusinessOverview.inventory_position) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_position_from_confirmed_rows");
|
||||
}
|
||||
if (derivedBusinessOverview.inventory_turnover_proxy) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_turnover_proxy_from_confirmed_rows");
|
||||
}
|
||||
}
|
||||
const sourceRowsSummary = summarizeBusinessOverviewRows({
|
||||
incomingResult,
|
||||
|
||||
Reference in New Issue
Block a user