Open-World: добавить staleness risk proxy в бизнес-обзор
This commit is contained in:
+28
-1
@@ -367,6 +367,9 @@ function headlineFor(mode, pilot) {
|
||||
if (overview.inventory_turnover_proxy) {
|
||||
families.push("оборотный proxy склада");
|
||||
}
|
||||
if (overview.inventory_staleness_risk_proxy) {
|
||||
families.push("staleness risk proxy склада");
|
||||
}
|
||||
const unknownFamilies = [overview.trading_margin_proxy ? "чистая прибыль/точная маржа" : "прибыль/маржа"];
|
||||
if (!overview.tax_position) {
|
||||
unknownFamilies.push("НДС");
|
||||
@@ -559,6 +562,7 @@ function buildMustNotClaim(pilot) {
|
||||
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 present business overview inventory staleness risk proxy as confirmed obsolete stock, reserve, write-off, 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) {
|
||||
@@ -844,6 +848,18 @@ function percentText(part, total) {
|
||||
const pct = percentOfTotal(part, total);
|
||||
return pct === null ? null : `${pct}%`;
|
||||
}
|
||||
function inventoryStalenessRiskBandRu(riskBand) {
|
||||
if (riskBand === "high") {
|
||||
return "высокая зона внимания";
|
||||
}
|
||||
if (riskBand === "elevated") {
|
||||
return "повышенная зона внимания";
|
||||
}
|
||||
if (riskBand === "watch") {
|
||||
return "зона наблюдения";
|
||||
}
|
||||
return "низкий видимый риск";
|
||||
}
|
||||
function derivedBusinessOverviewConfirmedLines(pilot) {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
@@ -923,6 +939,10 @@ function derivedBusinessOverviewConfirmedLines(pilot) {
|
||||
: `${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-оборачиваемость и не анализ устаревания.`);
|
||||
}
|
||||
if (overview.inventory_staleness_risk_proxy) {
|
||||
const proxy = overview.inventory_staleness_risk_proxy;
|
||||
lines.push(`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
function businessOverviewCashSynthesisLine(overview) {
|
||||
@@ -990,6 +1010,9 @@ function businessOverviewRiskSynthesisLine(overview) {
|
||||
: `sales-to-stock ${overview.inventory_turnover_proxy.sales_to_stock_amount_ratio}x`;
|
||||
signals.push(`оборотный proxy склада: ${ratioText}`);
|
||||
}
|
||||
if (overview.inventory_staleness_risk_proxy) {
|
||||
signals.push(`staleness risk proxy склада: ${inventoryStalenessRiskBandRu(overview.inventory_staleness_risk_proxy.risk_band)}, возраст ${overview.inventory_staleness_risk_proxy.max_purchase_age_days} дн.`);
|
||||
}
|
||||
return signals.length > 0
|
||||
? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.`
|
||||
: null;
|
||||
@@ -1001,7 +1024,8 @@ function businessOverviewExecutiveVerdictLine(overview) {
|
||||
overview.debt_position ||
|
||||
overview.debt_open_settlement_quality ||
|
||||
overview.inventory_position ||
|
||||
overview.inventory_turnover_proxy);
|
||||
overview.inventory_turnover_proxy ||
|
||||
overview.inventory_staleness_risk_proxy);
|
||||
if (!hasCash && !hasExtraSignals) {
|
||||
return null;
|
||||
}
|
||||
@@ -1093,6 +1117,9 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||
if (pilot.derived_business_overview?.inventory_turnover_proxy) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_inventory_turnover_proxy");
|
||||
}
|
||||
if (pilot.derived_business_overview?.inventory_staleness_risk_proxy) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_inventory_staleness_risk_proxy");
|
||||
}
|
||||
const confirmedLines = businessOverviewLines.length > 0
|
||||
? businessOverviewLines
|
||||
: pilot.derived_ranked_value_flow && derivedValueLine
|
||||
|
||||
+72
-2
@@ -2424,6 +2424,54 @@ function deriveBusinessOverviewInventoryTurnoverProxy(input) {
|
||||
inference_basis: "sales_document_revenue_vs_inventory_balance_confirmed_1c_rows"
|
||||
};
|
||||
}
|
||||
function inventoryStalenessRiskBand(input) {
|
||||
if (input.maxPurchaseAgeDays >= 365 && input.salesToStockAmountRatio < 1) {
|
||||
return "high";
|
||||
}
|
||||
if (input.maxPurchaseAgeDays >= 365 || input.salesToStockAmountRatio < 1) {
|
||||
return "elevated";
|
||||
}
|
||||
if (input.maxPurchaseAgeDays >= 180 || input.salesToStockAmountRatio < 2) {
|
||||
return "watch";
|
||||
}
|
||||
return "lower_visible_risk";
|
||||
}
|
||||
function deriveBusinessOverviewInventoryStalenessRiskProxy(input) {
|
||||
const { inventoryPosition, inventoryTurnoverProxy } = input;
|
||||
const maxPurchaseAgeDays = inventoryPosition?.aging_signal?.max_age_days;
|
||||
const oldestPurchaseDate = inventoryPosition?.aging_signal?.oldest_purchase_date;
|
||||
const salesToStockAmountRatio = inventoryTurnoverProxy?.sales_to_stock_amount_ratio;
|
||||
if (!inventoryPosition ||
|
||||
!inventoryTurnoverProxy ||
|
||||
!oldestPurchaseDate ||
|
||||
maxPurchaseAgeDays === null ||
|
||||
maxPurchaseAgeDays === undefined ||
|
||||
salesToStockAmountRatio === null ||
|
||||
salesToStockAmountRatio === undefined) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
as_of_date: inventoryPosition.as_of_date,
|
||||
period_scope: inventoryTurnoverProxy.period_scope,
|
||||
oldest_purchase_date: oldestPurchaseDate,
|
||||
max_purchase_age_days: maxPurchaseAgeDays,
|
||||
sales_to_stock_amount_ratio: salesToStockAmountRatio,
|
||||
risk_band: inventoryStalenessRiskBand({ maxPurchaseAgeDays, salesToStockAmountRatio }),
|
||||
inference_basis: "purchase_date_age_and_sales_to_stock_proxy_confirmed_1c_rows"
|
||||
};
|
||||
}
|
||||
function inventoryStalenessRiskBandRu(riskBand) {
|
||||
if (riskBand === "high") {
|
||||
return "высокая зона внимания";
|
||||
}
|
||||
if (riskBand === "elevated") {
|
||||
return "повышенная зона внимания";
|
||||
}
|
||||
if (riskBand === "watch") {
|
||||
return "зона наблюдения";
|
||||
}
|
||||
return "низкий видимый риск";
|
||||
}
|
||||
function deriveBusinessOverview(input) {
|
||||
const incoming = deriveValueFlowSideSummary(input.incomingResult);
|
||||
const outgoing = deriveValueFlowSideSummary(input.outgoingResult);
|
||||
@@ -2454,6 +2502,10 @@ function deriveBusinessOverview(input) {
|
||||
inventoryPosition,
|
||||
tradingMarginProxy
|
||||
});
|
||||
const inventoryStalenessRiskProxy = deriveBusinessOverviewInventoryStalenessRiskProxy({
|
||||
inventoryPosition,
|
||||
inventoryTurnoverProxy
|
||||
});
|
||||
const checkedSignalCount = [
|
||||
incoming.rows_with_amount > 0,
|
||||
outgoing.rows_with_amount > 0,
|
||||
@@ -2463,7 +2515,8 @@ function deriveBusinessOverview(input) {
|
||||
Boolean(debtPosition),
|
||||
Boolean(debtOpenSettlementQuality),
|
||||
Boolean(inventoryPosition),
|
||||
Boolean(inventoryTurnoverProxy)
|
||||
Boolean(inventoryTurnoverProxy),
|
||||
Boolean(inventoryStalenessRiskProxy)
|
||||
].filter(Boolean).length;
|
||||
if (checkedSignalCount <= 0) {
|
||||
return null;
|
||||
@@ -2485,6 +2538,7 @@ function deriveBusinessOverview(input) {
|
||||
debt_open_settlement_quality: debtOpenSettlementQuality,
|
||||
inventory_position: inventoryPosition,
|
||||
inventory_turnover_proxy: inventoryTurnoverProxy,
|
||||
inventory_staleness_risk_proxy: inventoryStalenessRiskProxy,
|
||||
coverage_limited_by_probe_limit: incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
|
||||
checked_signal_count: checkedSignalCount,
|
||||
missing_signal_families: [
|
||||
@@ -2492,7 +2546,13 @@ function deriveBusinessOverview(input) {
|
||||
debtPosition ? null : "debt_position",
|
||||
debtOpenSettlementQuality ? "debt_due_date_aging_quality" : "debt_open_settlement_quality",
|
||||
taxPosition ? null : "tax_position",
|
||||
inventoryPosition ? (inventoryTurnoverProxy ? "inventory_liquidity_quality" : "inventory_turnover_quality") : "inventory_position",
|
||||
inventoryPosition
|
||||
? inventoryStalenessRiskProxy
|
||||
? "inventory_reserve_liquidation_quality"
|
||||
: inventoryTurnoverProxy
|
||||
? "inventory_liquidity_quality"
|
||||
: "inventory_turnover_quality"
|
||||
: "inventory_position",
|
||||
inventoryPosition?.aging_signal ? null : "inventory_aging_quality"
|
||||
].filter((item) => Boolean(item)),
|
||||
inference_basis: inventoryPosition
|
||||
@@ -2623,6 +2683,10 @@ function buildBusinessOverviewConfirmedFacts(derived) {
|
||||
: `${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-оборачиваемость и не анализ устаревания.`);
|
||||
}
|
||||
if (derived.inventory_staleness_risk_proxy) {
|
||||
const proxy = derived.inventory_staleness_risk_proxy;
|
||||
facts.push(`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`);
|
||||
}
|
||||
return facts;
|
||||
}
|
||||
function buildBusinessOverviewInferredFacts(derived) {
|
||||
@@ -2689,6 +2753,9 @@ function buildBusinessOverviewUnknownFacts(derived) {
|
||||
: null,
|
||||
missing.has("inventory_liquidity_quality")
|
||||
? "Полная складская ликвидность этим бизнес-обзором не подтверждена: sales-to-stock proxy показывает только соотношение продажных документов и остатка на дату, без FIFO-оборачиваемости, устаревания, резервов и ликвидационной стоимости."
|
||||
: null,
|
||||
missing.has("inventory_reserve_liquidation_quality")
|
||||
? "Резервы, списания, подтвержденная неликвидность и ликвидационная стоимость склада этим бизнес-обзором не подтверждены: staleness proxy показывает только возраст закупочного сигнала и sales-to-stock, без управленческого решения о запасах."
|
||||
: null
|
||||
].filter((item) => Boolean(item));
|
||||
if (derived?.coverage_limited_by_probe_limit) {
|
||||
@@ -3643,6 +3710,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
if (derivedBusinessOverview.inventory_turnover_proxy) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_turnover_proxy_from_confirmed_rows");
|
||||
}
|
||||
if (derivedBusinessOverview.inventory_staleness_risk_proxy) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_staleness_risk_proxy_from_confirmed_rows");
|
||||
}
|
||||
}
|
||||
const sourceRowsSummary = summarizeBusinessOverviewRows({
|
||||
incomingResult,
|
||||
|
||||
@@ -470,6 +470,9 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
|
||||
if (overview.inventory_turnover_proxy) {
|
||||
families.push("оборотный proxy склада");
|
||||
}
|
||||
if (overview.inventory_staleness_risk_proxy) {
|
||||
families.push("staleness risk proxy склада");
|
||||
}
|
||||
const unknownFamilies = [overview.trading_margin_proxy ? "чистая прибыль/точная маржа" : "прибыль/маржа"];
|
||||
if (!overview.tax_position) {
|
||||
unknownFamilies.push("НДС");
|
||||
@@ -670,6 +673,7 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
|
||||
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 present business overview inventory staleness risk proxy as confirmed obsolete stock, reserve, write-off, 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) {
|
||||
@@ -992,6 +996,21 @@ function percentText(part: number, total: number): string | null {
|
||||
return pct === null ? null : `${pct}%`;
|
||||
}
|
||||
|
||||
function inventoryStalenessRiskBandRu(
|
||||
riskBand: NonNullable<BusinessOverview["inventory_staleness_risk_proxy"]>["risk_band"]
|
||||
): string {
|
||||
if (riskBand === "high") {
|
||||
return "высокая зона внимания";
|
||||
}
|
||||
if (riskBand === "elevated") {
|
||||
return "повышенная зона внимания";
|
||||
}
|
||||
if (riskBand === "watch") {
|
||||
return "зона наблюдения";
|
||||
}
|
||||
return "низкий видимый риск";
|
||||
}
|
||||
|
||||
function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
@@ -1095,6 +1114,12 @@ function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilot
|
||||
`Оборотный 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-оборачиваемость и не анализ устаревания.`
|
||||
);
|
||||
}
|
||||
if (overview.inventory_staleness_risk_proxy) {
|
||||
const proxy = overview.inventory_staleness_risk_proxy;
|
||||
lines.push(
|
||||
`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`
|
||||
);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
@@ -1167,6 +1192,11 @@ function businessOverviewRiskSynthesisLine(overview: BusinessOverview): string |
|
||||
: `sales-to-stock ${overview.inventory_turnover_proxy.sales_to_stock_amount_ratio}x`;
|
||||
signals.push(`оборотный proxy склада: ${ratioText}`);
|
||||
}
|
||||
if (overview.inventory_staleness_risk_proxy) {
|
||||
signals.push(
|
||||
`staleness risk proxy склада: ${inventoryStalenessRiskBandRu(overview.inventory_staleness_risk_proxy.risk_band)}, возраст ${overview.inventory_staleness_risk_proxy.max_purchase_age_days} дн.`
|
||||
);
|
||||
}
|
||||
return signals.length > 0
|
||||
? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.`
|
||||
: null;
|
||||
@@ -1180,7 +1210,8 @@ function businessOverviewExecutiveVerdictLine(overview: BusinessOverview): strin
|
||||
overview.debt_position ||
|
||||
overview.debt_open_settlement_quality ||
|
||||
overview.inventory_position ||
|
||||
overview.inventory_turnover_proxy
|
||||
overview.inventory_turnover_proxy ||
|
||||
overview.inventory_staleness_risk_proxy
|
||||
);
|
||||
if (!hasCash && !hasExtraSignals) {
|
||||
return null;
|
||||
@@ -1282,6 +1313,9 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||
if (pilot.derived_business_overview?.inventory_turnover_proxy) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_inventory_turnover_proxy");
|
||||
}
|
||||
if (pilot.derived_business_overview?.inventory_staleness_risk_proxy) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_inventory_staleness_risk_proxy");
|
||||
}
|
||||
const confirmedLines = businessOverviewLines.length > 0
|
||||
? businessOverviewLines
|
||||
: pilot.derived_ranked_value_flow && derivedValueLine
|
||||
|
||||
@@ -157,6 +157,7 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverview {
|
||||
debt_open_settlement_quality: AssistantMcpDiscoveryDerivedBusinessOverviewDebtOpenSettlementQuality | null;
|
||||
inventory_position: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition | null;
|
||||
inventory_turnover_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy | null;
|
||||
inventory_staleness_risk_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy | null;
|
||||
coverage_limited_by_probe_limit: boolean;
|
||||
checked_signal_count: number;
|
||||
missing_signal_families: string[];
|
||||
@@ -319,6 +320,16 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverPr
|
||||
inference_basis: "sales_document_revenue_vs_inventory_balance_confirmed_1c_rows";
|
||||
}
|
||||
|
||||
export interface AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy {
|
||||
as_of_date: string;
|
||||
period_scope: string;
|
||||
oldest_purchase_date: string;
|
||||
max_purchase_age_days: number;
|
||||
sales_to_stock_amount_ratio: number;
|
||||
risk_band: "lower_visible_risk" | "watch" | "elevated" | "high";
|
||||
inference_basis: "purchase_date_age_and_sales_to_stock_proxy_confirmed_1c_rows";
|
||||
}
|
||||
|
||||
export interface AssistantMcpDiscoveryDerivedMetadataSurface {
|
||||
metadata_scope: string | null;
|
||||
requested_meta_types: string[];
|
||||
@@ -3253,6 +3264,68 @@ function deriveBusinessOverviewInventoryTurnoverProxy(input: {
|
||||
};
|
||||
}
|
||||
|
||||
function inventoryStalenessRiskBand(input: {
|
||||
maxPurchaseAgeDays: number;
|
||||
salesToStockAmountRatio: number;
|
||||
}): AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy["risk_band"] {
|
||||
if (input.maxPurchaseAgeDays >= 365 && input.salesToStockAmountRatio < 1) {
|
||||
return "high";
|
||||
}
|
||||
if (input.maxPurchaseAgeDays >= 365 || input.salesToStockAmountRatio < 1) {
|
||||
return "elevated";
|
||||
}
|
||||
if (input.maxPurchaseAgeDays >= 180 || input.salesToStockAmountRatio < 2) {
|
||||
return "watch";
|
||||
}
|
||||
return "lower_visible_risk";
|
||||
}
|
||||
|
||||
function deriveBusinessOverviewInventoryStalenessRiskProxy(input: {
|
||||
inventoryPosition: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition | null;
|
||||
inventoryTurnoverProxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy | null;
|
||||
}): AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy | null {
|
||||
const { inventoryPosition, inventoryTurnoverProxy } = input;
|
||||
const maxPurchaseAgeDays = inventoryPosition?.aging_signal?.max_age_days;
|
||||
const oldestPurchaseDate = inventoryPosition?.aging_signal?.oldest_purchase_date;
|
||||
const salesToStockAmountRatio = inventoryTurnoverProxy?.sales_to_stock_amount_ratio;
|
||||
if (
|
||||
!inventoryPosition ||
|
||||
!inventoryTurnoverProxy ||
|
||||
!oldestPurchaseDate ||
|
||||
maxPurchaseAgeDays === null ||
|
||||
maxPurchaseAgeDays === undefined ||
|
||||
salesToStockAmountRatio === null ||
|
||||
salesToStockAmountRatio === undefined
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
as_of_date: inventoryPosition.as_of_date,
|
||||
period_scope: inventoryTurnoverProxy.period_scope,
|
||||
oldest_purchase_date: oldestPurchaseDate,
|
||||
max_purchase_age_days: maxPurchaseAgeDays,
|
||||
sales_to_stock_amount_ratio: salesToStockAmountRatio,
|
||||
risk_band: inventoryStalenessRiskBand({ maxPurchaseAgeDays, salesToStockAmountRatio }),
|
||||
inference_basis: "purchase_date_age_and_sales_to_stock_proxy_confirmed_1c_rows"
|
||||
};
|
||||
}
|
||||
|
||||
function inventoryStalenessRiskBandRu(
|
||||
riskBand: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy["risk_band"]
|
||||
): string {
|
||||
if (riskBand === "high") {
|
||||
return "высокая зона внимания";
|
||||
}
|
||||
if (riskBand === "elevated") {
|
||||
return "повышенная зона внимания";
|
||||
}
|
||||
if (riskBand === "watch") {
|
||||
return "зона наблюдения";
|
||||
}
|
||||
return "низкий видимый риск";
|
||||
}
|
||||
|
||||
function deriveBusinessOverview(input: {
|
||||
incomingResult: AssistantMcpDiscoveryCoverageAwareQueryResult | null;
|
||||
outgoingResult: AssistantMcpDiscoveryCoverageAwareQueryResult | null;
|
||||
@@ -3298,6 +3371,10 @@ function deriveBusinessOverview(input: {
|
||||
inventoryPosition,
|
||||
tradingMarginProxy
|
||||
});
|
||||
const inventoryStalenessRiskProxy = deriveBusinessOverviewInventoryStalenessRiskProxy({
|
||||
inventoryPosition,
|
||||
inventoryTurnoverProxy
|
||||
});
|
||||
const checkedSignalCount = [
|
||||
incoming.rows_with_amount > 0,
|
||||
outgoing.rows_with_amount > 0,
|
||||
@@ -3307,7 +3384,8 @@ function deriveBusinessOverview(input: {
|
||||
Boolean(debtPosition),
|
||||
Boolean(debtOpenSettlementQuality),
|
||||
Boolean(inventoryPosition),
|
||||
Boolean(inventoryTurnoverProxy)
|
||||
Boolean(inventoryTurnoverProxy),
|
||||
Boolean(inventoryStalenessRiskProxy)
|
||||
].filter(Boolean).length;
|
||||
if (checkedSignalCount <= 0) {
|
||||
return null;
|
||||
@@ -3330,6 +3408,7 @@ function deriveBusinessOverview(input: {
|
||||
debt_open_settlement_quality: debtOpenSettlementQuality,
|
||||
inventory_position: inventoryPosition,
|
||||
inventory_turnover_proxy: inventoryTurnoverProxy,
|
||||
inventory_staleness_risk_proxy: inventoryStalenessRiskProxy,
|
||||
coverage_limited_by_probe_limit:
|
||||
incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
|
||||
checked_signal_count: checkedSignalCount,
|
||||
@@ -3338,7 +3417,13 @@ function deriveBusinessOverview(input: {
|
||||
debtPosition ? null : "debt_position",
|
||||
debtOpenSettlementQuality ? "debt_due_date_aging_quality" : "debt_open_settlement_quality",
|
||||
taxPosition ? null : "tax_position",
|
||||
inventoryPosition ? (inventoryTurnoverProxy ? "inventory_liquidity_quality" : "inventory_turnover_quality") : "inventory_position",
|
||||
inventoryPosition
|
||||
? inventoryStalenessRiskProxy
|
||||
? "inventory_reserve_liquidation_quality"
|
||||
: 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:
|
||||
@@ -3509,6 +3594,12 @@ function buildBusinessOverviewConfirmedFacts(derived: AssistantMcpDiscoveryDeriv
|
||||
`Оборотный 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-оборачиваемость и не анализ устаревания.`
|
||||
);
|
||||
}
|
||||
if (derived.inventory_staleness_risk_proxy) {
|
||||
const proxy = derived.inventory_staleness_risk_proxy;
|
||||
facts.push(
|
||||
`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`
|
||||
);
|
||||
}
|
||||
return facts;
|
||||
}
|
||||
|
||||
@@ -3580,6 +3671,9 @@ function buildBusinessOverviewUnknownFacts(derived: AssistantMcpDiscoveryDerived
|
||||
: null,
|
||||
missing.has("inventory_liquidity_quality")
|
||||
? "Полная складская ликвидность этим бизнес-обзором не подтверждена: sales-to-stock proxy показывает только соотношение продажных документов и остатка на дату, без FIFO-оборачиваемости, устаревания, резервов и ликвидационной стоимости."
|
||||
: null,
|
||||
missing.has("inventory_reserve_liquidation_quality")
|
||||
? "Резервы, списания, подтвержденная неликвидность и ликвидационная стоимость склада этим бизнес-обзором не подтверждены: staleness proxy показывает только возраст закупочного сигнала и sales-to-stock, без управленческого решения о запасах."
|
||||
: null
|
||||
].filter((item): item is string => Boolean(item));
|
||||
if (derived?.coverage_limited_by_probe_limit) {
|
||||
@@ -4649,6 +4743,9 @@ export async function executeAssistantMcpDiscoveryPilot(
|
||||
if (derivedBusinessOverview.inventory_turnover_proxy) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_turnover_proxy_from_confirmed_rows");
|
||||
}
|
||||
if (derivedBusinessOverview.inventory_staleness_risk_proxy) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_staleness_risk_proxy_from_confirmed_rows");
|
||||
}
|
||||
}
|
||||
const sourceRowsSummary = summarizeBusinessOverviewRows({
|
||||
incomingResult,
|
||||
|
||||
@@ -488,16 +488,22 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||
|
||||
expect(draft.headline).toContain("складской срез");
|
||||
expect(draft.headline).toContain("оборотный proxy склада");
|
||||
expect(draft.headline).toContain("staleness risk proxy склада");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("Складской срез на 2020-12-31");
|
||||
expect(draft.confirmed_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.confirmed_lines.join("\n")).toContain("Staleness risk proxy склада");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("зона наблюдения");
|
||||
expect(draft.inference_lines.join("\n")).toContain("оборотный proxy склада");
|
||||
expect(draft.unknown_lines.join("\n")).toContain("Полная складская ликвидность");
|
||||
expect(draft.inference_lines.join("\n")).toContain("staleness risk 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.reason_codes).toContain("answer_contains_business_overview_inventory_staleness_risk_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.");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview inventory staleness risk proxy as confirmed obsolete stock, reserve, write-off, or liquidation value.");
|
||||
});
|
||||
|
||||
it("renders metadata-scoped movement all-time follow-up as an all-time bounded answer", async () => {
|
||||
|
||||
@@ -592,16 +592,28 @@ describe("assistant MCP discovery pilot executor", () => {
|
||||
sales_to_stock_amount_ratio: 2,
|
||||
stock_to_sales_revenue_pct: 50
|
||||
});
|
||||
expect(result.derived_business_overview?.inventory_staleness_risk_proxy).toMatchObject({
|
||||
period_scope: "2020",
|
||||
as_of_date: "2020-12-31",
|
||||
oldest_purchase_date: "2020-01-10",
|
||||
max_purchase_age_days: 356,
|
||||
sales_to_stock_amount_ratio: 2,
|
||||
risk_band: "watch"
|
||||
});
|
||||
expect(result.derived_business_overview?.missing_signal_families).not.toContain("inventory_position");
|
||||
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.derived_business_overview?.missing_signal_families).not.toContain("inventory_liquidity_quality");
|
||||
expect(result.derived_business_overview?.missing_signal_families).toContain("inventory_reserve_liquidation_quality");
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("Складской срез на 2020-12-31");
|
||||
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.evidence.confirmed_facts.join("\n")).toContain("Staleness risk proxy склада");
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("зона наблюдения");
|
||||
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(result.reason_codes).toContain("pilot_derived_business_overview_inventory_staleness_risk_proxy_from_confirmed_rows");
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(10);
|
||||
const inventoryCall = deps.executeAddressMcpQuery.mock.calls[6]?.[0];
|
||||
expect(inventoryCall?.account_scope).toContain("41.01");
|
||||
|
||||
Reference in New Issue
Block a user