Open-World: добавить debt staleness risk proxy в бизнес-обзор
This commit is contained in:
+33
-1
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user