Open-World: усилить аналитический синтез бизнес-обзора
This commit is contained in:
parent
9b26bd05ef
commit
822baedcba
|
|
@ -23,8 +23,9 @@ If another document says `78%`, `87%`, `92%`, or `85%` for a module that is now
|
|||
- Completed active slice: `Business Overview Open-Settlement Quality Bridge`: explicit-period business overview can check open-contract settlement concentration on 60/62/76, while due-date aging/overdue debt remains unclaimed until a reviewed due-date route exists.
|
||||
- Completed active slice: `Selected-Item Profitability Route Bridge`: selected-object inventory profitability now has a bounded exact recipe over purchase/sale document rows, with explicit boundaries that this is a gross spread/margin proxy rather than company net profit.
|
||||
- Completed active slice: `Business Overview Contract-Date Debt Age Signal Bridge`: explicit-period open-settlement quality can now include contract-date age as a bounded signal, while due-date aging/overdue debt remains unclaimed until a reviewed payment-term route exists.
|
||||
- Completed active slice: `Business Overview Analyst Synthesis Layer`: business-overview answers now turn checked fact families into a bounded analyst note with operating scale, customer concentration, risk contours, and explicit profit/margin boundaries.
|
||||
- Next active slice: continue breadth into company-wide profit/margin, real due-date debt aging, inventory-liquidity/turnover, and broader unfamiliar 1C route families only where reviewed evidence routes exist.
|
||||
- Active module progress: `~70% (Open-World Bounded Autonomy Breadth)`.
|
||||
- Active module progress: `~73% (Open-World Bounded Autonomy Breadth)`.
|
||||
|
||||
## Reporting Rule
|
||||
|
||||
|
|
|
|||
|
|
@ -227,6 +227,26 @@ Local validation is accepted for this slice:
|
|||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `65/65` with `1` skipped.
|
||||
|
||||
## Slice 10 - Business Overview Analyst Synthesis Layer
|
||||
|
||||
This slice improves answer usefulness after the fact families have been checked.
|
||||
|
||||
It does not add a new 1C fact route. It upgrades the response draft so a broad company-analysis answer reads like a bounded analyst note instead of a metric list.
|
||||
|
||||
Implemented now:
|
||||
|
||||
- the answer adapter emits multiple business-overview inference lines: checked operating scale, incoming/outgoing net direction, customer concentration, debt/tax/stock risk contours, and a concise `Сводный LLM-аудит`;
|
||||
- customer concentration is computed only from confirmed top-customer and incoming-flow rows;
|
||||
- debt, open-settlement, contract-age, tax, and inventory signals are summarized only when those families are present in the current derived overview;
|
||||
- the synthesis keeps the hard boundary that this is not profit, margin, accounting net result, company health, due-date aging, or full inventory liquidity;
|
||||
- the draft carries a stable `answer_contains_business_overview_analyst_synthesis` reason code for replay review.
|
||||
|
||||
Local validation is accepted for this slice:
|
||||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `34/34` with `1` skipped.
|
||||
- `npm.cmd test -- assistantMcp`: passed `305/305` with `9` skipped.
|
||||
- `npm.cmd run build`: passed.
|
||||
|
||||
### Still Pending Breadth Slices
|
||||
|
||||
Grow this bridge beyond the first confirmed signal bundle:
|
||||
|
|
@ -311,3 +331,11 @@ Contract-date debt age signal validation:
|
|||
- `npm.cmd run build`: passed.
|
||||
|
||||
Graphify rebuild after Slice 9 code/doc sync: `6016 nodes`, `13098 edges`, `139 communities`.
|
||||
|
||||
Business-overview analyst synthesis validation:
|
||||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `34/34` with `1` skipped.
|
||||
- `npm.cmd test -- assistantMcp`: passed `305/305` with `9` skipped.
|
||||
- `npm.cmd run build`: passed.
|
||||
|
||||
Graphify rebuild after Slice 10 code/doc sync: `6023 nodes`, `13112 edges`, `136 communities`.
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ Status canon for planning:
|
|||
- The current completed breadth slice is `Business Overview Open-Settlement Quality Bridge`: explicit-period business overview can check open-contract settlement concentration, while due-date aging and confirmed overdue debt remain outside the answer until a reviewed due-date route exists.
|
||||
- The current completed breadth slice is `Selected-Item Profitability Route Bridge`: selected-object inventory profitability now has a bounded exact route over purchase/sale document rows and reports gross spread/margin proxy without claiming company net profit.
|
||||
- The current completed breadth slice is `Business Overview Contract-Date Debt Age Signal Bridge`: explicit-period open-settlement quality can include contract-date age as a bounded signal, while due-date aging/overdue debt still waits for reviewed payment-term evidence.
|
||||
- The current completed breadth slice is `Business Overview Analyst Synthesis Layer`: broad company-analysis answers now synthesize checked fact families into operating scale, customer concentration, risk contours, and a concise bounded LLM-audit.
|
||||
- The next active breadth slice continues breadth into company-wide profit/margin, real due-date debt aging, inventory-liquidity/turnover, 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).
|
||||
|
||||
|
|
@ -123,11 +124,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: `~70%`, 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, and contract-date debt age bridged locally; company-wide profit/margin, true due-date debt aging/overdue, and real inventory-liquidity expansion are still pending
|
||||
- active Open-World Bounded Autonomy Breadth progress: `~73%`, 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, and analyst synthesis added to business-overview answer drafting; company-wide profit/margin, true due-date debt aging/overdue, and real inventory-liquidity expansion are still pending
|
||||
- Post-F semantic integrity module progress: `~99%` operationally closed, with remaining risk now treated as next-slice discovery rather than an open blocker inside the closed slice
|
||||
- active inventory-stock breadth slice progress: `100%` for the declared scenario pack, not for arbitrary inventory questions
|
||||
- Planner Autonomy Consolidation progress: `100%` for the declared module, with catalog-fabric, value-flow arbitration, lifecycle bounded inference, broad-evaluation bridge, inventory catalog templates, inventory runtime-boundary honesty, exact inventory recipe bridging, unambiguous metadata-surface lane inference, catalog chain-template scoring, structured chain-match contract exposure, runtime/debug propagation, subject-aware bidirectional comparison arbitration, structured catalog-alignment verdicts, representative alignment regression guard, catalog-alignment reason-code telemetry, explicit `alignment_status` propagation, truth-harness/acceptance-matrix surfacing, soft divergence warning, `catalog_alignment_ok` acceptance invariant, step-level expected catalog-alignment assertions, phase66 and phase32 spec alignment expectations, AGENT source-catalog surfacing, generated phase83 mixed planner-brain replay spec, checked-source user-facing error sanitation, surface-grounded catalog promotion, and guarded live phase83 acceptance validated. Broader unfamiliar 1C asks are now next-module breadth work rather than an open blocker inside this declared slice
|
||||
- graph snapshot after latest rebuild: `6016 nodes`, `13098 edges`, `139 communities`
|
||||
- graph snapshot after latest rebuild: `6023 nodes`, `13112 edges`, `136 communities`
|
||||
- current regression-gate breakpoint:
|
||||
- the validated hot paths are no longer structurally broken;
|
||||
- flagship continuity collapse is no longer the primary risk;
|
||||
|
|
@ -175,6 +176,7 @@ Latest live proof now includes:
|
|||
- business-overview inventory-position fact-family bridge accepted live: `address_truth_harness_phase87_business_overview_inventory_position_live_20260504_inventory2` accepted `2/2`, proving explicit-date stock-on-hand position and all-time follow-up protection against stale inventory snapshot reuse
|
||||
- business-overview open-settlement quality bridge accepted live: `address_truth_harness_phase88_business_overview_open_settlement_quality_live_20260504_openquality4` accepted `2/2`, proving explicit-period open-contract concentration and all-time follow-up protection against stale open-contract/debt-quality reuse
|
||||
- business-overview contract-date debt age signal accepted locally: targeted executor/answer-adapter slice passed `65/65` with `1` skipped; full MCP-discovery slice passed `305/305` with `9` skipped; build passed; graphify rebuilt to `6016 nodes`, `13098 edges`, `139 communities`; contract-date age is surfaced as a bounded signal while due-date aging/overdue debt remains unclaimed
|
||||
- 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
|
||||
- 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`
|
||||
|
|
|
|||
|
|
@ -822,6 +822,20 @@ function businessOverviewNetDirectionRu(direction) {
|
|||
}
|
||||
return "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
|
||||
}
|
||||
function amountHumanRu(value) {
|
||||
const rounded = Math.round(Math.abs(value) * 100) / 100;
|
||||
return `${new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 2 }).format(rounded)} руб.`;
|
||||
}
|
||||
function percentOfTotal(part, total) {
|
||||
if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.round((part / total) * 10_000) / 100;
|
||||
}
|
||||
function percentText(part, total) {
|
||||
const pct = percentOfTotal(part, total);
|
||||
return pct === null ? null : `${pct}%`;
|
||||
}
|
||||
function derivedBusinessOverviewConfirmedLines(pilot) {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
|
|
@ -888,19 +902,94 @@ function derivedBusinessOverviewConfirmedLines(pilot) {
|
|||
}
|
||||
return lines;
|
||||
}
|
||||
function derivedBusinessOverviewInferenceLine(pilot) {
|
||||
function businessOverviewCashSynthesisLine(overview) {
|
||||
const incoming = overview.incoming_customer_revenue;
|
||||
const outgoing = overview.outgoing_supplier_payout;
|
||||
if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const checkedOperationalScale = Math.abs(incoming.total_amount) + Math.abs(outgoing.total_amount);
|
||||
return [
|
||||
`Аналитический вывод по оборотам: проверенный операционный размах ${amountHumanRu(checkedOperationalScale)}; входящий поток ${incoming.total_amount_human_ru}, исходящий ${outgoing.total_amount_human_ru}.`,
|
||||
`${businessOverviewNetDirectionRu(overview.net_direction)}; расчетное нетто ${overview.net_amount_human_ru}.`
|
||||
].join(" ");
|
||||
}
|
||||
function businessOverviewCustomerConcentrationLine(overview) {
|
||||
const leader = overview.top_customers[0];
|
||||
if (!leader || overview.incoming_customer_revenue.total_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const share = percentText(leader.total_amount, overview.incoming_customer_revenue.total_amount);
|
||||
return share
|
||||
? `Концентрация входящего потока: крупнейший подтвержденный клиент ${leader.axis_value} дает около ${share} проверенных входящих поступлений (${leader.total_amount_human_ru}). Это сигнал зависимости от клиента, а не полный customer-risk аудит.`
|
||||
: `Крупнейший подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`;
|
||||
}
|
||||
function businessOverviewRiskSynthesisLine(overview) {
|
||||
const signals = [];
|
||||
if (overview.tax_position) {
|
||||
const taxDirection = overview.tax_position.net_vat_direction === "vat_to_pay"
|
||||
? `НДС к уплате ${overview.tax_position.net_vat_amount_human_ru}`
|
||||
: overview.tax_position.net_vat_direction === "vat_to_recover_or_offset"
|
||||
? `НДС к вычету/зачету ${overview.tax_position.net_vat_amount_human_ru}`
|
||||
: "НДС-позиция сбалансирована";
|
||||
signals.push(taxDirection);
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
const debtDirection = overview.debt_position.net_debt_position_direction === "net_receivable"
|
||||
? `дебиторка больше кредиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
|
||||
: overview.debt_position.net_debt_position_direction === "net_payable"
|
||||
? `кредиторка больше дебиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
|
||||
: "дебиторка и кредиторка сбалансированы";
|
||||
signals.push(debtDirection);
|
||||
}
|
||||
if (overview.debt_open_settlement_quality?.concentration_top_contract_pct !== null && overview.debt_open_settlement_quality?.top_contracts[0]) {
|
||||
const topContract = overview.debt_open_settlement_quality.top_contracts[0];
|
||||
signals.push(`крупнейший открытый договор держит ${overview.debt_open_settlement_quality.concentration_top_contract_pct}% открытых остатков (${topContract.total_amount_human_ru})`);
|
||||
}
|
||||
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.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) {
|
||||
signals.push(`самый старый складской purchase-date сигнал ${overview.inventory_position.aging_signal.max_age_days} дн.`);
|
||||
}
|
||||
}
|
||||
return signals.length > 0
|
||||
? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.`
|
||||
: null;
|
||||
}
|
||||
function businessOverviewExecutiveVerdictLine(overview) {
|
||||
const hasCash = overview.incoming_customer_revenue.rows_with_amount > 0 || overview.outgoing_supplier_payout.rows_with_amount > 0;
|
||||
const hasExtraSignals = Boolean(overview.tax_position ||
|
||||
overview.debt_position ||
|
||||
overview.debt_open_settlement_quality ||
|
||||
overview.inventory_position);
|
||||
if (!hasCash && !hasExtraSignals) {
|
||||
return null;
|
||||
}
|
||||
const cashTone = overview.net_direction === "net_incoming"
|
||||
? "операционно входящий поток сильнее исходящего"
|
||||
: overview.net_direction === "net_outgoing"
|
||||
? "операционно исходящий поток сильнее входящего, это зона внимания к расходам/закупкам"
|
||||
: "операционный поток выглядит сбалансированным";
|
||||
const evidenceTone = hasExtraSignals
|
||||
? "часть налоговых, долговых или складских контуров уже отдельно проверена"
|
||||
: "налоги, долги и склад еще не дают проверенного управленческого контекста";
|
||||
return `Сводный LLM-аудит по подтвержденному: ${cashTone}; ${evidenceTone}. Это полезный управленческий срез по найденным строкам 1С, но не финальный вывод о прибыльности, марже или здоровье компании.`;
|
||||
}
|
||||
function derivedBusinessOverviewInferenceLines(pilot) {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
return null;
|
||||
}
|
||||
if (overview.incoming_customer_revenue.rows_with_amount <= 0 &&
|
||||
overview.outgoing_supplier_payout.rows_with_amount <= 0) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
`Расчетное нетто по найденным строкам: ${overview.net_amount_human_ru}; ${businessOverviewNetDirectionRu(overview.net_direction)}.`,
|
||||
"Это нормальный операционный сигнал, но не прибыль и не маржа: для управленческого вывода нужны отдельные расходы, себестоимость, долги, налоги и склад."
|
||||
].join(" ");
|
||||
businessOverviewCashSynthesisLine(overview),
|
||||
businessOverviewCustomerConcentrationLine(overview),
|
||||
businessOverviewRiskSynthesisLine(overview),
|
||||
businessOverviewExecutiveVerdictLine(overview),
|
||||
"Это аналитическая интерпретация подтвержденных строк, а не прибыль и не маржа: для финального управленческого вывода нужны отдельные расходы, себестоимость, закрывающие документы, долги, налоги и складская оборачиваемость."
|
||||
].filter((line) => Boolean(line));
|
||||
}
|
||||
function businessOverviewUnknownLines(pilot) {
|
||||
if (!pilot.derived_business_overview) {
|
||||
|
|
@ -915,17 +1004,22 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
|||
if (pilot.evidence.unknown_facts.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_unknown_fact_boundary");
|
||||
}
|
||||
if (pilot.evidence.inferred_facts.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||||
}
|
||||
const derivedInferenceLine = derivedBusinessOverviewInferenceLine(pilot) ??
|
||||
derivedActivityInferenceLine(pilot) ??
|
||||
const businessOverviewInferenceLines = derivedBusinessOverviewInferenceLines(pilot);
|
||||
const derivedInferenceLine = derivedActivityInferenceLine(pilot) ??
|
||||
derivedMetadataInferenceLine(pilot) ??
|
||||
derivedRankedValueFlowInferenceLine(pilot) ??
|
||||
derivedEntityResolutionInferenceLine(pilot);
|
||||
const inferenceLines = derivedInferenceLine
|
||||
? [derivedInferenceLine]
|
||||
: pilot.evidence.inferred_facts;
|
||||
const inferenceLines = businessOverviewInferenceLines.length > 0
|
||||
? businessOverviewInferenceLines
|
||||
: derivedInferenceLine
|
||||
? [derivedInferenceLine]
|
||||
: pilot.evidence.inferred_facts;
|
||||
if (inferenceLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||||
}
|
||||
if (businessOverviewInferenceLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_analyst_synthesis");
|
||||
}
|
||||
const derivedMetadataLine = derivedMetadataConfirmedLine(pilot);
|
||||
const derivedEntityResolutionLine = derivedEntityResolutionConfirmedLine(pilot);
|
||||
const derivedValueLine = derivedBidirectionalValueFlowConfirmedLine(pilot) ??
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ export interface AssistantMcpDiscoveryAnswerDraftContract {
|
|||
reason_codes: string[];
|
||||
}
|
||||
|
||||
type BusinessOverview = NonNullable<AssistantMcpDiscoveryPilotExecutionContract["derived_business_overview"]>;
|
||||
|
||||
function normalizeReasonCode(value: string): string | null {
|
||||
const normalized = value
|
||||
.trim()
|
||||
|
|
@ -965,6 +967,23 @@ function businessOverviewNetDirectionRu(direction: "net_incoming" | "net_outgoin
|
|||
return "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
|
||||
}
|
||||
|
||||
function amountHumanRu(value: number): string {
|
||||
const rounded = Math.round(Math.abs(value) * 100) / 100;
|
||||
return `${new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 2 }).format(rounded)} руб.`;
|
||||
}
|
||||
|
||||
function percentOfTotal(part: number, total: number): number | null {
|
||||
if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.round((part / total) * 10_000) / 100;
|
||||
}
|
||||
|
||||
function percentText(part: number, total: number): string | null {
|
||||
const pct = percentOfTotal(part, total);
|
||||
return pct === null ? null : `${pct}%`;
|
||||
}
|
||||
|
||||
function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
|
|
@ -1052,21 +1071,103 @@ function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilot
|
|||
return lines;
|
||||
}
|
||||
|
||||
function derivedBusinessOverviewInferenceLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||
function businessOverviewCashSynthesisLine(overview: BusinessOverview): string | null {
|
||||
const incoming = overview.incoming_customer_revenue;
|
||||
const outgoing = overview.outgoing_supplier_payout;
|
||||
if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const checkedOperationalScale = Math.abs(incoming.total_amount) + Math.abs(outgoing.total_amount);
|
||||
return [
|
||||
`Аналитический вывод по оборотам: проверенный операционный размах ${amountHumanRu(checkedOperationalScale)}; входящий поток ${incoming.total_amount_human_ru}, исходящий ${outgoing.total_amount_human_ru}.`,
|
||||
`${businessOverviewNetDirectionRu(overview.net_direction)}; расчетное нетто ${overview.net_amount_human_ru}.`
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
function businessOverviewCustomerConcentrationLine(overview: BusinessOverview): string | null {
|
||||
const leader = overview.top_customers[0];
|
||||
if (!leader || overview.incoming_customer_revenue.total_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const share = percentText(leader.total_amount, overview.incoming_customer_revenue.total_amount);
|
||||
return share
|
||||
? `Концентрация входящего потока: крупнейший подтвержденный клиент ${leader.axis_value} дает около ${share} проверенных входящих поступлений (${leader.total_amount_human_ru}). Это сигнал зависимости от клиента, а не полный customer-risk аудит.`
|
||||
: `Крупнейший подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`;
|
||||
}
|
||||
|
||||
function businessOverviewRiskSynthesisLine(overview: BusinessOverview): string | null {
|
||||
const signals: string[] = [];
|
||||
if (overview.tax_position) {
|
||||
const taxDirection =
|
||||
overview.tax_position.net_vat_direction === "vat_to_pay"
|
||||
? `НДС к уплате ${overview.tax_position.net_vat_amount_human_ru}`
|
||||
: overview.tax_position.net_vat_direction === "vat_to_recover_or_offset"
|
||||
? `НДС к вычету/зачету ${overview.tax_position.net_vat_amount_human_ru}`
|
||||
: "НДС-позиция сбалансирована";
|
||||
signals.push(taxDirection);
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
const debtDirection =
|
||||
overview.debt_position.net_debt_position_direction === "net_receivable"
|
||||
? `дебиторка больше кредиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
|
||||
: overview.debt_position.net_debt_position_direction === "net_payable"
|
||||
? `кредиторка больше дебиторки на ${overview.debt_position.net_debt_position_amount_human_ru}`
|
||||
: "дебиторка и кредиторка сбалансированы";
|
||||
signals.push(debtDirection);
|
||||
}
|
||||
if (overview.debt_open_settlement_quality?.concentration_top_contract_pct !== null && overview.debt_open_settlement_quality?.top_contracts[0]) {
|
||||
const topContract = overview.debt_open_settlement_quality.top_contracts[0];
|
||||
signals.push(`крупнейший открытый договор держит ${overview.debt_open_settlement_quality.concentration_top_contract_pct}% открытых остатков (${topContract.total_amount_human_ru})`);
|
||||
}
|
||||
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.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) {
|
||||
signals.push(`самый старый складской purchase-date сигнал ${overview.inventory_position.aging_signal.max_age_days} дн.`);
|
||||
}
|
||||
}
|
||||
return signals.length > 0
|
||||
? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.`
|
||||
: null;
|
||||
}
|
||||
|
||||
function businessOverviewExecutiveVerdictLine(overview: BusinessOverview): string | null {
|
||||
const hasCash = overview.incoming_customer_revenue.rows_with_amount > 0 || overview.outgoing_supplier_payout.rows_with_amount > 0;
|
||||
const hasExtraSignals = Boolean(
|
||||
overview.tax_position ||
|
||||
overview.debt_position ||
|
||||
overview.debt_open_settlement_quality ||
|
||||
overview.inventory_position
|
||||
);
|
||||
if (!hasCash && !hasExtraSignals) {
|
||||
return null;
|
||||
}
|
||||
const cashTone =
|
||||
overview.net_direction === "net_incoming"
|
||||
? "операционно входящий поток сильнее исходящего"
|
||||
: overview.net_direction === "net_outgoing"
|
||||
? "операционно исходящий поток сильнее входящего, это зона внимания к расходам/закупкам"
|
||||
: "операционный поток выглядит сбалансированным";
|
||||
const evidenceTone = hasExtraSignals
|
||||
? "часть налоговых, долговых или складских контуров уже отдельно проверена"
|
||||
: "налоги, долги и склад еще не дают проверенного управленческого контекста";
|
||||
return `Сводный LLM-аудит по подтвержденному: ${cashTone}; ${evidenceTone}. Это полезный управленческий срез по найденным строкам 1С, но не финальный вывод о прибыльности, марже или здоровье компании.`;
|
||||
}
|
||||
|
||||
function derivedBusinessOverviewInferenceLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
overview.incoming_customer_revenue.rows_with_amount <= 0 &&
|
||||
overview.outgoing_supplier_payout.rows_with_amount <= 0
|
||||
) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
`Расчетное нетто по найденным строкам: ${overview.net_amount_human_ru}; ${businessOverviewNetDirectionRu(overview.net_direction)}.`,
|
||||
"Это нормальный операционный сигнал, но не прибыль и не маржа: для управленческого вывода нужны отдельные расходы, себестоимость, долги, налоги и склад."
|
||||
].join(" ");
|
||||
businessOverviewCashSynthesisLine(overview),
|
||||
businessOverviewCustomerConcentrationLine(overview),
|
||||
businessOverviewRiskSynthesisLine(overview),
|
||||
businessOverviewExecutiveVerdictLine(overview),
|
||||
"Это аналитическая интерпретация подтвержденных строк, а не прибыль и не маржа: для финального управленческого вывода нужны отдельные расходы, себестоимость, закрывающие документы, долги, налоги и складская оборачиваемость."
|
||||
].filter((line): line is string => Boolean(line));
|
||||
}
|
||||
|
||||
function businessOverviewUnknownLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||
|
|
@ -1085,18 +1186,23 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
|||
if (pilot.evidence.unknown_facts.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_unknown_fact_boundary");
|
||||
}
|
||||
if (pilot.evidence.inferred_facts.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||||
}
|
||||
const businessOverviewInferenceLines = derivedBusinessOverviewInferenceLines(pilot);
|
||||
const derivedInferenceLine =
|
||||
derivedBusinessOverviewInferenceLine(pilot) ??
|
||||
derivedActivityInferenceLine(pilot) ??
|
||||
derivedMetadataInferenceLine(pilot) ??
|
||||
derivedRankedValueFlowInferenceLine(pilot) ??
|
||||
derivedEntityResolutionInferenceLine(pilot);
|
||||
const inferenceLines = derivedInferenceLine
|
||||
const inferenceLines = businessOverviewInferenceLines.length > 0
|
||||
? businessOverviewInferenceLines
|
||||
: derivedInferenceLine
|
||||
? [derivedInferenceLine]
|
||||
: pilot.evidence.inferred_facts;
|
||||
if (inferenceLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||||
}
|
||||
if (businessOverviewInferenceLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_analyst_synthesis");
|
||||
}
|
||||
const derivedMetadataLine = derivedMetadataConfirmedLine(pilot);
|
||||
const derivedEntityResolutionLine = derivedEntityResolutionConfirmedLine(pilot);
|
||||
const derivedValueLine =
|
||||
|
|
|
|||
|
|
@ -239,11 +239,15 @@ 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.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.reason_codes).toContain("answer_contains_business_overview");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_analyst_synthesis");
|
||||
});
|
||||
|
||||
it("surfaces checked VAT/tax position in business overview without treating it as profit", async () => {
|
||||
|
|
@ -386,10 +390,14 @@ describe("assistant MCP discovery answer adapter", () => {
|
|||
expect(draft.confirmed_lines.join("\n")).toContain("Возрастной сигнал открытых расчетов");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("не due-date анализ");
|
||||
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("Сводный 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_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.");
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue