Open-World: добавить sales-to-stock proxy в бизнес-обзор

This commit is contained in:
2026-05-04 10:22:08 +03:00
parent 062655eca0
commit bf3ae110ef
9 changed files with 251 additions and 20 deletions
@@ -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
@@ -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,
@@ -467,6 +467,9 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
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("НДС");
@@ -666,6 +669,7 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
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) {
@@ -1079,6 +1083,18 @@ function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilot
);
}
}
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;
}
@@ -1145,6 +1161,12 @@ function businessOverviewRiskSynthesisLine(overview: BusinessOverview): string |
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;
@@ -1157,7 +1179,8 @@ function businessOverviewExecutiveVerdictLine(overview: BusinessOverview): strin
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;
@@ -1256,6 +1279,9 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
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
@@ -156,6 +156,7 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverview {
debt_position: AssistantMcpDiscoveryDerivedBusinessOverviewDebtPosition | null;
debt_open_settlement_quality: AssistantMcpDiscoveryDerivedBusinessOverviewDebtOpenSettlementQuality | null;
inventory_position: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition | null;
inventory_turnover_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy | null;
coverage_limited_by_probe_limit: boolean;
checked_signal_count: number;
missing_signal_families: string[];
@@ -306,6 +307,18 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition {
inference_basis: "inventory_on_hand_confirmed_1c_balance_rows";
}
export interface AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy {
period_scope: string;
as_of_date: string;
sales_revenue: number;
sales_revenue_human_ru: string;
inventory_amount: number;
inventory_amount_human_ru: string;
sales_to_stock_amount_ratio: number | null;
stock_to_sales_revenue_pct: number | null;
inference_basis: "sales_document_revenue_vs_inventory_balance_confirmed_1c_rows";
}
export interface AssistantMcpDiscoveryDerivedMetadataSurface {
metadata_scope: string | null;
requested_meta_types: string[];
@@ -2918,6 +2931,13 @@ function percentageOfTotal(part: number, total: number): number | null {
return Math.round((part / total) * 10_000) / 100;
}
function ratioOfTotal(part: number, total: number): number | null {
if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) {
return null;
}
return Math.round((part / total) * 100) / 100;
}
function deriveBusinessOverviewDebtOpenSettlementQuality(input: {
openContractsResult: AddressMcpQueryExecutorResult | null;
debtAsOfDate: string | null;
@@ -3208,6 +3228,31 @@ function deriveBusinessOverviewInventoryPosition(input: {
};
}
function deriveBusinessOverviewInventoryTurnoverProxy(input: {
inventoryPosition: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition | null;
tradingMarginProxy: AssistantMcpDiscoveryDerivedBusinessOverviewTradingMarginProxy | null;
}): AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy | null {
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: {
incomingResult: AssistantMcpDiscoveryCoverageAwareQueryResult | null;
outgoingResult: AssistantMcpDiscoveryCoverageAwareQueryResult | null;
@@ -3249,6 +3294,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,
@@ -3257,7 +3306,8 @@ function deriveBusinessOverview(input: {
Boolean(tradingMarginProxy),
Boolean(debtPosition),
Boolean(debtOpenSettlementQuality),
Boolean(inventoryPosition)
Boolean(inventoryPosition),
Boolean(inventoryTurnoverProxy)
].filter(Boolean).length;
if (checkedSignalCount <= 0) {
return null;
@@ -3279,6 +3329,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,
@@ -3287,7 +3338,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): item is string => Boolean(item)),
inference_basis:
@@ -3446,6 +3497,18 @@ function buildBusinessOverviewConfirmedFacts(derived: AssistantMcpDiscoveryDeriv
);
}
}
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;
}
@@ -3505,8 +3568,7 @@ function buildBusinessOverviewUnknownFacts(derived: AssistantMcpDiscoveryDerived
: null,
missing.has("inventory_health")
? "Складская ликвидность и товарные остатки этим бизнес-обзором не подтверждены: нужен отдельный inventory-срез."
: null
,
: null,
missing.has("inventory_position")
? "Складской остаток этим бизнес-обзором не подтвержден: нужен отдельный inventory-срез на явную дату."
: null,
@@ -3515,6 +3577,9 @@ function buildBusinessOverviewUnknownFacts(derived: AssistantMcpDiscoveryDerived
: null,
missing.has("inventory_turnover_quality")
? "Скорость продаж, оборачиваемость и ликвидность склада этим бизнес-обзором не подтверждены: нужен отдельный inventory/продажный анализ, а не только остаток на дату."
: null,
missing.has("inventory_liquidity_quality")
? "Полная складская ликвидность этим бизнес-обзором не подтверждена: sales-to-stock proxy показывает только соотношение продажных документов и остатка на дату, без FIFO-оборачиваемости, устаревания, резервов и ликвидационной стоимости."
: null
].filter((item): item is string => Boolean(item));
if (derived?.coverage_limited_by_probe_limit) {
@@ -4581,6 +4646,9 @@ export async function executeAssistantMcpDiscoveryPilot(
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,
@@ -474,18 +474,30 @@ describe("assistant MCP discovery answer adapter", () => {
{ Period: "2020-01-10T00:00:00", Amount: 200000, Quantity: 8, Item: "Товар А" }
]
},
{ rows: [{ Period: "2020-01-15T00:00:00", Registrator: "Поступление 1" }] }
{ rows: [{ Period: "2020-01-15T00:00:00", Registrator: "Поступление 1" }] },
{
rows: [
{ Period: "2020-03-01T00:00:00", Amount: 600000, Item: "Товар А", Counterparty: "Клиент А", AccountKt: "41.01" },
{ Period: "2020-02-01T00:00:00", Amount: 240000, Item: "Товар А", Counterparty: "Поставщик А", AccountDt: "41.01" }
]
}
])
);
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
expect(draft.headline).toContain("складской срез");
expect(draft.headline).toContain("оборотный proxy склада");
expect(draft.confirmed_lines.join("\n")).toContain("Складской срез на 2020-12-31");
expect(draft.confirmed_lines.join("\n")).toContain("Товар А");
expect(draft.unknown_lines.join("\n")).toContain("оборачиваемость");
expect(draft.confirmed_lines.join("\n")).toContain("Оборотный proxy склада за 2020");
expect(draft.confirmed_lines.join("\n")).toContain("sales-to-stock ratio 2x");
expect(draft.inference_lines.join("\n")).toContain("оборотный proxy склада");
expect(draft.unknown_lines.join("\n")).toContain("Полная складская ликвидность");
expect(draft.reason_codes).toContain("answer_contains_business_overview_inventory_position");
expect(draft.reason_codes).toContain("answer_contains_business_overview_inventory_turnover_proxy");
expect(draft.must_not_claim).toContain("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health.");
expect(draft.must_not_claim).toContain("Do not present business overview inventory turnover proxy as full inventory liquidity, FIFO turnover, obsolescence analysis, or liquidation value.");
});
it("renders metadata-scoped movement all-time follow-up as an all-time bounded answer", async () => {
@@ -553,7 +553,12 @@ describe("assistant MCP discovery pilot executor", () => {
{ Period: "2020-12-15T00:00:00", Registrator: "Поступление 2" }
]
},
{ rows: [] }
{
rows: [
{ Period: "2020-03-01T00:00:00", Amount: 600000, Item: "Товар А", Counterparty: "Клиент А", AccountKt: "41.01" },
{ Period: "2020-02-01T00:00:00", Amount: 240000, Item: "Товар А", Counterparty: "Поставщик А", AccountDt: "41.01" }
]
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
@@ -579,12 +584,24 @@ describe("assistant MCP discovery pilot executor", () => {
total_amount: 250000,
total_quantity: 10
});
expect(result.derived_business_overview?.inventory_turnover_proxy).toMatchObject({
period_scope: "2020",
as_of_date: "2020-12-31",
sales_revenue: 600000,
inventory_amount: 300000,
sales_to_stock_amount_ratio: 2,
stock_to_sales_revenue_pct: 50
});
expect(result.derived_business_overview?.missing_signal_families).not.toContain("inventory_position");
expect(result.derived_business_overview?.missing_signal_families).toContain("inventory_turnover_quality");
expect(result.derived_business_overview?.missing_signal_families).not.toContain("inventory_turnover_quality");
expect(result.derived_business_overview?.missing_signal_families).toContain("inventory_liquidity_quality");
expect(result.evidence.confirmed_facts.join("\n")).toContain("Складской срез на 2020-12-31");
expect(result.evidence.unknown_facts.join("\n")).toContain("оборачиваемость");
expect(result.evidence.confirmed_facts.join("\n")).toContain("Оборотный proxy склада за 2020");
expect(result.evidence.confirmed_facts.join("\n")).toContain("sales-to-stock ratio 2x");
expect(result.evidence.unknown_facts.join("\n")).toContain("Полная складская ликвидность");
expect(result.reason_codes).toContain("pilot_business_overview_inventory_query_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_business_overview_inventory_position_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_derived_business_overview_inventory_turnover_proxy_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(10);
const inventoryCall = deps.executeAddressMcpQuery.mock.calls[6]?.[0];
expect(inventoryCall?.account_scope).toContain("41.01");