Open-World: добавить debt staleness risk proxy в бизнес-обзор

This commit is contained in:
dctouch 2026-05-04 11:08:20 +03:00
parent 34e7a135ee
commit cd22164f33
9 changed files with 293 additions and 7 deletions

View File

@ -28,8 +28,9 @@ If another document says `78%`, `87%`, `92%`, or `85%` for a module that is now
- 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.
- Completed active slice: `Business Overview Inventory Staleness Risk Proxy Bridge`: when current-turn stock aging and sales-to-stock evidence are both present, business overview can include a bounded warehouse staleness-risk proxy while confirmed obsolete stock, reserves, write-offs, and liquidation value remain unclaimed.
- Completed active slice: `Business Overview Gap-Specific Headline And Next-Step Precision`: broad company-analysis answers now name the remaining unchecked families from `missing_signal_families` instead of using stale generic profit/debt/VAT/warehouse wording after partial proxies are proven.
- Completed active slice: `Business Overview Debt Staleness Risk Proxy Bridge`: when current-turn open-settlement concentration and contract-date age are both present, business overview can include a bounded debt staleness-risk proxy while contractual delinquency, credit risk, and due-date aging remain unclaimed.
- Next active slice: continue breadth into exact company-wide accounting profit/margin, real due-date debt aging, confirmed inventory reserve/write-off/liquidation evidence, and broader unfamiliar 1C route families only where reviewed evidence routes exist.
- Active module progress: `~84% (Open-World Bounded Autonomy Breadth)`.
- Active module progress: `~86% (Open-World Bounded Autonomy Breadth)`.
## Reporting Rule
@ -66,7 +67,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, trading-margin proxy, sales-to-stock inventory proxy, and warehouse staleness-risk proxy into separately proven exact accounting profit/margin, due-date debt aging/overdue, and confirmed reserve/write-off/liquidation inventory 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, debt staleness-risk proxy, as-of-date inventory position, trading-margin proxy, sales-to-stock inventory proxy, and warehouse staleness-risk proxy into separately proven exact accounting profit/margin, due-date debt aging/overdue, and confirmed reserve/write-off/liquidation inventory 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;

View File

@ -276,7 +276,7 @@ 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 beyond stock position, sales-to-stock proxy, and purchase-date staleness risk proxy into full FIFO turnover, confirmed obsolete-stock/liquidity, reserves, write-offs, or liquidation value only when reviewed 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;
- upgrade debt evidence beyond as-of-date position, open-settlement concentration, contract-date age, and debt staleness risk proxy 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.
@ -376,6 +376,8 @@ Business-overview inventory sales velocity proxy validation:
- `npm.cmd test -- addressQueryRuntimeM23.test.ts`: passed `412`.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 15 code/doc sync: `6040 nodes`, `13158 edges`, `135 communities`.
Graphify rebuild after Slice 12 code/doc sync: `6030 nodes`, `13136 edges`, `137 communities`.
## Slice 12 - Business Overview Inventory Sales Velocity Proxy Bridge
@ -452,3 +454,29 @@ Local validation is accepted for this slice:
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 14 code/doc sync: `6036 nodes`, `13149 edges`, `134 communities`.
## Slice 15 - Business Overview Debt Staleness Risk Proxy Bridge
This slice adds a bounded management risk signal for open settlements without crossing into contractual delinquency.
It uses only already reviewed current-turn evidence:
- open-contract balance rows on 60/62/76;
- contract-date age extracted from those open-settlement rows;
- concentration of the oldest large open contract inside gross open balances.
Implemented now:
- the pilot derives `debt_staleness_risk_proxy` only when open-settlement quality and contract-date age are both present in the current business-overview turn;
- the proxy reports as-of date, gross open amount, oldest contract start date, max contract age, oldest large contract, its counterparty, amount, share of gross open balances, and a bounded risk band: `lower_visible_risk`, `watch`, `elevated`, or `high`;
- the answer adapter surfaces this as `staleness risk proxy открытых расчетов`, explicitly not as confirmed overdue debt, contractual delinquency, credit risk, or due-date aging;
- the headline narrows the remaining debt gap to `договорные сроки оплаты/due-date просрочка` when this proxy exists;
- M23 stayed green, including open-contract, stale-contract, VAT-after-contract, and follow-up carryover tests.
This is the debt-side counterpart to the inventory staleness proxy: it gives a useful analyst warning when old contract-date signals and concentration are visible, but it still leaves true due-date/payment-term aging as pending reviewed route work.
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.

View File

@ -63,6 +63,7 @@ Status canon for planning:
- 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 current completed breadth slice is `Business Overview Inventory Staleness Risk Proxy Bridge`: when current-turn stock aging and sales-to-stock evidence are both present, company analysis can include a bounded staleness-risk proxy while confirmed obsolete stock, reserves, write-offs, and liquidation value remain unclaimed.
- The current completed breadth slice is `Business Overview Gap-Specific Headline And Next-Step Precision`: business-overview answers now name remaining unchecked families from `missing_signal_families` instead of falling back to stale generic gap wording.
- The current completed breadth slice is `Business Overview Debt Staleness Risk Proxy Bridge`: when current-turn open-settlement concentration and contract-date age are both present, company analysis can include a bounded debt staleness-risk proxy while confirmed overdue debt, contractual delinquency, credit risk, and due-date aging remain unclaimed.
- The next active breadth slice continues breadth into exact company-wide accounting profit/margin, real due-date debt aging, confirmed reserve/write-off/liquidation inventory evidence, 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).
@ -128,11 +129,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: `~84%`, 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, inventory sales-to-stock proxy bridged locally, inventory staleness-risk proxy bridged locally, and gap-specific answer shaping bridged locally; exact accounting profit/margin, true due-date debt aging/overdue, and confirmed reserve/write-off/liquidation inventory evidence are still pending
- active Open-World Bounded Autonomy Breadth progress: `~86%`, 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, debt staleness-risk proxy bridged locally, analyst synthesis added to business-overview answer drafting, company-period trading margin proxy bridged locally, inventory sales-to-stock proxy bridged locally, inventory staleness-risk proxy bridged locally, and gap-specific answer shaping bridged locally; exact accounting profit/margin, true due-date debt aging/overdue, and confirmed reserve/write-off/liquidation inventory evidence 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: `6036 nodes`, `13149 edges`, `134 communities`
- graph snapshot after latest rebuild: `6040 nodes`, `13158 edges`, `135 communities`
- current regression-gate breakpoint:
- the validated hot paths are no longer structurally broken;
- flagship continuity collapse is no longer the primary risk;
@ -183,6 +184,7 @@ Latest live proof now includes:
- business-overview analyst synthesis accepted locally: answer-adapter slice passed `34/34` with `1` skipped; full MCP-discovery slice passed `305/305` with `9` skipped; build passed; graphify rebuilt to `6023 nodes`, `13112 edges`, `136 communities`; broad company-analysis drafts now include operating scale, customer concentration, risk contours, and bounded LLM-audit inference lines
- business-overview inventory staleness-risk proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6034 nodes`, `13145 edges`, `136 communities`; the proxy combines stock aging and sales-to-stock ratio while confirmed obsolete stock, reserves, write-offs, and liquidation value remain unclaimed
- business-overview gap-specific answer shaping accepted locally: answer-adapter slice passed `34/34` with `1` skipped; build passed; graphify rebuilt to `6036 nodes`, `13149 edges`, `134 communities`; headline and next-step wording now follow `missing_signal_families` instead of stale generic gap labels
- business-overview debt staleness-risk proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6040 nodes`, `13158 edges`, `135 communities`; the proxy combines contract-date age and open-balance concentration while confirmed overdue debt, contractual delinquency, credit risk, and due-date aging remain unclaimed
- inventory template lift accepted locally: catalog/data-need/planner/turn-input slice passed `139/139` with `6` skipped; full MCP-discovery slice passed `276/276` with `9` skipped; build passed; graphify stayed at `5912 nodes`, `12833 edges`, `138 communities`
- inventory runtime-boundary hardening accepted locally: runtime-bridge/answer-adapter/pilot-executor slice passed `68/68` with `1` skipped; full MCP-discovery slice passed `277/277` with `9` skipped; build passed; graphify rebuilt to `5913 nodes`, `12837 edges`, `138 communities`
- inventory exact-runtime bridge accepted locally: runtime-bridge/answer-adapter/pilot-executor slice passed `70/70` with `1` skipped; full MCP-discovery slice passed `279/279` with `9` skipped; build passed; graphify rebuilt to `5930 nodes`, `12884 edges`, `135 communities`

View File

@ -411,6 +411,9 @@ function headlineFor(mode, pilot) {
families.push("возрастной сигнал открытых расчетов");
}
}
if (overview.debt_staleness_risk_proxy) {
families.push("staleness risk proxy открытых расчетов");
}
if (overview.inventory_position) {
families.push("складской срез на дату");
}
@ -427,7 +430,11 @@ function headlineFor(mode, pilot) {
if (!overview.debt_position) {
unknownFamilies.push("долговой срез");
}
unknownFamilies.push(overview.debt_open_settlement_quality ? "due-date просрочка" : "качество открытых расчетов");
unknownFamilies.push(overview.debt_staleness_risk_proxy
? "договорные сроки оплаты/due-date просрочка"
: overview.debt_open_settlement_quality
? "due-date просрочка"
: "качество открытых расчетов");
unknownFamilies.push(businessOverviewInventoryUnknownLabel(overview));
return `По данным 1С собран ограниченный бизнес-обзор: ${families.join(", ")} подтверждены найденными строками; ${unknownFamilies.join(", ")} остаются отдельными непроверенными областями.`;
}
@ -612,6 +619,7 @@ function buildMustNotClaim(pilot) {
claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked.");
claims.push("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis.");
claims.push("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt.");
claims.push("Do not present business overview debt staleness risk proxy as confirmed overdue debt, contractual delinquency, credit risk, or due-date aging.");
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.");
@ -912,6 +920,18 @@ function inventoryStalenessRiskBandRu(riskBand) {
}
return "низкий видимый риск";
}
function debtStalenessRiskBandRu(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) {
@ -968,6 +988,11 @@ function derivedBusinessOverviewConfirmedLines(pilot) {
lines.push(`Возрастной сигнал открытых расчетов: самая ранняя найденная дата договора ${quality.age_signal.oldest_start_date}${ageText}. Это не просрочка и не due-date анализ.`);
}
}
if (overview.debt_staleness_risk_proxy) {
const proxy = overview.debt_staleness_risk_proxy;
const counterpartyText = proxy.top_contract_counterparty ? ` / ${proxy.top_contract_counterparty}` : "";
lines.push(`Staleness risk proxy открытых расчетов на ${proxy.as_of_date}: самый старый договорный сигнал ${proxy.oldest_contract_start_date}, возраст ${proxy.max_contract_age_days} дн.; старейший крупный договор ${proxy.top_contract}${counterpartyText} держит ${proxy.top_contract_amount_human_ru} (${proxy.top_contract_share_pct}% брутто открытых остатков); оценка ${debtStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная просрочка, не кредитный риск и не due-date aging.`);
}
if (overview.inventory_position) {
const leader = overview.inventory_position.top_items[0];
const leaderText = leader
@ -1050,6 +1075,9 @@ function businessOverviewRiskSynthesisLine(overview) {
if (overview.debt_open_settlement_quality?.age_signal?.max_age_days !== null && overview.debt_open_settlement_quality?.age_signal?.max_age_days !== undefined) {
signals.push(`самый старый договорный возрастной сигнал ${overview.debt_open_settlement_quality.age_signal.max_age_days} дн.`);
}
if (overview.debt_staleness_risk_proxy) {
signals.push(`staleness risk proxy открытых расчетов: ${debtStalenessRiskBandRu(overview.debt_staleness_risk_proxy.risk_band)}, возраст ${overview.debt_staleness_risk_proxy.max_contract_age_days} дн., концентрация старейшего крупного договора ${overview.debt_staleness_risk_proxy.top_contract_share_pct}%`);
}
if (overview.inventory_position) {
signals.push(`складской остаток на дату ${overview.inventory_position.total_amount_human_ru}`);
if (overview.inventory_position.aging_signal?.max_age_days !== null && overview.inventory_position.aging_signal?.max_age_days !== undefined) {
@ -1075,6 +1103,7 @@ function businessOverviewExecutiveVerdictLine(overview) {
overview.trading_margin_proxy ||
overview.debt_position ||
overview.debt_open_settlement_quality ||
overview.debt_staleness_risk_proxy ||
overview.inventory_position ||
overview.inventory_turnover_proxy ||
overview.inventory_staleness_risk_proxy);
@ -1163,6 +1192,9 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_age_signal");
}
}
if (pilot.derived_business_overview?.debt_staleness_risk_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_staleness_risk_proxy");
}
if (pilot.derived_business_overview?.inventory_position) {
pushReason(reasonCodes, "answer_contains_business_overview_inventory_position");
}

View File

@ -2298,6 +2298,61 @@ function deriveBusinessOverviewDebtOpenSettlementQuality(input) {
inference_basis: "open_contracts_confirmed_1c_balance_rows"
};
}
function debtStalenessRiskBand(input) {
if (input.maxContractAgeDays >= 365 && input.topContractSharePct >= 50) {
return "high";
}
if (input.maxContractAgeDays >= 365 || input.topContractSharePct >= 50) {
return "elevated";
}
if (input.maxContractAgeDays >= 180 || input.topContractSharePct >= 35) {
return "watch";
}
return "lower_visible_risk";
}
function deriveBusinessOverviewDebtStalenessRiskProxy(quality) {
const ageSignal = quality?.age_signal;
const topAgedContract = ageSignal?.top_aged_contracts[0];
const topContractSharePct = topAgedContract?.share_of_gross_open_amount_pct;
if (!quality ||
!ageSignal?.oldest_start_date ||
ageSignal.max_age_days === null ||
ageSignal.max_age_days === undefined ||
!topAgedContract ||
topContractSharePct === null ||
topContractSharePct === undefined) {
return null;
}
return {
as_of_date: quality.as_of_date,
gross_open_amount: quality.gross_open_amount,
gross_open_amount_human_ru: quality.gross_open_amount_human_ru,
oldest_contract_start_date: ageSignal.oldest_start_date,
max_contract_age_days: ageSignal.max_age_days,
top_contract: topAgedContract.contract,
top_contract_counterparty: topAgedContract.counterparty,
top_contract_amount: topAgedContract.total_amount,
top_contract_amount_human_ru: topAgedContract.total_amount_human_ru,
top_contract_share_pct: topContractSharePct,
risk_band: debtStalenessRiskBand({
maxContractAgeDays: ageSignal.max_age_days,
topContractSharePct
}),
inference_basis: "contract_date_age_and_open_balance_concentration_confirmed_1c_rows"
};
}
function debtStalenessRiskBandRu(riskBand) {
if (riskBand === "high") {
return "высокая зона внимания";
}
if (riskBand === "elevated") {
return "повышенная зона внимания";
}
if (riskBand === "watch") {
return "зона наблюдения";
}
return "низкий видимый риск";
}
function daysBetweenIsoDates(leftIsoDate, rightIsoDate) {
const leftMatch = leftIsoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
const rightMatch = rightIsoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
@ -2493,6 +2548,7 @@ function deriveBusinessOverview(input) {
openContractsResult: input.openContractsResult,
debtAsOfDate: input.debtAsOfDate
});
const debtStalenessRiskProxy = deriveBusinessOverviewDebtStalenessRiskProxy(debtOpenSettlementQuality);
const inventoryPosition = deriveBusinessOverviewInventoryPosition({
inventoryOnHandResult: input.inventoryOnHandResult,
inventoryAgingResult: input.inventoryAgingResult,
@ -2514,6 +2570,7 @@ function deriveBusinessOverview(input) {
Boolean(tradingMarginProxy),
Boolean(debtPosition),
Boolean(debtOpenSettlementQuality),
Boolean(debtStalenessRiskProxy),
Boolean(inventoryPosition),
Boolean(inventoryTurnoverProxy),
Boolean(inventoryStalenessRiskProxy)
@ -2536,6 +2593,7 @@ function deriveBusinessOverview(input) {
trading_margin_proxy: tradingMarginProxy,
debt_position: debtPosition,
debt_open_settlement_quality: debtOpenSettlementQuality,
debt_staleness_risk_proxy: debtStalenessRiskProxy,
inventory_position: inventoryPosition,
inventory_turnover_proxy: inventoryTurnoverProxy,
inventory_staleness_risk_proxy: inventoryStalenessRiskProxy,
@ -2660,6 +2718,11 @@ function buildBusinessOverviewConfirmedFacts(derived) {
facts.push(`Возрастной сигнал открытых расчетов подтвержден по датам договоров: самая ранняя дата договора ${quality.age_signal.oldest_start_date}${ageText}. Это не договорная просрочка и не due-date анализ.`);
}
}
if (derived.debt_staleness_risk_proxy) {
const proxy = derived.debt_staleness_risk_proxy;
const counterpartyText = proxy.top_contract_counterparty ? ` / ${proxy.top_contract_counterparty}` : "";
facts.push(`Staleness risk proxy открытых расчетов на ${proxy.as_of_date}: самый старый договорный сигнал ${proxy.oldest_contract_start_date}, возраст ${proxy.max_contract_age_days} дн.; старейший крупный договор ${proxy.top_contract}${counterpartyText} держит ${proxy.top_contract_amount_human_ru} (${proxy.top_contract_share_pct}% брутто открытых остатков); оценка ${debtStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная просрочка, не кредитный риск и не due-date aging.`);
}
if (derived.inventory_position) {
const leader = derived.inventory_position.top_items[0];
const leaderText = leader
@ -3704,6 +3767,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
pushReason(reasonCodes, "pilot_derived_business_overview_debt_age_signal_from_contract_dates");
}
}
if (derivedBusinessOverview.debt_staleness_risk_proxy) {
pushReason(reasonCodes, "pilot_derived_business_overview_debt_staleness_risk_proxy_from_confirmed_rows");
}
if (derivedBusinessOverview.inventory_position) {
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_position_from_confirmed_rows");
}

View File

@ -510,6 +510,9 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
families.push("возрастной сигнал открытых расчетов");
}
}
if (overview.debt_staleness_risk_proxy) {
families.push("staleness risk proxy открытых расчетов");
}
if (overview.inventory_position) {
families.push("складской срез на дату");
}
@ -526,7 +529,13 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
if (!overview.debt_position) {
unknownFamilies.push("долговой срез");
}
unknownFamilies.push(overview.debt_open_settlement_quality ? "due-date просрочка" : "качество открытых расчетов");
unknownFamilies.push(
overview.debt_staleness_risk_proxy
? "договорные сроки оплаты/due-date просрочка"
: overview.debt_open_settlement_quality
? "due-date просрочка"
: "качество открытых расчетов"
);
unknownFamilies.push(businessOverviewInventoryUnknownLabel(overview));
return `По данным 1С собран ограниченный бизнес-обзор: ${families.join(", ")} подтверждены найденными строками; ${unknownFamilies.join(", ")} остаются отдельными непроверенными областями.`;
}
@ -719,6 +728,7 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked.");
claims.push("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis.");
claims.push("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt.");
claims.push("Do not present business overview debt staleness risk proxy as confirmed overdue debt, contractual delinquency, credit risk, or due-date aging.");
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.");
@ -1059,6 +1069,21 @@ function inventoryStalenessRiskBandRu(
return "низкий видимый риск";
}
function debtStalenessRiskBandRu(
riskBand: NonNullable<BusinessOverview["debt_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) {
@ -1133,6 +1158,13 @@ function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilot
);
}
}
if (overview.debt_staleness_risk_proxy) {
const proxy = overview.debt_staleness_risk_proxy;
const counterpartyText = proxy.top_contract_counterparty ? ` / ${proxy.top_contract_counterparty}` : "";
lines.push(
`Staleness risk proxy открытых расчетов на ${proxy.as_of_date}: самый старый договорный сигнал ${proxy.oldest_contract_start_date}, возраст ${proxy.max_contract_age_days} дн.; старейший крупный договор ${proxy.top_contract}${counterpartyText} держит ${proxy.top_contract_amount_human_ru} (${proxy.top_contract_share_pct}% брутто открытых остатков); оценка ${debtStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная просрочка, не кредитный риск и не due-date aging.`
);
}
if (overview.inventory_position) {
const leader = overview.inventory_position.top_items[0];
const leaderText = leader
@ -1228,6 +1260,11 @@ function businessOverviewRiskSynthesisLine(overview: BusinessOverview): string |
if (overview.debt_open_settlement_quality?.age_signal?.max_age_days !== null && overview.debt_open_settlement_quality?.age_signal?.max_age_days !== undefined) {
signals.push(`самый старый договорный возрастной сигнал ${overview.debt_open_settlement_quality.age_signal.max_age_days} дн.`);
}
if (overview.debt_staleness_risk_proxy) {
signals.push(
`staleness risk proxy открытых расчетов: ${debtStalenessRiskBandRu(overview.debt_staleness_risk_proxy.risk_band)}, возраст ${overview.debt_staleness_risk_proxy.max_contract_age_days} дн., концентрация старейшего крупного договора ${overview.debt_staleness_risk_proxy.top_contract_share_pct}%`
);
}
if (overview.inventory_position) {
signals.push(`складской остаток на дату ${overview.inventory_position.total_amount_human_ru}`);
if (overview.inventory_position.aging_signal?.max_age_days !== null && overview.inventory_position.aging_signal?.max_age_days !== undefined) {
@ -1257,6 +1294,7 @@ function businessOverviewExecutiveVerdictLine(overview: BusinessOverview): strin
overview.trading_margin_proxy ||
overview.debt_position ||
overview.debt_open_settlement_quality ||
overview.debt_staleness_risk_proxy ||
overview.inventory_position ||
overview.inventory_turnover_proxy ||
overview.inventory_staleness_risk_proxy
@ -1355,6 +1393,9 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
pushReason(reasonCodes, "answer_contains_business_overview_debt_age_signal");
}
}
if (pilot.derived_business_overview?.debt_staleness_risk_proxy) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_staleness_risk_proxy");
}
if (pilot.derived_business_overview?.inventory_position) {
pushReason(reasonCodes, "answer_contains_business_overview_inventory_position");
}

View File

@ -155,6 +155,7 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverview {
trading_margin_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewTradingMarginProxy | null;
debt_position: AssistantMcpDiscoveryDerivedBusinessOverviewDebtPosition | null;
debt_open_settlement_quality: AssistantMcpDiscoveryDerivedBusinessOverviewDebtOpenSettlementQuality | null;
debt_staleness_risk_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewDebtStalenessRiskProxy | null;
inventory_position: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition | null;
inventory_turnover_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy | null;
inventory_staleness_risk_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy | null;
@ -277,6 +278,21 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverviewDebtOpenSettlementQ
inference_basis: "open_contracts_confirmed_1c_balance_rows";
}
export interface AssistantMcpDiscoveryDerivedBusinessOverviewDebtStalenessRiskProxy {
as_of_date: string;
gross_open_amount: number;
gross_open_amount_human_ru: string;
oldest_contract_start_date: string;
max_contract_age_days: number;
top_contract: string;
top_contract_counterparty: string | null;
top_contract_amount: number;
top_contract_amount_human_ru: string;
top_contract_share_pct: number;
risk_band: "lower_visible_risk" | "watch" | "elevated" | "high";
inference_basis: "contract_date_age_and_open_balance_concentration_confirmed_1c_rows";
}
export interface AssistantMcpDiscoveryBusinessOverviewInventoryItemBucket {
item: string;
rows_with_amount: number;
@ -3111,6 +3127,74 @@ function deriveBusinessOverviewDebtOpenSettlementQuality(input: {
};
}
function debtStalenessRiskBand(input: {
maxContractAgeDays: number;
topContractSharePct: number;
}): AssistantMcpDiscoveryDerivedBusinessOverviewDebtStalenessRiskProxy["risk_band"] {
if (input.maxContractAgeDays >= 365 && input.topContractSharePct >= 50) {
return "high";
}
if (input.maxContractAgeDays >= 365 || input.topContractSharePct >= 50) {
return "elevated";
}
if (input.maxContractAgeDays >= 180 || input.topContractSharePct >= 35) {
return "watch";
}
return "lower_visible_risk";
}
function deriveBusinessOverviewDebtStalenessRiskProxy(
quality: AssistantMcpDiscoveryDerivedBusinessOverviewDebtOpenSettlementQuality | null
): AssistantMcpDiscoveryDerivedBusinessOverviewDebtStalenessRiskProxy | null {
const ageSignal = quality?.age_signal;
const topAgedContract = ageSignal?.top_aged_contracts[0];
const topContractSharePct = topAgedContract?.share_of_gross_open_amount_pct;
if (
!quality ||
!ageSignal?.oldest_start_date ||
ageSignal.max_age_days === null ||
ageSignal.max_age_days === undefined ||
!topAgedContract ||
topContractSharePct === null ||
topContractSharePct === undefined
) {
return null;
}
return {
as_of_date: quality.as_of_date,
gross_open_amount: quality.gross_open_amount,
gross_open_amount_human_ru: quality.gross_open_amount_human_ru,
oldest_contract_start_date: ageSignal.oldest_start_date,
max_contract_age_days: ageSignal.max_age_days,
top_contract: topAgedContract.contract,
top_contract_counterparty: topAgedContract.counterparty,
top_contract_amount: topAgedContract.total_amount,
top_contract_amount_human_ru: topAgedContract.total_amount_human_ru,
top_contract_share_pct: topContractSharePct,
risk_band: debtStalenessRiskBand({
maxContractAgeDays: ageSignal.max_age_days,
topContractSharePct
}),
inference_basis: "contract_date_age_and_open_balance_concentration_confirmed_1c_rows"
};
}
function debtStalenessRiskBandRu(
riskBand: AssistantMcpDiscoveryDerivedBusinessOverviewDebtStalenessRiskProxy["risk_band"]
): string {
if (riskBand === "high") {
return "высокая зона внимания";
}
if (riskBand === "elevated") {
return "повышенная зона внимания";
}
if (riskBand === "watch") {
return "зона наблюдения";
}
return "низкий видимый риск";
}
function daysBetweenIsoDates(leftIsoDate: string, rightIsoDate: string): number | null {
const leftMatch = leftIsoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
const rightMatch = rightIsoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
@ -3362,6 +3446,7 @@ function deriveBusinessOverview(input: {
openContractsResult: input.openContractsResult,
debtAsOfDate: input.debtAsOfDate
});
const debtStalenessRiskProxy = deriveBusinessOverviewDebtStalenessRiskProxy(debtOpenSettlementQuality);
const inventoryPosition = deriveBusinessOverviewInventoryPosition({
inventoryOnHandResult: input.inventoryOnHandResult,
inventoryAgingResult: input.inventoryAgingResult,
@ -3383,6 +3468,7 @@ function deriveBusinessOverview(input: {
Boolean(tradingMarginProxy),
Boolean(debtPosition),
Boolean(debtOpenSettlementQuality),
Boolean(debtStalenessRiskProxy),
Boolean(inventoryPosition),
Boolean(inventoryTurnoverProxy),
Boolean(inventoryStalenessRiskProxy)
@ -3406,6 +3492,7 @@ function deriveBusinessOverview(input: {
trading_margin_proxy: tradingMarginProxy,
debt_position: debtPosition,
debt_open_settlement_quality: debtOpenSettlementQuality,
debt_staleness_risk_proxy: debtStalenessRiskProxy,
inventory_position: inventoryPosition,
inventory_turnover_proxy: inventoryTurnoverProxy,
inventory_staleness_risk_proxy: inventoryStalenessRiskProxy,
@ -3565,6 +3652,13 @@ function buildBusinessOverviewConfirmedFacts(derived: AssistantMcpDiscoveryDeriv
);
}
}
if (derived.debt_staleness_risk_proxy) {
const proxy = derived.debt_staleness_risk_proxy;
const counterpartyText = proxy.top_contract_counterparty ? ` / ${proxy.top_contract_counterparty}` : "";
facts.push(
`Staleness risk proxy открытых расчетов на ${proxy.as_of_date}: самый старый договорный сигнал ${proxy.oldest_contract_start_date}, возраст ${proxy.max_contract_age_days} дн.; старейший крупный договор ${proxy.top_contract}${counterpartyText} держит ${proxy.top_contract_amount_human_ru} (${proxy.top_contract_share_pct}% брутто открытых остатков); оценка ${debtStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная просрочка, не кредитный риск и не due-date aging.`
);
}
if (derived.inventory_position) {
const leader = derived.inventory_position.top_items[0];
const leaderText = leader
@ -4737,6 +4831,9 @@ export async function executeAssistantMcpDiscoveryPilot(
pushReason(reasonCodes, "pilot_derived_business_overview_debt_age_signal_from_contract_dates");
}
}
if (derivedBusinessOverview.debt_staleness_risk_proxy) {
pushReason(reasonCodes, "pilot_derived_business_overview_debt_staleness_risk_proxy_from_confirmed_rows");
}
if (derivedBusinessOverview.inventory_position) {
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_position_from_confirmed_rows");
}

View File

@ -404,21 +404,29 @@ describe("assistant MCP discovery answer adapter", () => {
expect(draft.headline).toContain("долговой срез");
expect(draft.headline).toContain("качество открытых расчетов");
expect(draft.headline).toContain("возрастной сигнал открытых расчетов");
expect(draft.headline).toContain("staleness risk proxy открытых расчетов");
expect(draft.headline).toContain("договорные сроки оплаты/due-date просрочка");
expect(draft.confirmed_lines.join("\n")).toContain("Долговой срез на 2020-12-31");
expect(draft.confirmed_lines.join("\n")).toContain("Качество открытых расчетов на 2020-12-31");
expect(draft.confirmed_lines.join("\n")).toContain("Возрастной сигнал открытых расчетов");
expect(draft.confirmed_lines.join("\n")).toContain("не due-date анализ");
expect(draft.confirmed_lines.join("\n")).toContain("Staleness risk proxy открытых расчетов");
expect(draft.confirmed_lines.join("\n")).toContain("высокая зона внимания");
expect(draft.confirmed_lines.join("\n")).toContain("не due-date aging");
expect(draft.confirmed_lines.join("\n")).toContain("нетто");
expect(draft.inference_lines.join("\n")).toContain("Риски и контуры внимания");
expect(draft.inference_lines.join("\n")).toContain("самый старый договорный возрастной сигнал");
expect(draft.inference_lines.join("\n")).toContain("staleness risk proxy открытых расчетов");
expect(draft.inference_lines.join("\n")).toContain("Сводный LLM-аудит");
expect(draft.unknown_lines.join("\n")).toContain("due-date");
expect(draft.reason_codes).toContain("answer_contains_business_overview_debt_position");
expect(draft.reason_codes).toContain("answer_contains_business_overview_open_settlement_quality");
expect(draft.reason_codes).toContain("answer_contains_business_overview_debt_age_signal");
expect(draft.reason_codes).toContain("answer_contains_business_overview_debt_staleness_risk_proxy");
expect(draft.reason_codes).toContain("answer_contains_business_overview_analyst_synthesis");
expect(draft.must_not_claim).toContain("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis.");
expect(draft.must_not_claim).toContain("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt.");
expect(draft.must_not_claim).toContain("Do not present business overview debt staleness risk proxy as confirmed overdue debt, contractual delinquency, credit risk, or due-date aging.");
});
it("surfaces checked inventory-position snapshot in business overview without treating it as warehouse liquidity", async () => {

View File

@ -468,6 +468,14 @@ describe("assistant MCP discovery pilot executor", () => {
latest_start_date: "2020-03-15",
max_age_days: 690
});
expect(result.derived_business_overview?.debt_staleness_risk_proxy).toMatchObject({
as_of_date: "2020-12-31",
oldest_contract_start_date: "2019-02-10",
max_contract_age_days: 690,
top_contract_amount: 90000,
top_contract_share_pct: 75,
risk_band: "high"
});
expect(result.derived_business_overview?.debt_open_settlement_quality?.age_signal?.top_aged_contracts[0]).toMatchObject({
contract: "Договор А от 10.02.2019",
start_date: "2019-02-10",
@ -480,6 +488,8 @@ describe("assistant MCP discovery pilot executor", () => {
expect(result.evidence.confirmed_facts.join("\n")).toContain("Долговая позиция на 2020-12-31");
expect(result.evidence.confirmed_facts.join("\n")).toContain("Качество открытых расчетов на 2020-12-31");
expect(result.evidence.confirmed_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.confirmed_facts.join("\n")).toContain("не due-date анализ");
expect(result.evidence.unknown_facts.join("\n")).toContain("due-date");
expect(result.reason_codes).toContain("pilot_business_overview_debt_query_mcp_executed");
@ -487,6 +497,7 @@ describe("assistant MCP discovery pilot executor", () => {
expect(result.reason_codes).toContain("pilot_business_overview_open_contracts_query_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_business_overview_open_settlement_quality_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_derived_business_overview_debt_age_signal_from_contract_dates");
expect(result.reason_codes).toContain("pilot_derived_business_overview_debt_staleness_risk_proxy_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(10);
const receivablesCall = deps.executeAddressMcpQuery.mock.calls[3]?.[0];
const payablesCall = deps.executeAddressMcpQuery.mock.calls[4]?.[0];