Open-World: добавить концентрацию поставщиков в бизнес-обзор
This commit is contained in:
@@ -616,6 +616,7 @@ function buildMustNotClaim(pilot) {
|
||||
if (isBusinessOverviewPilot(pilot)) {
|
||||
claims.push("Do not present business overview cash-flow spread as profit or margin.");
|
||||
claims.push("Do not present business overview trading-margin proxy as clean profit, accounting financial result, or exact cost-of-sales margin.");
|
||||
claims.push("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
|
||||
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.");
|
||||
@@ -950,6 +951,10 @@ function derivedBusinessOverviewConfirmedLines(pilot) {
|
||||
if (leader) {
|
||||
lines.push(`Самый крупный подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`);
|
||||
}
|
||||
const supplierLeader = overview.top_suppliers?.[0];
|
||||
if (supplierLeader) {
|
||||
lines.push(`Самый крупный подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${supplierLeader.axis_value} — ${supplierLeader.total_amount_human_ru}.`);
|
||||
}
|
||||
if (overview.activity_period) {
|
||||
lines.push(`Окно подтвержденной активности в 1С: ${overview.activity_period.first_activity_date} — ${overview.activity_period.latest_activity_date}; ориентировочно ${overview.activity_period.duration_human_ru}.`);
|
||||
}
|
||||
@@ -1044,6 +1049,16 @@ function businessOverviewCustomerConcentrationLine(overview) {
|
||||
? `Концентрация входящего потока: крупнейший подтвержденный клиент ${leader.axis_value} дает около ${share} проверенных входящих поступлений (${leader.total_amount_human_ru}). Это сигнал зависимости от клиента, а не полный customer-risk аудит.`
|
||||
: `Крупнейший подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`;
|
||||
}
|
||||
function businessOverviewSupplierConcentrationLine(overview) {
|
||||
const leader = overview.top_suppliers?.[0];
|
||||
if (!leader || overview.outgoing_supplier_payout.total_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const share = percentText(leader.total_amount, overview.outgoing_supplier_payout.total_amount);
|
||||
return share
|
||||
? `Концентрация исходящего потока: крупнейший подтвержденный поставщик/получатель исходящих платежей ${leader.axis_value} держит около ${share} проверенных исходящих платежей (${leader.total_amount_human_ru}). Это сигнал procurement concentration по найденным строкам, а не полный vendor-risk аудит или структура всех расходов.`
|
||||
: `Крупнейший подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`;
|
||||
}
|
||||
function businessOverviewRiskSynthesisLine(overview) {
|
||||
const signals = [];
|
||||
if (overview.tax_position) {
|
||||
@@ -1128,6 +1143,7 @@ function derivedBusinessOverviewInferenceLines(pilot) {
|
||||
return [
|
||||
businessOverviewCashSynthesisLine(overview),
|
||||
businessOverviewCustomerConcentrationLine(overview),
|
||||
businessOverviewSupplierConcentrationLine(overview),
|
||||
businessOverviewRiskSynthesisLine(overview),
|
||||
businessOverviewExecutiveVerdictLine(overview),
|
||||
"Это аналитическая интерпретация подтвержденных строк, а не прибыль и не маржа: для финального управленческого вывода нужны отдельные расходы, себестоимость, закрывающие документы, долги, налоги и складская оборачиваемость."
|
||||
@@ -1183,6 +1199,9 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||
if (pilot.derived_business_overview?.trading_margin_proxy) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_trading_margin_proxy");
|
||||
}
|
||||
if (pilot.derived_business_overview?.top_suppliers?.length) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_supplier_concentration");
|
||||
}
|
||||
if (pilot.derived_business_overview?.debt_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_debt_position");
|
||||
}
|
||||
|
||||
+24
-1
@@ -2536,6 +2536,12 @@ function deriveBusinessOverview(input) {
|
||||
direction: "incoming_customer_revenue",
|
||||
rankingNeed: "top_desc"
|
||||
});
|
||||
const rankedOutgoing = deriveRankedValueFlow(input.outgoingResult, {
|
||||
organizationScope: input.organizationScope,
|
||||
periodScope: input.periodScope,
|
||||
direction: "outgoing_supplier_payout",
|
||||
rankingNeed: "top_desc"
|
||||
});
|
||||
const activityPeriod = deriveActivityPeriod(input.lifecycleResult);
|
||||
const taxPosition = deriveBusinessOverviewTaxPosition(input.taxResult, input.periodScope);
|
||||
const tradingMarginProxy = deriveBusinessOverviewTradingMarginProxy(input.tradingMarginResult, input.periodScope);
|
||||
@@ -2588,6 +2594,7 @@ function deriveBusinessOverview(input) {
|
||||
net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)),
|
||||
net_direction: netDirectionFromAmount(netAmount),
|
||||
top_customers: rankedIncoming?.ranked_values ?? [],
|
||||
top_suppliers: rankedOutgoing?.ranked_values ?? [],
|
||||
activity_period: activityPeriod,
|
||||
tax_position: taxPosition,
|
||||
trading_margin_proxy: tradingMarginProxy,
|
||||
@@ -2677,6 +2684,10 @@ function buildBusinessOverviewConfirmedFacts(derived) {
|
||||
const leader = derived.top_customers[0];
|
||||
facts.push(`Самый крупный подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`);
|
||||
}
|
||||
if (derived.top_suppliers.length > 0) {
|
||||
const leader = derived.top_suppliers[0];
|
||||
facts.push(`Самый крупный подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`);
|
||||
}
|
||||
if (derived.activity_period) {
|
||||
facts.push(`Подтвержденное окно активности в 1С: ${derived.activity_period.first_activity_date} — ${derived.activity_period.latest_activity_date}.`);
|
||||
}
|
||||
@@ -2765,10 +2776,19 @@ function buildBusinessOverviewInferredFacts(derived) {
|
||||
: derived.net_direction === "net_outgoing"
|
||||
? "денежный поток в проверенном срезе больше исходящий, чем входящий"
|
||||
: "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
|
||||
const supplierLeader = derived.top_suppliers[0];
|
||||
const supplierSharePct = supplierLeader
|
||||
? percentageOfTotal(supplierLeader.total_amount, derived.outgoing_supplier_payout.total_amount)
|
||||
: null;
|
||||
return [
|
||||
`Расчетное нетто по найденным строкам: ${derived.net_amount_human_ru}; ${direction}.`,
|
||||
supplierLeader
|
||||
? supplierSharePct !== null
|
||||
? `Крупнейший подтвержденный поставщик/получатель исходящих платежей ${supplierLeader.axis_value} держит около ${supplierSharePct}% проверенного исходящего потока (${supplierLeader.total_amount_human_ru}). Это procurement concentration proxy по найденным строкам, а не полный vendor-risk аудит.`
|
||||
: `Крупнейший подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${supplierLeader.axis_value} — ${supplierLeader.total_amount_human_ru}.`
|
||||
: null,
|
||||
"Это операционный денежный сигнал по найденным строкам 1С, а не прибыль, маржа или бухгалтерское заключение о здоровье бизнеса."
|
||||
];
|
||||
].filter((fact) => Boolean(fact));
|
||||
}
|
||||
function buildBusinessOverviewUnknownFacts(derived) {
|
||||
const missing = new Set(derived?.missing_signal_families ?? [
|
||||
@@ -3749,6 +3769,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||
if (derivedBusinessOverview.top_customers.length > 0) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_top_customers_from_confirmed_rows");
|
||||
}
|
||||
if (derivedBusinessOverview.top_suppliers.length > 0) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_top_suppliers_from_confirmed_rows");
|
||||
}
|
||||
if (derivedBusinessOverview.activity_period) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_activity_window_from_confirmed_rows");
|
||||
}
|
||||
|
||||
@@ -725,6 +725,7 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
|
||||
if (isBusinessOverviewPilot(pilot)) {
|
||||
claims.push("Do not present business overview cash-flow spread as profit or margin.");
|
||||
claims.push("Do not present business overview trading-margin proxy as clean profit, accounting financial result, or exact cost-of-sales margin.");
|
||||
claims.push("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
|
||||
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.");
|
||||
@@ -1106,6 +1107,12 @@ function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilot
|
||||
if (leader) {
|
||||
lines.push(`Самый крупный подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`);
|
||||
}
|
||||
const supplierLeader = overview.top_suppliers?.[0];
|
||||
if (supplierLeader) {
|
||||
lines.push(
|
||||
`Самый крупный подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${supplierLeader.axis_value} — ${supplierLeader.total_amount_human_ru}.`
|
||||
);
|
||||
}
|
||||
if (overview.activity_period) {
|
||||
lines.push(
|
||||
`Окно подтвержденной активности в 1С: ${overview.activity_period.first_activity_date} — ${overview.activity_period.latest_activity_date}; ориентировочно ${overview.activity_period.duration_human_ru}.`
|
||||
@@ -1227,6 +1234,17 @@ function businessOverviewCustomerConcentrationLine(overview: BusinessOverview):
|
||||
: `Крупнейший подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`;
|
||||
}
|
||||
|
||||
function businessOverviewSupplierConcentrationLine(overview: BusinessOverview): string | null {
|
||||
const leader = overview.top_suppliers?.[0];
|
||||
if (!leader || overview.outgoing_supplier_payout.total_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const share = percentText(leader.total_amount, overview.outgoing_supplier_payout.total_amount);
|
||||
return share
|
||||
? `Концентрация исходящего потока: крупнейший подтвержденный поставщик/получатель исходящих платежей ${leader.axis_value} держит около ${share} проверенных исходящих платежей (${leader.total_amount_human_ru}). Это сигнал procurement concentration по найденным строкам, а не полный vendor-risk аудит или структура всех расходов.`
|
||||
: `Крупнейший подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`;
|
||||
}
|
||||
|
||||
function businessOverviewRiskSynthesisLine(overview: BusinessOverview): string | null {
|
||||
const signals: string[] = [];
|
||||
if (overview.tax_position) {
|
||||
@@ -1322,6 +1340,7 @@ function derivedBusinessOverviewInferenceLines(pilot: AssistantMcpDiscoveryPilot
|
||||
return [
|
||||
businessOverviewCashSynthesisLine(overview),
|
||||
businessOverviewCustomerConcentrationLine(overview),
|
||||
businessOverviewSupplierConcentrationLine(overview),
|
||||
businessOverviewRiskSynthesisLine(overview),
|
||||
businessOverviewExecutiveVerdictLine(overview),
|
||||
"Это аналитическая интерпретация подтвержденных строк, а не прибыль и не маржа: для финального управленческого вывода нужны отдельные расходы, себестоимость, закрывающие документы, долги, налоги и складская оборачиваемость."
|
||||
@@ -1384,6 +1403,9 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||
if (pilot.derived_business_overview?.trading_margin_proxy) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_trading_margin_proxy");
|
||||
}
|
||||
if (pilot.derived_business_overview?.top_suppliers?.length) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_supplier_concentration");
|
||||
}
|
||||
if (pilot.derived_business_overview?.debt_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_debt_position");
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverview {
|
||||
net_amount_human_ru: string;
|
||||
net_direction: AssistantMcpDiscoveryNetDirection;
|
||||
top_customers: AssistantMcpDiscoveryRankedValueFlowBucket[];
|
||||
top_suppliers: AssistantMcpDiscoveryRankedValueFlowBucket[];
|
||||
activity_period: AssistantMcpDiscoveryDerivedActivityPeriod | null;
|
||||
tax_position: AssistantMcpDiscoveryDerivedBusinessOverviewTaxPosition | null;
|
||||
trading_margin_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewTradingMarginProxy | null;
|
||||
@@ -3434,6 +3435,12 @@ function deriveBusinessOverview(input: {
|
||||
direction: "incoming_customer_revenue",
|
||||
rankingNeed: "top_desc"
|
||||
});
|
||||
const rankedOutgoing = deriveRankedValueFlow(input.outgoingResult, {
|
||||
organizationScope: input.organizationScope,
|
||||
periodScope: input.periodScope,
|
||||
direction: "outgoing_supplier_payout",
|
||||
rankingNeed: "top_desc"
|
||||
});
|
||||
const activityPeriod = deriveActivityPeriod(input.lifecycleResult);
|
||||
const taxPosition = deriveBusinessOverviewTaxPosition(input.taxResult, input.periodScope);
|
||||
const tradingMarginProxy = deriveBusinessOverviewTradingMarginProxy(input.tradingMarginResult, input.periodScope);
|
||||
@@ -3487,6 +3494,7 @@ function deriveBusinessOverview(input: {
|
||||
net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)),
|
||||
net_direction: netDirectionFromAmount(netAmount),
|
||||
top_customers: rankedIncoming?.ranked_values ?? [],
|
||||
top_suppliers: rankedOutgoing?.ranked_values ?? [],
|
||||
activity_period: activityPeriod,
|
||||
tax_position: taxPosition,
|
||||
trading_margin_proxy: tradingMarginProxy,
|
||||
@@ -3597,6 +3605,12 @@ function buildBusinessOverviewConfirmedFacts(derived: AssistantMcpDiscoveryDeriv
|
||||
`Самый крупный подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`
|
||||
);
|
||||
}
|
||||
if (derived.top_suppliers.length > 0) {
|
||||
const leader = derived.top_suppliers[0];
|
||||
facts.push(
|
||||
`Самый крупный подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`
|
||||
);
|
||||
}
|
||||
if (derived.activity_period) {
|
||||
facts.push(
|
||||
`Подтвержденное окно активности в 1С: ${derived.activity_period.first_activity_date} — ${derived.activity_period.latest_activity_date}.`
|
||||
@@ -3713,10 +3727,19 @@ function buildBusinessOverviewInferredFacts(derived: AssistantMcpDiscoveryDerive
|
||||
: derived.net_direction === "net_outgoing"
|
||||
? "денежный поток в проверенном срезе больше исходящий, чем входящий"
|
||||
: "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
|
||||
const supplierLeader = derived.top_suppliers[0];
|
||||
const supplierSharePct = supplierLeader
|
||||
? percentageOfTotal(supplierLeader.total_amount, derived.outgoing_supplier_payout.total_amount)
|
||||
: null;
|
||||
return [
|
||||
`Расчетное нетто по найденным строкам: ${derived.net_amount_human_ru}; ${direction}.`,
|
||||
supplierLeader
|
||||
? supplierSharePct !== null
|
||||
? `Крупнейший подтвержденный поставщик/получатель исходящих платежей ${supplierLeader.axis_value} держит около ${supplierSharePct}% проверенного исходящего потока (${supplierLeader.total_amount_human_ru}). Это procurement concentration proxy по найденным строкам, а не полный vendor-risk аудит.`
|
||||
: `Крупнейший подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${supplierLeader.axis_value} — ${supplierLeader.total_amount_human_ru}.`
|
||||
: null,
|
||||
"Это операционный денежный сигнал по найденным строкам 1С, а не прибыль, маржа или бухгалтерское заключение о здоровье бизнеса."
|
||||
];
|
||||
].filter((fact): fact is string => Boolean(fact));
|
||||
}
|
||||
|
||||
function buildBusinessOverviewUnknownFacts(derived: AssistantMcpDiscoveryDerivedBusinessOverview | null): string[] {
|
||||
@@ -4813,6 +4836,9 @@ export async function executeAssistantMcpDiscoveryPilot(
|
||||
if (derivedBusinessOverview.top_customers.length > 0) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_top_customers_from_confirmed_rows");
|
||||
}
|
||||
if (derivedBusinessOverview.top_suppliers.length > 0) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_top_suppliers_from_confirmed_rows");
|
||||
}
|
||||
if (derivedBusinessOverview.activity_period) {
|
||||
pushReason(reasonCodes, "pilot_derived_business_overview_activity_window_from_confirmed_rows");
|
||||
}
|
||||
|
||||
@@ -239,14 +239,18 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||
expect(draft.headline).toContain("бизнес-обзор");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("Входящие поступления");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("Самый крупный подтвержденный клиент");
|
||||
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("Концентрация исходящего потока");
|
||||
expect(draft.inference_lines.join("\n")).toContain("Сводный LLM-аудит");
|
||||
expect(draft.inference_lines.join("\n")).toContain("не прибыль и не маржа");
|
||||
expect(draft.unknown_lines.join("\n")).toContain("Прибыль и маржа");
|
||||
expect(draft.unknown_lines.join("\n")).toContain("Налоговая/VAT-позиция");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview cash-flow spread as profit or margin.");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_supplier_concentration");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_analyst_synthesis");
|
||||
});
|
||||
|
||||
|
||||
@@ -189,12 +189,19 @@ describe("assistant MCP discovery pilot executor", () => {
|
||||
axis_value: "Клиент А",
|
||||
total_amount: 120000
|
||||
});
|
||||
expect(result.derived_business_overview?.top_suppliers[0]).toMatchObject({
|
||||
axis_value: "Поставщик А",
|
||||
total_amount: 150000
|
||||
});
|
||||
expect(result.derived_business_overview?.activity_period?.duration_total_months).toBe(11);
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("В 1С подтверждены входящие поступления");
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("Самый крупный подтвержденный поставщик");
|
||||
expect(result.evidence.inferred_facts.join("\n")).toContain("procurement concentration proxy");
|
||||
expect(result.evidence.unknown_facts).toContain(
|
||||
"Прибыль и маржа этим бизнес-обзором не подтверждены: нужны себестоимость, расходы и закрывающие документы."
|
||||
);
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_from_confirmed_rows");
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_top_suppliers_from_confirmed_rows");
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user