Open-World: добавить профиль документов в бизнес-обзор

This commit is contained in:
dctouch 2026-05-05 13:09:42 +03:00
parent e803942472
commit 466b3b66e5
9 changed files with 479 additions and 26 deletions

View File

@ -36,8 +36,9 @@ If another document says `78%`, `87%`, `92%`, or `85%` for a module that is now
- Completed active slice: `Business Overview Debt Due-Date Boundary Bridge`: organization-level overdue debt, debt quality, debt aging, due-date, and credit-risk wording now routes to `business_overview`, while explicit buyer/debtor lists stay in exact receivables routes with a due-date proof boundary.
- Completed active slice: `Business Overview Inventory Reserve/Liquidation Boundary Bridge`: organization-level inventory reserve, write-off, obsolete-stock, and liquidation-value wording now routes to `business_overview`, while explicit item/stock lists stay in exact inventory routes with a reserve/liquidation proof boundary.
- Completed active slice: `Business Overview Supplier/Procurement Quality Boundary Bridge`: organization-level supplier concentration, vendor-risk, dependency, and procurement-quality wording now routes to `business_overview`, while supplier payment/open-settlement/doc questions stay in exact supplier/payables routes with a vendor-risk proof boundary.
- Completed active slice: `Business Overview Document/Account Activity Profile Bridge`: business overview now executes the reviewed `document_type_and_account_section_profile` recipe and surfaces confirmed operational activity mix without claiming process quality, accounting correctness, or complete 1C activity coverage.
- 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: `~96% (Open-World Bounded Autonomy Breadth)`.
- Active module progress: `~97% (Open-World Bounded Autonomy Breadth)`.
## Reporting Rule
@ -74,7 +75,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, customer and supplier concentration, yearly operating-flow dynamics, explicit profit/margin wording boundaries, explicit debt due-date wording boundaries, explicit inventory reserve/liquidation wording boundaries, explicit supplier/procurement-quality wording boundaries, 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, confirmed vendor-risk/procurement-quality analysis, and confirmed reserve/write-off/liquidation inventory evidence families;
- extend `business_overview` beyond money-flow/activity, customer and supplier concentration, document/account-section activity mix, yearly operating-flow dynamics, explicit profit/margin wording boundaries, explicit debt due-date wording boundaries, explicit inventory reserve/liquidation wording boundaries, explicit supplier/procurement-quality wording boundaries, 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, confirmed vendor-risk/procurement-quality analysis, 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

@ -646,3 +646,28 @@ Local validation is accepted for this slice:
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 22 code/doc sync: `6062 nodes`, `13207 edges`, `136 communities`.
## Slice 23 - Business Overview Document/Account Activity Profile Bridge
This slice adds a low-risk operational-mix fact family to broad company analysis.
The runtime already has a reviewed `document_type_and_account_section_profile` recipe. Before this slice, that evidence existed as a separate exact profile but was not visible inside `business_overview`, so a broad company-analysis answer could discuss money flow, tax/debt/stock proxies, and concentration while still missing a basic activity-shape question: which document types and accounting sections dominate the visible 1C activity.
Implemented now:
- `business_overview` selects and executes the reviewed `document_type_and_account_section_profile` recipe as an optional current-turn profile probe;
- the pilot derives `document_activity_profile` with top document types, top account sections, totals, shares, matched rows, and the stable basis `document_type_and_account_section_profile_confirmed_1c_rows`;
- evidence and answer drafting can surface the leading document type and leading account section as confirmed operational activity mix;
- risk synthesis and headline wording can mention this activity mix when present;
- `must_not_claim` explicitly forbids treating the profile as process quality, accounting correctness, or complete 1C activity coverage;
- missing exact accounting profit, due-date debt aging, vendor-risk/procurement quality, and reserve/liquidation inventory evidence remain pending reviewed-route work.
This is not a process-audit engine. It is a bounded management context bridge: "what kind of confirmed 1C activity is visible" without pretending to prove whether the business processes or accounting are healthy.
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 `416`.
- `npm.cmd run build`: passed.
Graphify rebuild after Slice 23 code/doc sync: `6066 nodes`, `13222 edges`, `136 communities`.

View File

@ -71,6 +71,7 @@ Status canon for planning:
- The current completed breadth slice is `Business Overview Debt Due-Date Boundary Bridge`: organization-level overdue debt, debt quality, debt aging, due-date, and credit-risk wording now reaches `business_overview`, while explicit buyer/debtor lists stay in exact receivables routes with a due-date proof boundary.
- The current completed breadth slice is `Business Overview Inventory Reserve/Liquidation Boundary Bridge`: organization-level inventory reserve, write-off, obsolete-stock, and liquidation-value wording now reaches `business_overview`, while explicit item/stock lists stay in exact inventory routes with a reserve/liquidation proof boundary.
- The current completed breadth slice is `Business Overview Supplier/Procurement Quality Boundary Bridge`: organization-level supplier concentration, vendor-risk, dependency, and procurement-quality wording now reaches `business_overview`, while supplier payment/open-settlement/doc questions stay in exact supplier/payables routes with a vendor-risk proof boundary.
- The current completed breadth slice is `Business Overview Document/Account Activity Profile Bridge`: business overview now executes the reviewed document-type/account-section profile and can surface confirmed operational activity mix without claiming process quality, accounting correctness, or full 1C coverage.
- 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).
@ -136,11 +137,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: `~96%`, 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, debt due-date boundary arbitration bridged locally, inventory reserve/liquidation boundary arbitration bridged locally, supplier/procurement-quality boundary arbitration bridged locally, supplier concentration proxy bridged locally, yearly operating-flow proxy bridged locally, earnings/best-year wording arbitration bridged locally, profit/margin wording boundary arbitration 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, confirmed vendor-risk/procurement-quality analysis, and confirmed reserve/write-off/liquidation inventory evidence are still pending
- active Open-World Bounded Autonomy Breadth progress: `~97%`, 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, debt due-date boundary arbitration bridged locally, inventory reserve/liquidation boundary arbitration bridged locally, supplier/procurement-quality boundary arbitration bridged locally, supplier concentration proxy bridged locally, document/account-section activity profile bridged locally, yearly operating-flow proxy bridged locally, earnings/best-year wording arbitration bridged locally, profit/margin wording boundary arbitration 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, confirmed vendor-risk/procurement-quality analysis, 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: `6052 nodes`, `13187 edges`, `138 communities`
- graph snapshot after latest rebuild: `6066 nodes`, `13222 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;

View File

@ -399,6 +399,9 @@ function headlineFor(mode, pilot) {
if (overview.activity_period) {
families.push("активность");
}
if (overview.document_activity_profile) {
families.push("профиль типов документов и разделов учета");
}
if (overview.tax_position) {
families.push("НДС-позиция");
}
@ -621,6 +624,7 @@ function buildMustNotClaim(pilot) {
claims.push("Do not present business overview yearly operating-flow breakdown as profit, financial result, or a complete annual P&L.");
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 present business overview document/account-section activity profile as process quality, accounting correctness, or completeness of all 1C activity.");
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.");
@ -977,6 +981,23 @@ function derivedBusinessOverviewConfirmedLines(pilot) {
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}.`);
}
if (overview.document_activity_profile) {
const profile = overview.document_activity_profile;
const topDocument = profile.top_document_types[0];
const topSection = profile.top_account_sections[0];
const parts = [];
if (topDocument) {
const shareText = topDocument.share_pct === null ? "" : ` (${topDocument.share_pct}%)`;
parts.push(`ведущий тип документов ${topDocument.document_type}${topDocument.count} документов${shareText}`);
}
if (topSection) {
const shareText = topSection.share_pct === null ? "" : ` (${topSection.share_pct}%)`;
parts.push(`ведущий раздел учета ${topSection.account_section}${topSection.operation_count} операций${shareText}`);
}
if (parts.length > 0) {
lines.push(`Профиль операционной активности${organization}${period}: ${parts.join("; ")}. Это activity mix по найденным строкам 1С, а не аудит качества учета или полноты процессов.`);
}
}
if (overview.tax_position) {
const taxDirection = overview.tax_position.net_vat_direction === "vat_to_pay"
? "к уплате"
@ -1138,6 +1159,17 @@ function businessOverviewRiskSynthesisLine(overview) {
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.document_activity_profile) {
const topDocument = overview.document_activity_profile.top_document_types[0];
const topSection = overview.document_activity_profile.top_account_sections[0];
const parts = [
topDocument ? `ведущий тип документов ${topDocument.document_type}` : null,
topSection ? `ведущий раздел учета ${topSection.account_section}` : null
].filter((item) => Boolean(item));
if (parts.length > 0) {
signals.push(`операционный activity mix: ${parts.join(", ")}`);
}
}
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) {
@ -1159,7 +1191,7 @@ function businessOverviewRiskSynthesisLine(overview) {
}
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 ||
const hasTaxDebtInventorySignals = Boolean(overview.tax_position ||
overview.trading_margin_proxy ||
overview.debt_position ||
overview.debt_open_settlement_quality ||
@ -1167,6 +1199,8 @@ function businessOverviewExecutiveVerdictLine(overview) {
overview.inventory_position ||
overview.inventory_turnover_proxy ||
overview.inventory_staleness_risk_proxy);
const hasDocumentActivitySignal = Boolean(overview.document_activity_profile);
const hasExtraSignals = hasTaxDebtInventorySignals || hasDocumentActivitySignal;
if (!hasCash && !hasExtraSignals) {
return null;
}
@ -1175,8 +1209,10 @@ function businessOverviewExecutiveVerdictLine(overview) {
: overview.net_direction === "net_outgoing"
? "операционно исходящий поток сильнее входящего, это зона внимания к расходам/закупкам"
: "операционный поток выглядит сбалансированным";
const evidenceTone = hasExtraSignals
const evidenceTone = hasTaxDebtInventorySignals
? "часть налоговых, долговых или складских контуров уже отдельно проверена"
: hasDocumentActivitySignal
? "операционный activity mix по типам документов и разделам учета уже отдельно проверен"
: "налоги, долги и склад еще не дают проверенного управленческого контекста";
return `Сводный LLM-аудит по подтвержденному: ${cashTone}; ${evidenceTone}. Это полезный управленческий срез по найденным строкам 1С, но не финальный вывод о прибыльности, марже или здоровье компании.`;
}
@ -1251,6 +1287,9 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
if (pilot.derived_business_overview?.yearly_breakdown?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_yearly_operating_breakdown");
}
if (pilot.derived_business_overview?.document_activity_profile) {
pushReason(reasonCodes, "answer_contains_business_overview_document_activity_profile");
}
if (pilot.derived_business_overview?.debt_position) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_position");
}

View File

@ -160,6 +160,17 @@ function buildValueFlowFilters(planner) {
sort: "period_asc"
};
}
function buildBusinessOverviewDocumentActivityFilters(planner) {
const meaning = planner.discovery_plan.turn_meaning_ref;
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
return {
...dateScopeToFilters(dateScope),
...(organization ? { organization } : {}),
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
sort: "period_asc"
};
}
function buildBusinessOverviewTaxFilters(planner) {
const meaning = planner.discovery_plan.turn_meaning_ref;
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
@ -1836,6 +1847,75 @@ function deriveBusinessOverviewYearlyBreakdown(input) {
};
});
}
function normalizeBusinessOverviewActivityMarker(row) {
const marker = toNonEmptyString(rowDocumentValue(row));
return marker ? marker.trim().toUpperCase() : null;
}
function normalizeBusinessOverviewAccountSection(value) {
const normalized = String(value ?? "").replace(/\s+/g, "").trim();
if (!normalized) {
return null;
}
const sectionMatch = normalized.match(/^(\d{2})(?:[.\-_/]|$)/);
return sectionMatch?.[1] ?? normalized;
}
function deriveBusinessOverviewDocumentActivityProfile(result, periodScope) {
if (!result || result.error || result.matched_rows <= 0) {
return null;
}
const documentTypeBuckets = new Map();
const accountSectionBuckets = new Map();
for (const row of result.rows) {
const marker = normalizeBusinessOverviewActivityMarker(row);
const count = rowAmountValue(row);
if (!marker || count === null || count <= 0) {
continue;
}
if (marker === "DOC_TYPE_DOCS") {
const documentType = rowDebitAccountValue(row) ?? rowAccountValue(row);
if (documentType) {
documentTypeBuckets.set(documentType, (documentTypeBuckets.get(documentType) ?? 0) + count);
}
continue;
}
if (marker === "SECTION_DT_OPS" || marker === "SECTION_KT_OPS") {
const section = normalizeBusinessOverviewAccountSection(rowDebitAccountValue(row) ?? rowAccountValue(row));
if (section) {
accountSectionBuckets.set(section, (accountSectionBuckets.get(section) ?? 0) + count);
}
}
}
const totalDocumentTypeCount = Array.from(documentTypeBuckets.values()).reduce((sum, count) => sum + count, 0);
const totalAccountSectionOperations = Array.from(accountSectionBuckets.values()).reduce((sum, count) => sum + count, 0);
const topDocumentTypes = Array.from(documentTypeBuckets.entries())
.map(([documentType, count]) => ({
document_type: documentType,
count,
share_pct: percentageOfTotal(count, totalDocumentTypeCount)
}))
.sort((left, right) => right.count - left.count || left.document_type.localeCompare(right.document_type, "ru"))
.slice(0, 5);
const topAccountSections = Array.from(accountSectionBuckets.entries())
.map(([accountSection, operationCount]) => ({
account_section: accountSection,
operation_count: operationCount,
share_pct: percentageOfTotal(operationCount, totalAccountSectionOperations)
}))
.sort((left, right) => right.operation_count - left.operation_count || left.account_section.localeCompare(right.account_section, "ru"))
.slice(0, 5);
if (topDocumentTypes.length <= 0 && topAccountSections.length <= 0) {
return null;
}
return {
period_scope: periodScope,
rows_matched: result.matched_rows,
total_document_type_count: totalDocumentTypeCount,
total_account_section_operations: totalAccountSectionOperations,
top_document_types: topDocumentTypes,
top_account_sections: topAccountSections,
inference_basis: "document_type_and_account_section_profile_confirmed_1c_rows"
};
}
function deriveValueFlow(result, counterparty, periodScope, direction, aggregationAxis) {
if (!result || result.error || result.matched_rows <= 0) {
return null;
@ -2633,6 +2713,7 @@ function deriveBusinessOverview(input) {
openContractsResult: input.openContractsResult,
debtAsOfDate: input.debtAsOfDate
});
const documentActivityProfile = deriveBusinessOverviewDocumentActivityProfile(input.documentActivityProfileResult, input.periodScope);
const debtStalenessRiskProxy = deriveBusinessOverviewDebtStalenessRiskProxy(debtOpenSettlementQuality);
const inventoryPosition = deriveBusinessOverviewInventoryPosition({
inventoryOnHandResult: input.inventoryOnHandResult,
@ -2656,6 +2737,7 @@ function deriveBusinessOverview(input) {
Boolean(debtPosition),
Boolean(debtOpenSettlementQuality),
Boolean(debtStalenessRiskProxy),
Boolean(documentActivityProfile),
Boolean(inventoryPosition),
Boolean(inventoryTurnoverProxy),
Boolean(inventoryStalenessRiskProxy)
@ -2684,6 +2766,7 @@ function deriveBusinessOverview(input) {
inventory_position: inventoryPosition,
inventory_turnover_proxy: inventoryTurnoverProxy,
inventory_staleness_risk_proxy: inventoryStalenessRiskProxy,
document_activity_profile: documentActivityProfile,
coverage_limited_by_probe_limit: incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
checked_signal_count: checkedSignalCount,
missing_signal_families: [
@ -2700,7 +2783,9 @@ function deriveBusinessOverview(input) {
: "inventory_position",
inventoryPosition?.aging_signal ? null : "inventory_aging_quality"
].filter((item) => Boolean(item)),
inference_basis: inventoryPosition
inference_basis: documentActivityProfile
? "business_overview_from_confirmed_1c_multi_family_rows"
: inventoryPosition
? "business_overview_from_confirmed_1c_multi_family_rows"
: debtOpenSettlementQuality
? "business_overview_from_confirmed_1c_multi_family_rows"
@ -2739,6 +2824,9 @@ function summarizeBusinessOverviewRows(input) {
if (input.openContractsResult && !input.openContractsResult.error) {
parts.push(`${input.openContractsResult.fetched_rows} open-contract rows fetched, ${input.openContractsResult.matched_rows} matched`);
}
if (input.documentActivityProfileResult && !input.documentActivityProfileResult.error) {
parts.push(`${input.documentActivityProfileResult.fetched_rows} document/account-section profile rows fetched, ${input.documentActivityProfileResult.matched_rows} matched`);
}
if (input.inventoryOnHandResult && !input.inventoryOnHandResult.error) {
parts.push(`${input.inventoryOnHandResult.fetched_rows} inventory on-hand rows fetched, ${input.inventoryOnHandResult.matched_rows} matched`);
}
@ -2774,6 +2862,23 @@ function buildBusinessOverviewConfirmedFacts(derived) {
if (derived.activity_period) {
facts.push(`Подтвержденное окно активности в 1С: ${derived.activity_period.first_activity_date}${derived.activity_period.latest_activity_date}.`);
}
if (derived.document_activity_profile) {
const profile = derived.document_activity_profile;
const topDocument = profile.top_document_types[0];
const topSection = profile.top_account_sections[0];
const parts = [];
if (topDocument) {
const shareText = topDocument.share_pct === null ? "" : ` (${topDocument.share_pct}%)`;
parts.push(`ведущий тип документов ${topDocument.document_type}${topDocument.count} документов${shareText}`);
}
if (topSection) {
const shareText = topSection.share_pct === null ? "" : ` (${topSection.share_pct}%)`;
parts.push(`ведущий раздел учета ${topSection.account_section}${topSection.operation_count} операций${shareText}`);
}
if (parts.length > 0) {
facts.push(`Профиль операционной активности${organization}${period} подтвержден по типам документов и разделам учета 1С: ${parts.join("; ")}. Это activity mix, а не аудит качества учета или полноты бизнес-процессов.`);
}
}
if (derived.tax_position) {
const taxDirection = derived.tax_position.net_vat_direction === "vat_to_pay"
? "к уплате"
@ -3537,10 +3642,12 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
let receivablesResult = null;
let payablesResult = null;
let openContractsResult = null;
let documentActivityProfileResult = null;
let inventoryOnHandResult = null;
let inventoryAgingResult = null;
const valueFilters = buildValueFlowFilters(planner);
const lifecycleFilters = buildLifecycleFilters(planner);
const documentActivityFilters = buildBusinessOverviewDocumentActivityFilters(planner);
const taxFilters = buildBusinessOverviewTaxFilters(planner);
const tradingMarginFilters = buildBusinessOverviewTradingMarginFilters(planner);
const debtFilters = buildBusinessOverviewDebtFilters(planner);
@ -3550,6 +3657,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
const incomingSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("customer_revenue_and_payments", valueFilters);
const outgoingSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("supplier_payouts_profile", valueFilters);
const lifecycleSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("counterparty_activity_lifecycle", lifecycleFilters);
const documentActivitySelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("document_type_and_account_section_profile", documentActivityFilters);
const taxSelection = taxFilters
? (0, addressRecipeCatalog_1.selectAddressRecipe)("vat_liability_confirmed_for_tax_period", taxFilters)
: null;
@ -3601,6 +3709,13 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
};
}
pushReason(reasonCodes, "pilot_business_overview_recipes_selected");
if (documentActivitySelection.selected_recipe) {
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_recipe_selected");
}
else {
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_recipe_not_available");
pushUnique(queryLimitations, "Business overview document/account-section profile requires an executable document-section profile recipe");
}
if (taxSelection?.selected_recipe) {
pushReason(reasonCodes, "pilot_business_overview_tax_recipe_selected");
}
@ -3824,6 +3939,15 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
});
probeResults.push(queryResultToProbeResult(step.primitive_id, tradingMarginResult));
}
if (documentActivitySelection.selected_recipe) {
const documentActivityPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(documentActivitySelection.selected_recipe, documentActivityFilters);
documentActivityProfileResult = await runtimeDeps.executeAddressMcpQuery({
query: documentActivityPlan.query,
limit: documentActivityPlan.limit,
account_scope: documentActivityPlan.account_scope
});
probeResults.push(queryResultToProbeResult(step.primitive_id, documentActivityProfileResult));
}
if (lifecycleResult.error) {
pushUnique(queryLimitations, lifecycleResult.error);
pushReason(reasonCodes, "pilot_business_overview_query_documents_mcp_error");
@ -3838,6 +3962,13 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
else if (tradingMarginResult) {
pushReason(reasonCodes, "pilot_business_overview_trading_margin_query_mcp_executed");
}
if (documentActivityProfileResult?.error) {
pushUnique(queryLimitations, documentActivityProfileResult.error);
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_query_mcp_error");
}
else if (documentActivityProfileResult) {
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_query_mcp_executed");
}
continue;
}
skippedPrimitives.push(step.primitive_id);
@ -3852,6 +3983,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
receivablesResult,
payablesResult,
openContractsResult,
documentActivityProfileResult,
debtAsOfDate,
inventoryOnHandResult,
inventoryAgingResult,
@ -3873,6 +4005,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
if (derivedBusinessOverview.activity_period) {
pushReason(reasonCodes, "pilot_derived_business_overview_activity_window_from_confirmed_rows");
}
if (derivedBusinessOverview.document_activity_profile) {
pushReason(reasonCodes, "pilot_derived_business_overview_document_activity_profile_from_confirmed_rows");
}
if (derivedBusinessOverview.tax_position) {
pushReason(reasonCodes, "pilot_derived_business_overview_tax_position_from_confirmed_rows");
}
@ -3910,6 +4045,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
receivablesResult,
payablesResult,
openContractsResult,
documentActivityProfileResult,
inventoryOnHandResult,
inventoryAgingResult
});

View File

@ -498,6 +498,9 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
if (overview.activity_period) {
families.push("активность");
}
if (overview.document_activity_profile) {
families.push("профиль типов документов и разделов учета");
}
if (overview.tax_position) {
families.push("НДС-позиция");
}
@ -730,6 +733,7 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
claims.push("Do not present business overview yearly operating-flow breakdown as profit, financial result, or a complete annual P&L.");
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 present business overview document/account-section activity profile as process quality, accounting correctness, or completeness of all 1C activity.");
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.");
@ -1141,6 +1145,25 @@ function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilot
`Окно подтвержденной активности в 1С: ${overview.activity_period.first_activity_date}${overview.activity_period.latest_activity_date}; ориентировочно ${overview.activity_period.duration_human_ru}.`
);
}
if (overview.document_activity_profile) {
const profile = overview.document_activity_profile;
const topDocument = profile.top_document_types[0];
const topSection = profile.top_account_sections[0];
const parts: string[] = [];
if (topDocument) {
const shareText = topDocument.share_pct === null ? "" : ` (${topDocument.share_pct}%)`;
parts.push(`ведущий тип документов ${topDocument.document_type}${topDocument.count} документов${shareText}`);
}
if (topSection) {
const shareText = topSection.share_pct === null ? "" : ` (${topSection.share_pct}%)`;
parts.push(`ведущий раздел учета ${topSection.account_section}${topSection.operation_count} операций${shareText}`);
}
if (parts.length > 0) {
lines.push(
`Профиль операционной активности${organization}${period}: ${parts.join("; ")}. Это activity mix по найденным строкам 1С, а не аудит качества учета или полноты процессов.`
);
}
}
if (overview.tax_position) {
const taxDirection =
overview.tax_position.net_vat_direction === "vat_to_pay"
@ -1333,6 +1356,17 @@ function businessOverviewRiskSynthesisLine(overview: BusinessOverview): string |
`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.document_activity_profile) {
const topDocument = overview.document_activity_profile.top_document_types[0];
const topSection = overview.document_activity_profile.top_account_sections[0];
const parts = [
topDocument ? `ведущий тип документов ${topDocument.document_type}` : null,
topSection ? `ведущий раздел учета ${topSection.account_section}` : null
].filter((item): item is string => Boolean(item));
if (parts.length > 0) {
signals.push(`операционный activity mix: ${parts.join(", ")}`);
}
}
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) {
@ -1357,7 +1391,7 @@ function businessOverviewRiskSynthesisLine(overview: BusinessOverview): string |
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(
const hasTaxDebtInventorySignals = Boolean(
overview.tax_position ||
overview.trading_margin_proxy ||
overview.debt_position ||
@ -1367,6 +1401,8 @@ function businessOverviewExecutiveVerdictLine(overview: BusinessOverview): strin
overview.inventory_turnover_proxy ||
overview.inventory_staleness_risk_proxy
);
const hasDocumentActivitySignal = Boolean(overview.document_activity_profile);
const hasExtraSignals = hasTaxDebtInventorySignals || hasDocumentActivitySignal;
if (!hasCash && !hasExtraSignals) {
return null;
}
@ -1376,8 +1412,10 @@ function businessOverviewExecutiveVerdictLine(overview: BusinessOverview): strin
: overview.net_direction === "net_outgoing"
? "операционно исходящий поток сильнее входящего, это зона внимания к расходам/закупкам"
: "операционный поток выглядит сбалансированным";
const evidenceTone = hasExtraSignals
const evidenceTone = hasTaxDebtInventorySignals
? "часть налоговых, долговых или складских контуров уже отдельно проверена"
: hasDocumentActivitySignal
? "операционный activity mix по типам документов и разделам учета уже отдельно проверен"
: "налоги, долги и склад еще не дают проверенного управленческого контекста";
return `Сводный LLM-аудит по подтвержденному: ${cashTone}; ${evidenceTone}. Это полезный управленческий срез по найденным строкам 1С, но не финальный вывод о прибыльности, марже или здоровье компании.`;
}
@ -1460,6 +1498,9 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
if (pilot.derived_business_overview?.yearly_breakdown?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_yearly_operating_breakdown");
}
if (pilot.derived_business_overview?.document_activity_profile) {
pushReason(reasonCodes, "answer_contains_business_overview_document_activity_profile");
}
if (pilot.derived_business_overview?.debt_position) {
pushReason(reasonCodes, "answer_contains_business_overview_debt_position");
}

View File

@ -138,6 +138,28 @@ export interface AssistantMcpDiscoveryBusinessOverviewYearBucket {
net_direction: AssistantMcpDiscoveryNetDirection;
}
export interface AssistantMcpDiscoveryBusinessOverviewDocumentTypeBucket {
document_type: string;
count: number;
share_pct: number | null;
}
export interface AssistantMcpDiscoveryBusinessOverviewAccountSectionBucket {
account_section: string;
operation_count: number;
share_pct: number | null;
}
export interface AssistantMcpDiscoveryDerivedBusinessOverviewDocumentActivityProfile {
period_scope: string | null;
rows_matched: number;
total_document_type_count: number;
total_account_section_operations: number;
top_document_types: AssistantMcpDiscoveryBusinessOverviewDocumentTypeBucket[];
top_account_sections: AssistantMcpDiscoveryBusinessOverviewAccountSectionBucket[];
inference_basis: "document_type_and_account_section_profile_confirmed_1c_rows";
}
export interface AssistantMcpDiscoveryDerivedBidirectionalValueFlow {
counterparty: string | null;
period_scope: string | null;
@ -174,6 +196,7 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverview {
inventory_position: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition | null;
inventory_turnover_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy | null;
inventory_staleness_risk_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy | null;
document_activity_profile: AssistantMcpDiscoveryDerivedBusinessOverviewDocumentActivityProfile | null;
coverage_limited_by_probe_limit: boolean;
checked_signal_count: number;
missing_signal_families: string[];
@ -615,6 +638,18 @@ function buildValueFlowFilters(planner: AssistantMcpDiscoveryPlannerContract): A
};
}
function buildBusinessOverviewDocumentActivityFilters(planner: AssistantMcpDiscoveryPlannerContract): AddressFilterSet {
const meaning = planner.discovery_plan.turn_meaning_ref;
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
return {
...dateScopeToFilters(dateScope),
...(organization ? { organization } : {}),
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
sort: "period_asc"
};
}
function buildBusinessOverviewTaxFilters(planner: AssistantMcpDiscoveryPlannerContract): AddressFilterSet | null {
const meaning = planner.discovery_plan.turn_meaning_ref;
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
@ -2603,6 +2638,85 @@ function deriveBusinessOverviewYearlyBreakdown(input: {
});
}
function normalizeBusinessOverviewActivityMarker(row: Record<string, unknown>): string | null {
const marker = toNonEmptyString(rowDocumentValue(row));
return marker ? marker.trim().toUpperCase() : null;
}
function normalizeBusinessOverviewAccountSection(value: string | null): string | null {
const normalized = String(value ?? "").replace(/\s+/g, "").trim();
if (!normalized) {
return null;
}
const sectionMatch = normalized.match(/^(\d{2})(?:[.\-_/]|$)/);
return sectionMatch?.[1] ?? normalized;
}
function deriveBusinessOverviewDocumentActivityProfile(
result: AddressMcpQueryExecutorResult | null,
periodScope: string | null
): AssistantMcpDiscoveryDerivedBusinessOverviewDocumentActivityProfile | null {
if (!result || result.error || result.matched_rows <= 0) {
return null;
}
const documentTypeBuckets = new Map<string, number>();
const accountSectionBuckets = new Map<string, number>();
for (const row of result.rows) {
const marker = normalizeBusinessOverviewActivityMarker(row);
const count = rowAmountValue(row);
if (!marker || count === null || count <= 0) {
continue;
}
if (marker === "DOC_TYPE_DOCS") {
const documentType = rowDebitAccountValue(row) ?? rowAccountValue(row);
if (documentType) {
documentTypeBuckets.set(documentType, (documentTypeBuckets.get(documentType) ?? 0) + count);
}
continue;
}
if (marker === "SECTION_DT_OPS" || marker === "SECTION_KT_OPS") {
const section = normalizeBusinessOverviewAccountSection(rowDebitAccountValue(row) ?? rowAccountValue(row));
if (section) {
accountSectionBuckets.set(section, (accountSectionBuckets.get(section) ?? 0) + count);
}
}
}
const totalDocumentTypeCount = Array.from(documentTypeBuckets.values()).reduce((sum, count) => sum + count, 0);
const totalAccountSectionOperations = Array.from(accountSectionBuckets.values()).reduce((sum, count) => sum + count, 0);
const topDocumentTypes = Array.from(documentTypeBuckets.entries())
.map(([documentType, count]) => ({
document_type: documentType,
count,
share_pct: percentageOfTotal(count, totalDocumentTypeCount)
}))
.sort((left, right) => right.count - left.count || left.document_type.localeCompare(right.document_type, "ru"))
.slice(0, 5);
const topAccountSections = Array.from(accountSectionBuckets.entries())
.map(([accountSection, operationCount]) => ({
account_section: accountSection,
operation_count: operationCount,
share_pct: percentageOfTotal(operationCount, totalAccountSectionOperations)
}))
.sort((left, right) => right.operation_count - left.operation_count || left.account_section.localeCompare(right.account_section, "ru"))
.slice(0, 5);
if (topDocumentTypes.length <= 0 && topAccountSections.length <= 0) {
return null;
}
return {
period_scope: periodScope,
rows_matched: result.matched_rows,
total_document_type_count: totalDocumentTypeCount,
total_account_section_operations: totalAccountSectionOperations,
top_document_types: topDocumentTypes,
top_account_sections: topAccountSections,
inference_basis: "document_type_and_account_section_profile_confirmed_1c_rows"
};
}
function deriveValueFlow(
result: AssistantMcpDiscoveryCoverageAwareQueryResult | null,
counterparty: string | null,
@ -3521,6 +3635,7 @@ function deriveBusinessOverview(input: {
receivablesResult: AddressMcpQueryExecutorResult | null;
payablesResult: AddressMcpQueryExecutorResult | null;
openContractsResult: AddressMcpQueryExecutorResult | null;
documentActivityProfileResult: AddressMcpQueryExecutorResult | null;
debtAsOfDate: string | null;
inventoryOnHandResult: AddressMcpQueryExecutorResult | null;
inventoryAgingResult: AddressMcpQueryExecutorResult | null;
@ -3558,6 +3673,10 @@ function deriveBusinessOverview(input: {
openContractsResult: input.openContractsResult,
debtAsOfDate: input.debtAsOfDate
});
const documentActivityProfile = deriveBusinessOverviewDocumentActivityProfile(
input.documentActivityProfileResult,
input.periodScope
);
const debtStalenessRiskProxy = deriveBusinessOverviewDebtStalenessRiskProxy(debtOpenSettlementQuality);
const inventoryPosition = deriveBusinessOverviewInventoryPosition({
inventoryOnHandResult: input.inventoryOnHandResult,
@ -3581,6 +3700,7 @@ function deriveBusinessOverview(input: {
Boolean(debtPosition),
Boolean(debtOpenSettlementQuality),
Boolean(debtStalenessRiskProxy),
Boolean(documentActivityProfile),
Boolean(inventoryPosition),
Boolean(inventoryTurnoverProxy),
Boolean(inventoryStalenessRiskProxy)
@ -3610,6 +3730,7 @@ function deriveBusinessOverview(input: {
inventory_position: inventoryPosition,
inventory_turnover_proxy: inventoryTurnoverProxy,
inventory_staleness_risk_proxy: inventoryStalenessRiskProxy,
document_activity_profile: documentActivityProfile,
coverage_limited_by_probe_limit:
incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
checked_signal_count: checkedSignalCount,
@ -3628,7 +3749,9 @@ function deriveBusinessOverview(input: {
inventoryPosition?.aging_signal ? null : "inventory_aging_quality"
].filter((item): item is string => Boolean(item)),
inference_basis:
inventoryPosition
documentActivityProfile
? "business_overview_from_confirmed_1c_multi_family_rows"
: inventoryPosition
? "business_overview_from_confirmed_1c_multi_family_rows"
: debtOpenSettlementQuality
? "business_overview_from_confirmed_1c_multi_family_rows"
@ -3651,6 +3774,7 @@ function summarizeBusinessOverviewRows(input: {
receivablesResult: AddressMcpQueryExecutorResult | null;
payablesResult: AddressMcpQueryExecutorResult | null;
openContractsResult: AddressMcpQueryExecutorResult | null;
documentActivityProfileResult: AddressMcpQueryExecutorResult | null;
inventoryOnHandResult: AddressMcpQueryExecutorResult | null;
inventoryAgingResult: AddressMcpQueryExecutorResult | null;
}): string | null {
@ -3679,6 +3803,9 @@ function summarizeBusinessOverviewRows(input: {
if (input.openContractsResult && !input.openContractsResult.error) {
parts.push(`${input.openContractsResult.fetched_rows} open-contract rows fetched, ${input.openContractsResult.matched_rows} matched`);
}
if (input.documentActivityProfileResult && !input.documentActivityProfileResult.error) {
parts.push(`${input.documentActivityProfileResult.fetched_rows} document/account-section profile rows fetched, ${input.documentActivityProfileResult.matched_rows} matched`);
}
if (input.inventoryOnHandResult && !input.inventoryOnHandResult.error) {
parts.push(`${input.inventoryOnHandResult.fetched_rows} inventory on-hand rows fetched, ${input.inventoryOnHandResult.matched_rows} matched`);
}
@ -3727,6 +3854,25 @@ function buildBusinessOverviewConfirmedFacts(derived: AssistantMcpDiscoveryDeriv
`Подтвержденное окно активности в 1С: ${derived.activity_period.first_activity_date}${derived.activity_period.latest_activity_date}.`
);
}
if (derived.document_activity_profile) {
const profile = derived.document_activity_profile;
const topDocument = profile.top_document_types[0];
const topSection = profile.top_account_sections[0];
const parts: string[] = [];
if (topDocument) {
const shareText = topDocument.share_pct === null ? "" : ` (${topDocument.share_pct}%)`;
parts.push(`ведущий тип документов ${topDocument.document_type}${topDocument.count} документов${shareText}`);
}
if (topSection) {
const shareText = topSection.share_pct === null ? "" : ` (${topSection.share_pct}%)`;
parts.push(`ведущий раздел учета ${topSection.account_section}${topSection.operation_count} операций${shareText}`);
}
if (parts.length > 0) {
facts.push(
`Профиль операционной активности${organization}${period} подтвержден по типам документов и разделам учета 1С: ${parts.join("; ")}. Это activity mix, а не аудит качества учета или полноты бизнес-процессов.`
);
}
}
if (derived.tax_position) {
const taxDirection =
derived.tax_position.net_vat_direction === "vat_to_pay"
@ -4641,10 +4787,12 @@ export async function executeAssistantMcpDiscoveryPilot(
let receivablesResult: AddressMcpQueryExecutorResult | null = null;
let payablesResult: AddressMcpQueryExecutorResult | null = null;
let openContractsResult: AddressMcpQueryExecutorResult | null = null;
let documentActivityProfileResult: AddressMcpQueryExecutorResult | null = null;
let inventoryOnHandResult: AddressMcpQueryExecutorResult | null = null;
let inventoryAgingResult: AddressMcpQueryExecutorResult | null = null;
const valueFilters = buildValueFlowFilters(planner);
const lifecycleFilters = buildLifecycleFilters(planner);
const documentActivityFilters = buildBusinessOverviewDocumentActivityFilters(planner);
const taxFilters = buildBusinessOverviewTaxFilters(planner);
const tradingMarginFilters = buildBusinessOverviewTradingMarginFilters(planner);
const debtFilters = buildBusinessOverviewDebtFilters(planner);
@ -4654,6 +4802,7 @@ export async function executeAssistantMcpDiscoveryPilot(
const incomingSelection = selectAddressRecipe("customer_revenue_and_payments", valueFilters);
const outgoingSelection = selectAddressRecipe("supplier_payouts_profile", valueFilters);
const lifecycleSelection = selectAddressRecipe("counterparty_activity_lifecycle", lifecycleFilters);
const documentActivitySelection = selectAddressRecipe("document_type_and_account_section_profile", documentActivityFilters);
const taxSelection = taxFilters
? selectAddressRecipe("vat_liability_confirmed_for_tax_period", taxFilters)
: null;
@ -4707,6 +4856,12 @@ export async function executeAssistantMcpDiscoveryPilot(
}
pushReason(reasonCodes, "pilot_business_overview_recipes_selected");
if (documentActivitySelection.selected_recipe) {
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_recipe_selected");
} else {
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_recipe_not_available");
pushUnique(queryLimitations, "Business overview document/account-section profile requires an executable document-section profile recipe");
}
if (taxSelection?.selected_recipe) {
pushReason(reasonCodes, "pilot_business_overview_tax_recipe_selected");
} else if (!taxFilters) {
@ -4919,6 +5074,18 @@ export async function executeAssistantMcpDiscoveryPilot(
});
probeResults.push(queryResultToProbeResult(step.primitive_id, tradingMarginResult));
}
if (documentActivitySelection.selected_recipe) {
const documentActivityPlan = buildAddressRecipePlan(
documentActivitySelection.selected_recipe,
documentActivityFilters
);
documentActivityProfileResult = await runtimeDeps.executeAddressMcpQuery({
query: documentActivityPlan.query,
limit: documentActivityPlan.limit,
account_scope: documentActivityPlan.account_scope
});
probeResults.push(queryResultToProbeResult(step.primitive_id, documentActivityProfileResult));
}
if (lifecycleResult.error) {
pushUnique(queryLimitations, lifecycleResult.error);
pushReason(reasonCodes, "pilot_business_overview_query_documents_mcp_error");
@ -4931,6 +5098,12 @@ export async function executeAssistantMcpDiscoveryPilot(
} else if (tradingMarginResult) {
pushReason(reasonCodes, "pilot_business_overview_trading_margin_query_mcp_executed");
}
if (documentActivityProfileResult?.error) {
pushUnique(queryLimitations, documentActivityProfileResult.error);
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_query_mcp_error");
} else if (documentActivityProfileResult) {
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_query_mcp_executed");
}
continue;
}
@ -4947,6 +5120,7 @@ export async function executeAssistantMcpDiscoveryPilot(
receivablesResult,
payablesResult,
openContractsResult,
documentActivityProfileResult,
debtAsOfDate,
inventoryOnHandResult,
inventoryAgingResult,
@ -4968,6 +5142,9 @@ export async function executeAssistantMcpDiscoveryPilot(
if (derivedBusinessOverview.activity_period) {
pushReason(reasonCodes, "pilot_derived_business_overview_activity_window_from_confirmed_rows");
}
if (derivedBusinessOverview.document_activity_profile) {
pushReason(reasonCodes, "pilot_derived_business_overview_document_activity_profile_from_confirmed_rows");
}
if (derivedBusinessOverview.tax_position) {
pushReason(reasonCodes, "pilot_derived_business_overview_tax_position_from_confirmed_rows");
}
@ -5005,6 +5182,7 @@ export async function executeAssistantMcpDiscoveryPilot(
receivablesResult,
payablesResult,
openContractsResult,
documentActivityProfileResult,
inventoryOnHandResult,
inventoryAgingResult
});

View File

@ -233,6 +233,14 @@ describe("assistant MCP discovery answer adapter", () => {
{ Период: "2020-01-15T00:00:00", Регистратор: "Поступление 1" },
{ Период: "2020-12-15T00:00:00", Регистратор: "Поступление 2" }
]
},
{
rows: [
{ Period: "2020-01-01T00:00:00", Registrator: "DOC_TYPE_DOCS", AccountDt: "Списание с расчетного счета", Amount: 12 },
{ Period: "2020-01-01T00:00:00", Registrator: "DOC_TYPE_DOCS", AccountDt: "Поступление на расчетный счет", Amount: 8 },
{ Period: "2020-01-01T00:00:00", Registrator: "SECTION_DT_OPS", AccountDt: "60", Amount: 9 },
{ Period: "2020-01-01T00:00:00", Registrator: "SECTION_KT_OPS", AccountDt: "62", Amount: 6 }
]
}
])
);
@ -245,6 +253,9 @@ describe("assistant MCP discovery answer adapter", () => {
expect(draft.confirmed_lines.join("\n")).toContain("Самый крупный подтвержденный клиент");
expect(draft.confirmed_lines.join("\n")).toContain("Самый крупный подтвержденный поставщик");
expect(draft.confirmed_lines.join("\n")).toContain("Годовая раскладка операционного денежного потока");
expect(draft.confirmed_lines.join("\n")).toContain("Профиль операционной активности");
expect(draft.confirmed_lines.join("\n")).toContain("ведущий тип документов Списание с расчетного счета");
expect(draft.confirmed_lines.join("\n")).toContain("ведущий раздел учета 60");
expect(draft.inference_lines.join("\n")).toContain("Аналитический вывод по оборотам");
expect(draft.inference_lines.join("\n")).toContain("Концентрация входящего потока");
expect(draft.inference_lines.join("\n")).toContain("Концентрация исходящего потока");
@ -258,9 +269,11 @@ describe("assistant MCP discovery answer adapter", () => {
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 yearly operating-flow breakdown as profit, financial result, or a complete annual P&L.");
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.must_not_claim).toContain("Do not present business overview document/account-section activity profile as process quality, accounting correctness, or completeness of all 1C activity.");
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_yearly_operating_breakdown");
expect(draft.reason_codes).toContain("answer_contains_business_overview_document_activity_profile");
expect(draft.reason_codes).toContain("answer_contains_business_overview_analyst_synthesis");
});

View File

@ -156,8 +156,10 @@ describe("assistant MCP discovery pilot executor", () => {
},
{
rows: [
{ Period: "2020-03-01T00:00:00", Amount: 200000, Item: "Товар А", Counterparty: "Клиент А", СчетКт: "41.01" },
{ Period: "2020-02-01T00:00:00", Amount: 120000, Item: "Товар А", Counterparty: "Поставщик А", СчетДт: "41.01" }
{ Period: "2020-01-01T00:00:00", Registrator: "DOC_TYPE_DOCS", AccountDt: "Списание с расчетного счета", Amount: 12 },
{ Period: "2020-01-01T00:00:00", Registrator: "DOC_TYPE_DOCS", AccountDt: "Поступление на расчетный счет", Amount: 8 },
{ Period: "2020-01-01T00:00:00", Registrator: "SECTION_DT_OPS", AccountDt: "60", Amount: 9 },
{ Period: "2020-01-01T00:00:00", Registrator: "SECTION_KT_OPS", AccountDt: "62", Amount: 6 }
]
}
]);
@ -210,9 +212,25 @@ describe("assistant MCP discovery pilot executor", () => {
}
]);
expect(result.derived_business_overview?.activity_period?.duration_total_months).toBe(11);
expect(result.derived_business_overview?.document_activity_profile).toMatchObject({
rows_matched: 4,
total_document_type_count: 20,
total_account_section_operations: 15
});
expect(result.derived_business_overview?.document_activity_profile?.top_document_types[0]).toMatchObject({
document_type: "Списание с расчетного счета",
count: 12,
share_pct: 60
});
expect(result.derived_business_overview?.document_activity_profile?.top_account_sections[0]).toMatchObject({
account_section: "60",
operation_count: 9,
share_pct: 60
});
expect(result.evidence.confirmed_facts.join("\n")).toContain("В 1С подтверждены входящие поступления");
expect(result.evidence.confirmed_facts.join("\n")).toContain("Самый крупный подтвержденный поставщик");
expect(result.evidence.confirmed_facts.join("\n")).toContain("Годовая раскладка операционного денежного потока");
expect(result.evidence.confirmed_facts.join("\n")).toContain("Профиль операционной активности");
expect(result.evidence.inferred_facts.join("\n")).toContain("procurement concentration proxy");
expect(result.evidence.inferred_facts.join("\n")).toContain("Самый сильный год по подтвержденным входящим поступлениям: 2021");
expect(result.evidence.unknown_facts).toContain(
@ -221,7 +239,8 @@ describe("assistant MCP discovery pilot executor", () => {
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(result.reason_codes).toContain("pilot_derived_business_overview_yearly_operating_breakdown_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(3);
expect(result.reason_codes).toContain("pilot_derived_business_overview_document_activity_profile_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(4);
});
it("adds a checked VAT/tax family to business overview only when an explicit period is available", async () => {
@ -324,7 +343,7 @@ describe("assistant MCP discovery pilot executor", () => {
expect(result.reason_codes).toContain("pilot_derived_business_overview_tax_position_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_business_overview_trading_margin_query_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_business_overview_trading_margin_proxy_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(10);
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(11);
const taxCall = deps.executeAddressMcpQuery.mock.calls[2]?.[0];
const tradingMarginCall = deps.executeAddressMcpQuery.mock.calls[9]?.[0];
expect(String(taxCall?.query ?? "")).toContain("НДСЗаписиКнигиПродаж");
@ -524,7 +543,7 @@ describe("assistant MCP discovery pilot executor", () => {
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);
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(11);
const receivablesCall = deps.executeAddressMcpQuery.mock.calls[3]?.[0];
const payablesCall = deps.executeAddressMcpQuery.mock.calls[4]?.[0];
const openContractsCall = deps.executeAddressMcpQuery.mock.calls[5]?.[0];
@ -651,7 +670,7 @@ describe("assistant MCP discovery pilot executor", () => {
expect(result.reason_codes).toContain("pilot_derived_business_overview_inventory_position_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_derived_business_overview_inventory_turnover_proxy_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_derived_business_overview_inventory_staleness_risk_proxy_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(10);
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(11);
const inventoryCall = deps.executeAddressMcpQuery.mock.calls[6]?.[0];
expect(inventoryCall?.account_scope).toContain("41.01");
});