Open-World: добавить sales-to-stock proxy в бизнес-обзор
This commit is contained in:
parent
062655eca0
commit
bf3ae110ef
|
|
@ -25,8 +25,9 @@ If another document says `78%`, `87%`, `92%`, or `85%` for a module that is now
|
|||
- Completed active slice: `Business Overview Contract-Date Debt Age Signal Bridge`: explicit-period open-settlement quality can now include contract-date age as a bounded signal, while due-date aging/overdue debt remains unclaimed until a reviewed payment-term route exists.
|
||||
- Completed active slice: `Business Overview Analyst Synthesis Layer`: business-overview answers now turn checked fact families into a bounded analyst note with operating scale, customer concentration, risk contours, and explicit profit/margin boundaries.
|
||||
- Completed active slice: `Business Overview Trading Margin Proxy Bridge`: explicit-period business overview can include a bounded товарный sales-vs-purchase document proxy for revenue, purchase-cost trace, gross spread, and margin proxy, while clean profit/accounting финрезультат remains unclaimed.
|
||||
- Next active slice: continue breadth into exact company-wide accounting profit/margin, real due-date debt aging, inventory-liquidity/turnover, and broader unfamiliar 1C route families only where reviewed evidence routes exist.
|
||||
- Active module progress: `~77% (Open-World Bounded Autonomy Breadth)`.
|
||||
- Completed active slice: `Business Overview Inventory Sales Velocity Proxy Bridge`: when explicit-period stock and товарные sales evidence are both present, business overview can include a bounded sales-to-stock proxy while full FIFO turnover/liquidity remains unclaimed.
|
||||
- Next active slice: continue breadth into exact company-wide accounting profit/margin, real due-date debt aging, full inventory-liquidity/obsolescence, and broader unfamiliar 1C route families only where reviewed evidence routes exist.
|
||||
- Active module progress: `~80% (Open-World Bounded Autonomy Breadth)`.
|
||||
|
||||
## Reporting Rule
|
||||
|
||||
|
|
@ -63,7 +64,7 @@ The project is not yet a universal arbitrary-1C agent.
|
|||
|
||||
Remaining work belongs to the next breadth module:
|
||||
|
||||
- extend `business_overview` beyond money-flow/activity, explicit-period VAT/tax, as-of-date debt position, open-settlement concentration, contract-date debt age, as-of-date inventory position, and trading-margin proxy into separately proven exact accounting profit/margin, due-date debt aging/overdue, and real inventory-liquidity evidence families;
|
||||
- extend `business_overview` beyond money-flow/activity, explicit-period VAT/tax, as-of-date debt position, open-settlement concentration, contract-date debt age, as-of-date inventory position, trading-margin proxy, and sales-to-stock inventory proxy into separately proven exact accounting profit/margin, due-date debt aging/overdue, and full inventory-liquidity/obsolescence evidence families;
|
||||
- broader dynamic schema traversal for unfamiliar 1C asks;
|
||||
- more primitive descriptors where live evidence proves a real gap;
|
||||
- more replay-backed domain packs that start from user business meaning, not from route convenience;
|
||||
|
|
|
|||
|
|
@ -269,12 +269,13 @@ Local validation is accepted for this slice:
|
|||
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts addressQueryRuntimeM23.test.ts`: passed `478` with `1` skipped.
|
||||
- `npm.cmd run build`: passed.
|
||||
|
||||
|
||||
### Still Pending Breadth Slices
|
||||
|
||||
Grow this bridge beyond the first confirmed signal bundle:
|
||||
|
||||
- add separate evidence families for exact company-wide accounting profit/margin and due-date debt aging/overdue quality where reviewed closing-cost and due-date/payment-term routes exist;
|
||||
- extend inventory evidence from as-of-date stock position into real turnover/liquidity only when reviewed sales velocity, aging, or obsolescence evidence exists;
|
||||
- extend inventory evidence from sales-to-stock proxy into full FIFO turnover/liquidity/obsolescence only when reviewed stock aging, sales velocity, reserve, or liquidation-value evidence exists;
|
||||
- upgrade debt evidence from as-of-date position/open-settlement concentration/contract-date age into overdue aging only when reviewed due-date or payment-term aging evidence exists;
|
||||
- extend VAT/tax beyond explicit-period tax position only when the requested tax fact is provable and the period is explicit;
|
||||
- keep Post-F stale-scope and phase83 catalog-alignment canaries green while widening the route.
|
||||
|
|
@ -368,3 +369,36 @@ Business-overview trading-margin proxy validation:
|
|||
- `npm.cmd run build`: passed.
|
||||
|
||||
Graphify rebuild after Slice 11 code/doc sync: `6028 nodes`, `13131 edges`, `137 communities`.
|
||||
|
||||
Business-overview inventory sales velocity proxy validation:
|
||||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `66` with `1` skipped.
|
||||
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412`.
|
||||
- `npm.cmd run build`: passed.
|
||||
|
||||
Graphify rebuild after Slice 12 code/doc sync: `6030 nodes`, `13136 edges`, `137 communities`.
|
||||
|
||||
## Slice 12 - Business Overview Inventory Sales Velocity Proxy Bridge
|
||||
|
||||
This slice converts an existing missing family into a bounded cross-signal proxy.
|
||||
|
||||
It uses only already checked fact families:
|
||||
|
||||
- inventory on-hand amount on an explicit as-of date;
|
||||
- товарные sales document revenue for the same explicit period.
|
||||
|
||||
Implemented now:
|
||||
|
||||
- the pilot derives `inventory_turnover_proxy` only when both `inventory_position` and `trading_margin_proxy` are present in the current business-overview turn;
|
||||
- the proxy reports sales revenue, stock amount, sales-to-stock ratio, and stock-to-sales percentage;
|
||||
- the answer adapter surfaces this as `оборотный proxy склада`, not as full inventory liquidity, FIFO turnover, obsolescence analysis, or liquidation value;
|
||||
- when this proxy exists, the missing family narrows from `inventory_turnover_quality` to `inventory_liquidity_quality`;
|
||||
- all-time business overview still does not reuse stale stock or sales-window evidence.
|
||||
|
||||
This is a useful management signal for broad company analysis, but it is not a complete warehouse-liquidity conclusion. Full inventory quality still needs reviewed evidence for FIFO/lot turnover, aging/obsolescence, reserves, and liquidation value.
|
||||
|
||||
Local validation is accepted for this slice:
|
||||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `66` with `1` skipped.
|
||||
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412`.
|
||||
- `npm.cmd run build`: passed.
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ Status canon for planning:
|
|||
- The current completed breadth slice is `Business Overview Contract-Date Debt Age Signal Bridge`: explicit-period open-settlement quality can include contract-date age as a bounded signal, while due-date aging/overdue debt still waits for reviewed payment-term evidence.
|
||||
- The current completed breadth slice is `Business Overview Analyst Synthesis Layer`: broad company-analysis answers now synthesize checked fact families into operating scale, customer concentration, risk contours, and a concise bounded LLM-audit.
|
||||
- The current completed breadth slice is `Business Overview Trading Margin Proxy Bridge`: explicit-period company analysis can now include товарный sales-vs-purchase document proxy for revenue, purchase-cost trace, gross spread, and margin proxy, while clean profit/accounting финрезультат remains unclaimed.
|
||||
- The next active breadth slice continues breadth into exact company-wide accounting profit/margin, real due-date debt aging, inventory-liquidity/turnover, and broader unfamiliar 1C route families without relaxing truth boundaries.
|
||||
- The current completed breadth slice is `Business Overview Inventory Sales Velocity Proxy Bridge`: when explicit-period stock and sales evidence are both present, company analysis can include a bounded sales-to-stock proxy while full FIFO/liquidity/obsolescence remains unclaimed.
|
||||
- The next active breadth slice continues breadth into exact company-wide accounting profit/margin, real due-date debt aging, full inventory-liquidity/obsolescence, and broader unfamiliar 1C route families without relaxing truth boundaries.
|
||||
- The short source of truth for status wording is [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md).
|
||||
|
||||
It now documents a turnaround that is already operational in code, already materially past the acute regression breakpoint, and already moved through bounded MCP autonomy, Post-F hardening, inventory breadth proof, and the declared Planner Autonomy slice:
|
||||
|
|
@ -125,11 +126,11 @@ Current honest status:
|
|||
- pre-multidomain readiness: `~90%`
|
||||
- bounded-autonomy foundation readiness: `~89%`
|
||||
- open-world bounded-autonomy readiness: `~87%`
|
||||
- active Open-World Bounded Autonomy Breadth progress: `~77%`, with business-overview evidence fusion, the reviewed `business_overview` catalog/data-need/planner route-fabric slice, the fresh multi-probe runtime bridge, the explicit-period VAT/tax fact-family bridge, the explicit-period debt-position bridge, the explicit-date inventory-position bridge, the open-settlement quality bridge accepted by live semantic replay, selected-item profitability bridged by local semantic/runtime regression tests, contract-date debt age bridged locally, analyst synthesis added to business-overview answer drafting, and company-period trading margin proxy bridged locally; exact accounting profit/margin, true due-date debt aging/overdue, and real inventory-liquidity expansion are still pending
|
||||
- active Open-World Bounded Autonomy Breadth progress: `~80%`, with business-overview evidence fusion, the reviewed `business_overview` catalog/data-need/planner route-fabric slice, the fresh multi-probe runtime bridge, the explicit-period VAT/tax fact-family bridge, the explicit-period debt-position bridge, the explicit-date inventory-position bridge, the open-settlement quality bridge accepted by live semantic replay, selected-item profitability bridged by local semantic/runtime regression tests, contract-date debt age bridged locally, analyst synthesis added to business-overview answer drafting, company-period trading margin proxy bridged locally, and inventory sales-to-stock proxy bridged locally; exact accounting profit/margin, true due-date debt aging/overdue, and full inventory-liquidity/obsolescence expansion are still pending
|
||||
- Post-F semantic integrity module progress: `~99%` operationally closed, with remaining risk now treated as next-slice discovery rather than an open blocker inside the closed slice
|
||||
- active inventory-stock breadth slice progress: `100%` for the declared scenario pack, not for arbitrary inventory questions
|
||||
- Planner Autonomy Consolidation progress: `100%` for the declared module, with catalog-fabric, value-flow arbitration, lifecycle bounded inference, broad-evaluation bridge, inventory catalog templates, inventory runtime-boundary honesty, exact inventory recipe bridging, unambiguous metadata-surface lane inference, catalog chain-template scoring, structured chain-match contract exposure, runtime/debug propagation, subject-aware bidirectional comparison arbitration, structured catalog-alignment verdicts, representative alignment regression guard, catalog-alignment reason-code telemetry, explicit `alignment_status` propagation, truth-harness/acceptance-matrix surfacing, soft divergence warning, `catalog_alignment_ok` acceptance invariant, step-level expected catalog-alignment assertions, phase66 and phase32 spec alignment expectations, AGENT source-catalog surfacing, generated phase83 mixed planner-brain replay spec, checked-source user-facing error sanitation, surface-grounded catalog promotion, and guarded live phase83 acceptance validated. Broader unfamiliar 1C asks are now next-module breadth work rather than an open blocker inside this declared slice
|
||||
- graph snapshot after latest rebuild: `6028 nodes`, `13131 edges`, `137 communities`
|
||||
- graph snapshot after latest rebuild: `6030 nodes`, `13136 edges`, `137 communities`
|
||||
- current regression-gate breakpoint:
|
||||
- the validated hot paths are no longer structurally broken;
|
||||
- flagship continuity collapse is no longer the primary risk;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Reference in New Issue