ARCH: восстановить годовое покрытие MCP discovery помесячными пробами

This commit is contained in:
2026-04-21 08:41:35 +03:00
parent 4baa54fe81
commit cd3315e06d
15 changed files with 834 additions and 94 deletions
@@ -168,6 +168,149 @@ function queryResultToProbeResult(primitiveId, result) {
limitation: result.error
};
}
function toCoverageAwareQueryResult(result, options = {}) {
if (!result) {
return null;
}
return {
...result,
coverage_limited_by_probe_limit: options.coverageLimitedByProbeLimit ?? false,
coverage_recovered_by_period_chunking: options.coverageRecoveredByPeriodChunking ?? false,
period_chunking_granularity: options.periodChunkingGranularity ?? null,
period_chunk_count: options.periodChunkCount ?? 0
};
}
function monthWindowsForYear(year) {
const result = [];
for (let month = 0; month < 12; month += 1) {
const start = new Date(Date.UTC(Number(year), month, 1));
const end = new Date(Date.UTC(Number(year), month + 1, 0));
result.push({
period_from: `${start.getUTCFullYear()}-${String(start.getUTCMonth() + 1).padStart(2, "0")}-${String(start.getUTCDate()).padStart(2, "0")}`,
period_to: `${end.getUTCFullYear()}-${String(end.getUTCMonth() + 1).padStart(2, "0")}-${String(end.getUTCDate()).padStart(2, "0")}`
});
}
return result;
}
function periodWindowsForDateScope(dateScope) {
const yearMatch = dateScope?.match(/^(\d{4})$/);
if (yearMatch) {
return monthWindowsForYear(yearMatch[1]);
}
return [];
}
function mergeCoverageAwareQueryResults(results, options) {
const rawRows = results.flatMap((item) => item.raw_rows);
const rows = results.flatMap((item) => item.rows);
const errors = results.map((item) => toNonEmptyString(item.error)).filter((item) => Boolean(item));
return {
fetched_rows: results.reduce((sum, item) => sum + item.fetched_rows, 0),
matched_rows: results.reduce((sum, item) => sum + item.matched_rows, 0),
raw_rows: rawRows,
rows,
error: errors[0] ?? null,
coverage_limited_by_probe_limit: options.coverageLimitedByProbeLimit,
coverage_recovered_by_period_chunking: options.coverageRecoveredByPeriodChunking,
period_chunking_granularity: options.periodChunkingGranularity,
period_chunk_count: options.periodChunkCount
};
}
async function executeCoverageAwareValueFlowQuery(input) {
const queryLimitations = [];
const probeResults = [];
let executedProbeCount = 0;
const broadRecipePlan = input.recipePlanBuilder(input.baseFilters);
const broadResult = await input.deps.executeAddressMcpQuery({
query: broadRecipePlan.query,
limit: broadRecipePlan.limit,
account_scope: broadRecipePlan.account_scope
});
executedProbeCount += 1;
probeResults.push(queryResultToProbeResult(input.primitiveId, broadResult));
const broadCoverageLimited = !broadResult.error && broadResult.matched_rows >= input.maxRowsPerProbe;
if (broadResult.error) {
pushUnique(queryLimitations, broadResult.error);
return {
result: toCoverageAwareQueryResult(broadResult, {
coverageLimitedByProbeLimit: false
}),
probe_results: probeResults,
query_limitations: queryLimitations,
executed_probe_count: executedProbeCount
};
}
const periodWindows = periodWindowsForDateScope(input.dateScope);
if (!broadCoverageLimited || periodWindows.length === 0) {
return {
result: toCoverageAwareQueryResult(broadResult, {
coverageLimitedByProbeLimit: broadCoverageLimited
}),
probe_results: probeResults,
query_limitations: queryLimitations,
executed_probe_count: executedProbeCount
};
}
const requiredChunkProbeCount = periodWindows.length;
if (executedProbeCount + requiredChunkProbeCount > input.maxProbeCount) {
pushUnique(queryLimitations, "Requested period hit the MCP row limit, but the approved monthly recovery probe budget is smaller than the required subperiod count");
return {
result: toCoverageAwareQueryResult(broadResult, {
coverageLimitedByProbeLimit: true
}),
probe_results: probeResults,
query_limitations: queryLimitations,
executed_probe_count: executedProbeCount
};
}
const chunkResults = [];
let anyChunkLimited = false;
let anyChunkError = false;
for (const window of periodWindows) {
const chunkFilters = {
...input.baseFilters,
period_from: window.period_from,
period_to: window.period_to
};
const chunkPlan = input.recipePlanBuilder(chunkFilters);
const chunkResult = await input.deps.executeAddressMcpQuery({
query: chunkPlan.query,
limit: chunkPlan.limit,
account_scope: chunkPlan.account_scope
});
executedProbeCount += 1;
probeResults.push(queryResultToProbeResult(input.primitiveId, chunkResult));
if (chunkResult.error) {
anyChunkError = true;
pushUnique(queryLimitations, chunkResult.error);
continue;
}
if (chunkResult.matched_rows >= input.maxRowsPerProbe) {
anyChunkLimited = true;
}
chunkResults.push(chunkResult);
}
if (chunkResults.length === 0 && anyChunkError) {
return {
result: toCoverageAwareQueryResult(broadResult, {
coverageLimitedByProbeLimit: true
}),
probe_results: probeResults,
query_limitations: queryLimitations,
executed_probe_count: executedProbeCount
};
}
return {
result: mergeCoverageAwareQueryResults(chunkResults, {
coverageLimitedByProbeLimit: anyChunkLimited || anyChunkError,
coverageRecoveredByPeriodChunking: true,
periodChunkingGranularity: "month",
periodChunkCount: periodWindows.length
}),
probe_results: probeResults,
query_limitations: queryLimitations,
executed_probe_count: executedProbeCount
};
}
function summarizeLifecycleRows(result) {
if (result.error) {
return null;
@@ -184,6 +327,9 @@ function summarizeValueFlowRows(result) {
if (result.fetched_rows <= 0) {
return "0 MCP value-flow rows fetched";
}
if (result.coverage_recovered_by_period_chunking && result.period_chunking_granularity === "month") {
return `${result.period_chunk_count} monthly MCP value-flow probes fetched ${result.fetched_rows} rows total, ${result.matched_rows} matched value-flow scope after the broad probe hit the row limit`;
}
return `${result.fetched_rows} MCP value-flow rows fetched, ${result.matched_rows} matched value-flow scope`;
}
function rowDateValue(row) {
@@ -370,7 +516,7 @@ function deriveBidirectionalValueFlowMonthBreakdown(input) {
};
});
}
function deriveValueFlow(result, counterparty, periodScope, direction, probeLimit, aggregationAxis) {
function deriveValueFlow(result, counterparty, periodScope, direction, aggregationAxis) {
if (!result || result.error || result.matched_rows <= 0) {
return null;
}
@@ -401,12 +547,14 @@ function deriveValueFlow(result, counterparty, periodScope, direction, probeLimi
total_amount_human_ru: formatAmountHumanRu(totalAmount),
first_movement_date: dates[0] ?? null,
latest_movement_date: dates[dates.length - 1] ?? null,
coverage_limited_by_probe_limit: result.matched_rows >= probeLimit,
coverage_limited_by_probe_limit: result.coverage_limited_by_probe_limit,
coverage_recovered_by_period_chunking: result.coverage_recovered_by_period_chunking,
period_chunking_granularity: result.period_chunking_granularity,
monthly_breakdown: deriveValueFlowMonthBreakdown(result, aggregationAxis),
inference_basis: "sum_of_confirmed_1c_value_flow_rows"
};
}
function deriveValueFlowSideSummary(result, probeLimit) {
function deriveValueFlowSideSummary(result) {
if (!result || result.error || result.matched_rows <= 0) {
return {
rows_matched: 0,
@@ -415,7 +563,9 @@ function deriveValueFlowSideSummary(result, probeLimit) {
total_amount_human_ru: formatAmountHumanRu(0),
first_movement_date: null,
latest_movement_date: null,
coverage_limited_by_probe_limit: false
coverage_limited_by_probe_limit: false,
coverage_recovered_by_period_chunking: false,
period_chunking_granularity: null
};
}
let totalAmount = 0;
@@ -438,12 +588,14 @@ function deriveValueFlowSideSummary(result, probeLimit) {
total_amount_human_ru: formatAmountHumanRu(totalAmount),
first_movement_date: dates[0] ?? null,
latest_movement_date: dates[dates.length - 1] ?? null,
coverage_limited_by_probe_limit: result.matched_rows >= probeLimit
coverage_limited_by_probe_limit: result.coverage_limited_by_probe_limit,
coverage_recovered_by_period_chunking: result.coverage_recovered_by_period_chunking,
period_chunking_granularity: result.period_chunking_granularity
};
}
function deriveBidirectionalValueFlow(input) {
const incoming = deriveValueFlowSideSummary(input.incomingResult, input.probeLimit);
const outgoing = deriveValueFlowSideSummary(input.outgoingResult, input.probeLimit);
const incoming = deriveValueFlowSideSummary(input.incomingResult);
const outgoing = deriveValueFlowSideSummary(input.outgoingResult);
if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) {
return null;
}
@@ -458,6 +610,8 @@ function deriveBidirectionalValueFlow(input) {
net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)),
net_direction: netDirectionFromAmount(netAmount),
coverage_limited_by_probe_limit: incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
coverage_recovered_by_period_chunking: incoming.coverage_recovered_by_period_chunking || outgoing.coverage_recovered_by_period_chunking,
period_chunking_granularity: incoming.period_chunking_granularity ?? outgoing.period_chunking_granularity ?? null,
monthly_breakdown: deriveBidirectionalValueFlowMonthBreakdown({
incomingResult: input.incomingResult,
outgoingResult: input.outgoingResult,
@@ -474,10 +628,14 @@ function summarizeBidirectionalValueFlowRows(input) {
}
const incomingSummary = incoming?.error
? "incoming value-flow query failed"
: `${incoming?.fetched_rows ?? 0} incoming value-flow rows fetched, ${incoming?.matched_rows ?? 0} matched`;
: incoming?.coverage_recovered_by_period_chunking && incoming.period_chunking_granularity === "month"
? `${incoming.period_chunk_count} monthly incoming value-flow probes fetched ${incoming.fetched_rows} rows total, ${incoming.matched_rows} matched`
: `${incoming?.fetched_rows ?? 0} incoming value-flow rows fetched, ${incoming?.matched_rows ?? 0} matched`;
const outgoingSummary = outgoing?.error
? "outgoing supplier-payout query failed"
: `${outgoing?.fetched_rows ?? 0} outgoing supplier-payout rows fetched, ${outgoing?.matched_rows ?? 0} matched`;
: outgoing?.coverage_recovered_by_period_chunking && outgoing.period_chunking_granularity === "month"
? `${outgoing.period_chunk_count} monthly outgoing supplier-payout probes fetched ${outgoing.fetched_rows} rows total, ${outgoing.matched_rows} matched`
: `${outgoing?.fetched_rows ?? 0} outgoing supplier-payout rows fetched, ${outgoing?.matched_rows ?? 0} matched`;
return `${incomingSummary}; ${outgoingSummary}`;
}
function buildLifecycleConfirmedFacts(result, counterparty) {
@@ -539,6 +697,9 @@ function buildValueFlowInferredFacts(derived) {
else {
facts.push("Counterparty value-flow total was calculated from confirmed 1C movement rows");
}
if (derived.coverage_recovered_by_period_chunking && derived.period_chunking_granularity === "month") {
facts.push("Requested period coverage was recovered through monthly 1C value-flow probes after the broad probe hit the row limit");
}
if (derived.aggregation_axis === "month" && derived.monthly_breakdown.length > 0) {
facts.push("Counterparty monthly value-flow breakdown was grouped by month over confirmed 1C movement rows");
}
@@ -549,6 +710,9 @@ function buildBidirectionalValueFlowInferredFacts(derived) {
return [];
}
const facts = ["Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"];
if (derived.coverage_recovered_by_period_chunking && derived.period_chunking_granularity === "month") {
facts.push("Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes after a broad probe hit the row limit");
}
if (derived.aggregation_axis === "month" && derived.monthly_breakdown.length > 0) {
facts.push("Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows");
}
@@ -706,36 +870,50 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
};
}
pushReason(reasonCodes, "pilot_bidirectional_value_flow_recipes_selected");
const incomingRecipePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(incomingSelection.selected_recipe, filters);
const outgoingRecipePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(outgoingSelection.selected_recipe, filters);
for (const step of dryRun.execution_steps) {
if (step.primitive_id !== "query_movements") {
skippedPrimitives.push(step.primitive_id);
probeResults.push(skippedProbeResult(step, "pilot_bidirectional_value_flow_uses_two_query_movements_and_derives_net"));
continue;
}
incomingResult = await deps.executeAddressMcpQuery({
query: incomingRecipePlan.query,
limit: incomingRecipePlan.limit,
account_scope: incomingRecipePlan.account_scope
const incomingExecution = await executeCoverageAwareValueFlowQuery({
primitiveId: step.primitive_id,
recipePlanBuilder: (scopedFilters) => (0, addressRecipeCatalog_1.buildAddressRecipePlan)(incomingSelection.selected_recipe, scopedFilters),
baseFilters: filters,
dateScope,
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
deps
});
outgoingResult = await deps.executeAddressMcpQuery({
query: outgoingRecipePlan.query,
limit: outgoingRecipePlan.limit,
account_scope: outgoingRecipePlan.account_scope
const outgoingExecution = await executeCoverageAwareValueFlowQuery({
primitiveId: step.primitive_id,
recipePlanBuilder: (scopedFilters) => (0, addressRecipeCatalog_1.buildAddressRecipePlan)(outgoingSelection.selected_recipe, scopedFilters),
baseFilters: filters,
dateScope,
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
deps
});
incomingResult = incomingExecution.result;
outgoingResult = outgoingExecution.result;
pushUnique(executedPrimitives, step.primitive_id);
probeResults.push(queryResultToProbeResult(step.primitive_id, incomingResult));
probeResults.push(queryResultToProbeResult(step.primitive_id, outgoingResult));
if (incomingResult.error) {
pushUnique(queryLimitations, incomingResult.error);
probeResults.push(...incomingExecution.probe_results, ...outgoingExecution.probe_results);
for (const limitation of [...incomingExecution.query_limitations, ...outgoingExecution.query_limitations]) {
pushUnique(queryLimitations, limitation);
}
if (incomingResult?.error) {
pushReason(reasonCodes, "pilot_bidirectional_incoming_query_movements_mcp_error");
}
if (outgoingResult.error) {
pushUnique(queryLimitations, outgoingResult.error);
if (outgoingResult?.error) {
pushReason(reasonCodes, "pilot_bidirectional_outgoing_query_movements_mcp_error");
}
if (!incomingResult.error || !outgoingResult.error) {
if (incomingResult?.coverage_recovered_by_period_chunking) {
pushReason(reasonCodes, "pilot_bidirectional_incoming_monthly_period_chunking_recovered_coverage");
}
if (outgoingResult?.coverage_recovered_by_period_chunking) {
pushReason(reasonCodes, "pilot_bidirectional_outgoing_monthly_period_chunking_recovered_coverage");
}
if (!incomingResult?.error || !outgoingResult?.error) {
pushReason(reasonCodes, "pilot_bidirectional_query_movements_mcp_executed");
}
}
@@ -745,7 +923,6 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
outgoingResult,
counterparty,
periodScope: dateScope,
probeLimit: planner.discovery_plan.execution_budget.max_rows_per_probe,
aggregationAxis
});
if (derivedBidirectionalValueFlow) {
@@ -810,30 +987,39 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
pushReason(reasonCodes, valueFlowProfile.direction === "outgoing_supplier_payout"
? "pilot_supplier_payout_recipe_selected"
: "pilot_customer_revenue_recipe_selected");
const recipePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(selection.selected_recipe, filters);
for (const step of dryRun.execution_steps) {
if (step.primitive_id !== "query_movements") {
skippedPrimitives.push(step.primitive_id);
probeResults.push(skippedProbeResult(step, "pilot_value_flow_uses_query_movements_and_derives_aggregate"));
continue;
}
queryResult = await deps.executeAddressMcpQuery({
query: recipePlan.query,
limit: recipePlan.limit,
account_scope: recipePlan.account_scope
const execution = await executeCoverageAwareValueFlowQuery({
primitiveId: step.primitive_id,
recipePlanBuilder: (scopedFilters) => (0, addressRecipeCatalog_1.buildAddressRecipePlan)(selection.selected_recipe, scopedFilters),
baseFilters: filters,
dateScope,
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
deps
});
executedPrimitives.push(step.primitive_id);
probeResults.push(queryResultToProbeResult(step.primitive_id, queryResult));
if (queryResult.error) {
pushUnique(queryLimitations, queryResult.error);
queryResult = execution.result;
pushUnique(executedPrimitives, step.primitive_id);
probeResults.push(...execution.probe_results);
for (const limitation of execution.query_limitations) {
pushUnique(queryLimitations, limitation);
}
if (queryResult?.error) {
pushReason(reasonCodes, "pilot_query_movements_mcp_error");
}
else {
pushReason(reasonCodes, "pilot_query_movements_mcp_executed");
}
if (queryResult?.coverage_recovered_by_period_chunking) {
pushReason(reasonCodes, "pilot_monthly_period_chunking_recovered_coverage");
}
}
const sourceRowsSummary = queryResult ? summarizeValueFlowRows(queryResult) : null;
const derivedValueFlow = deriveValueFlow(queryResult, counterparty, dateScope, valueFlowProfile.direction, planner.discovery_plan.execution_budget.max_rows_per_probe, aggregationAxis);
const derivedValueFlow = deriveValueFlow(queryResult, counterparty, dateScope, valueFlowProfile.direction, aggregationAxis);
if (derivedValueFlow) {
pushReason(reasonCodes, "pilot_derived_value_flow_from_confirmed_rows");
if (aggregationAxis === "month" && derivedValueFlow.monthly_breakdown.length > 0) {
@@ -55,6 +55,24 @@ function addScopeAxes(axes, meaning) {
function includesAny(text, tokens) {
return tokens.some((token) => text.includes(token));
}
function isYearDateScope(meaning) {
return /^\d{4}$/.test(toNonEmptyString(meaning?.explicit_date_scope) ?? "");
}
function budgetOverrideFor(input, recipe) {
const meaning = input.turnMeaning ?? null;
const requestedAggregationAxis = aggregationAxis(meaning);
const isValueFlowRecipe = recipe.semanticDataNeed === "counterparty value-flow evidence" &&
recipe.primitives.includes("query_movements");
if (!isValueFlowRecipe) {
return {};
}
if (requestedAggregationAxis === "month" || isYearDateScope(meaning)) {
return {
maxProbeCount: 30
};
}
return {};
}
function recipeFor(input) {
const meaning = input.turnMeaning ?? null;
const domain = lower(meaning?.asked_domain_family);
@@ -136,14 +154,19 @@ function statusFrom(plan, review) {
}
function planAssistantMcpDiscovery(input) {
const recipe = recipeFor(input);
const budgetOverride = budgetOverrideFor(input, recipe);
const semanticDataNeed = toNonEmptyString(input.semanticDataNeed) ?? recipe.semanticDataNeed;
const reasonCodes = [];
pushReason(reasonCodes, recipe.reason);
if (budgetOverride.maxProbeCount) {
pushReason(reasonCodes, "planner_enabled_chunked_coverage_probe_budget");
}
const plan = (0, assistantMcpDiscoveryPolicy_1.buildAssistantMcpDiscoveryPlan)({
semanticDataNeed,
turnMeaning: input.turnMeaning,
proposedPrimitives: recipe.primitives,
requiredAxes: recipe.axes
requiredAxes: recipe.axes,
maxProbeCount: budgetOverride.maxProbeCount
});
const review = (0, assistantMcpCatalogIndex_1.reviewAssistantMcpDiscoveryPlanAgainstCatalog)(plan);
const plannerStatus = statusFrom(plan, review);
@@ -21,7 +21,7 @@ const DEFAULT_DISCOVERY_BUDGET = {
max_probe_count: 3,
max_rows_per_probe: 100
};
const MAX_PROBE_COUNT = 6;
const MAX_PROBE_COUNT = 36;
const MAX_ROWS_PER_PROBE = 500;
const ALLOWED_PRIMITIVE_SET = new Set(exports.ASSISTANT_MCP_DISCOVERY_PRIMITIVES);
function toNonEmptyString(value) {
@@ -139,6 +139,12 @@ function localizeLine(value) {
if (/^Full all-time bidirectional value-flow is not proven without an explicit checked period$/i.test(value)) {
return "Полный двусторонний денежный поток за все время без явно проверенного периода не подтвержден.";
}
if (/^Requested period coverage was recovered through monthly 1C value-flow probes after the broad probe hit the row limit$/i.test(value)) {
return "Покрытие запрошенного периода восстановлено помесячными проверками 1С после того, как общая выборка уперлась в лимит строк.";
}
if (/^Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes after a broad probe hit the row limit$/i.test(value)) {
return "Покрытие запрошенного периода по двустороннему денежному потоку восстановлено помесячными проверками 1С после того, как общая выборка уперлась в лимит строк хотя бы по одной стороне.";
}
return value;
}
function section(title, lines) {