import { executeAddressMcpQuery, type AddressMcpMetadataRowsResult } from "./addressMcpClient"; import { buildAssistantMcpDiscoveryRuntimeDryRun, type AssistantMcpDiscoveryRuntimeDryRunContract, type AssistantMcpDiscoveryRuntimeStepContract } from "./assistantMcpDiscoveryRuntimeAdapter"; import type { AssistantMcpDiscoveryPlannerContract } from "./assistantMcpDiscoveryPlanner"; import { resolveAssistantMcpDiscoveryEvidence, type AssistantMcpDiscoveryEvidenceContract, type AssistantMcpDiscoveryProbeResult } from "./assistantMcpDiscoveryPolicy"; import { buildAddressRecipePlan, selectAddressRecipe } from "./addressRecipeCatalog"; import type { AddressFilterSet, AddressIntent } from "../types/addressQuery"; export const ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION = "assistant_mcp_discovery_pilot_executor_v1" as const; export type AssistantMcpDiscoveryPilotStatus = | "executed" | "skipped_needs_clarification" | "blocked" | "unsupported"; export interface AssistantMcpDiscoveryPilotExecutorDeps { executeAddressMcpQuery: typeof executeAddressMcpQuery; } export interface AssistantMcpDiscoveryDerivedActivityPeriod { first_activity_date: string; latest_activity_date: string; matched_rows: number; duration_total_months: number; duration_years: number; duration_months_remainder: number; duration_human_ru: string; inference_basis: "first_and_latest_confirmed_1c_activity_rows"; } export type AssistantMcpDiscoveryAggregationAxis = "month"; export type AssistantMcpDiscoveryNetDirection = "net_incoming" | "net_outgoing" | "balanced"; export interface AssistantMcpDiscoveryValueFlowMonthBucket { month_bucket: string; rows_with_amount: number; total_amount: number; total_amount_human_ru: string; } export interface AssistantMcpDiscoveryDerivedValueFlow { value_flow_direction: "incoming_customer_revenue" | "outgoing_supplier_payout"; counterparty: string | null; period_scope: string | null; aggregation_axis: AssistantMcpDiscoveryAggregationAxis | null; rows_matched: number; rows_with_amount: number; total_amount: number; total_amount_human_ru: string; first_movement_date: string | null; latest_movement_date: string | null; coverage_limited_by_probe_limit: boolean; monthly_breakdown: AssistantMcpDiscoveryValueFlowMonthBucket[]; inference_basis: "sum_of_confirmed_1c_value_flow_rows"; } export interface AssistantMcpDiscoveryValueFlowSideSummary { rows_matched: number; rows_with_amount: number; total_amount: number; total_amount_human_ru: string; first_movement_date: string | null; latest_movement_date: string | null; coverage_limited_by_probe_limit: boolean; } export interface AssistantMcpDiscoveryBidirectionalValueFlowMonthBucket { month_bucket: string; incoming_total_amount: number; incoming_total_amount_human_ru: string; incoming_rows_with_amount: number; outgoing_total_amount: number; outgoing_total_amount_human_ru: string; outgoing_rows_with_amount: number; net_amount: number; net_amount_human_ru: string; net_direction: AssistantMcpDiscoveryNetDirection; } export interface AssistantMcpDiscoveryDerivedBidirectionalValueFlow { counterparty: string | null; period_scope: string | null; aggregation_axis: AssistantMcpDiscoveryAggregationAxis | null; incoming_customer_revenue: AssistantMcpDiscoveryValueFlowSideSummary; outgoing_supplier_payout: AssistantMcpDiscoveryValueFlowSideSummary; net_amount: number; net_amount_human_ru: string; net_direction: AssistantMcpDiscoveryNetDirection; coverage_limited_by_probe_limit: boolean; monthly_breakdown: AssistantMcpDiscoveryBidirectionalValueFlowMonthBucket[]; inference_basis: "incoming_minus_outgoing_confirmed_1c_value_flow_rows"; } export type AssistantMcpDiscoveryPilotScope = | "counterparty_lifecycle_query_documents_v1" | "counterparty_value_flow_query_movements_v1" | "counterparty_supplier_payout_query_movements_v1" | "counterparty_bidirectional_value_flow_query_movements_v1"; export interface AssistantMcpDiscoveryPilotExecutionContract { schema_version: typeof ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION; policy_owner: "assistantMcpDiscoveryPilotExecutor"; pilot_status: AssistantMcpDiscoveryPilotStatus; pilot_scope: AssistantMcpDiscoveryPilotScope; dry_run: AssistantMcpDiscoveryRuntimeDryRunContract; mcp_execution_performed: boolean; executed_primitives: string[]; skipped_primitives: string[]; probe_results: AssistantMcpDiscoveryProbeResult[]; evidence: AssistantMcpDiscoveryEvidenceContract; source_rows_summary: string | null; derived_activity_period: AssistantMcpDiscoveryDerivedActivityPeriod | null; derived_value_flow: AssistantMcpDiscoveryDerivedValueFlow | null; derived_bidirectional_value_flow: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null; query_limitations: string[]; reason_codes: string[]; } type AddressMcpQueryExecutorResult = Awaited>; const DEFAULT_DEPS: AssistantMcpDiscoveryPilotExecutorDeps = { executeAddressMcpQuery }; function toNonEmptyString(value: unknown): string | null { if (value === null || value === undefined) { return null; } const text = String(value).trim(); return text.length > 0 ? text : null; } function normalizeReasonCode(value: string): string | null { const normalized = value .trim() .replace(/[^\p{L}\p{N}_.:-]+/gu, "_") .replace(/^_+|_+$/g, "") .toLowerCase(); return normalized.length > 0 ? normalized.slice(0, 120) : null; } function pushReason(target: string[], value: string): void { const normalized = normalizeReasonCode(value); if (normalized && !target.includes(normalized)) { target.push(normalized); } } function pushUnique(target: string[], value: string): void { const text = value.trim(); if (text && !target.includes(text)) { target.push(text); } } function aggregationAxisForPlanner( planner: AssistantMcpDiscoveryPlannerContract ): AssistantMcpDiscoveryAggregationAxis | null { const axis = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.asked_aggregation_axis)?.toLowerCase(); return axis === "month" ? "month" : null; } function firstEntityCandidate(planner: AssistantMcpDiscoveryPlannerContract): string | null { const candidates = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? []; for (const candidate of candidates) { const text = toNonEmptyString(candidate); if (text) { return text; } } return null; } function dateScopeToFilters(dateScope: string | null): Pick { if (!dateScope) { return {}; } const yearMatch = dateScope.match(/^(\d{4})$/); if (yearMatch) { return { period_from: `${yearMatch[1]}-01-01`, period_to: `${yearMatch[1]}-12-31` }; } const dateMatch = dateScope.match(/^(\d{4})-(\d{2})-(\d{2})/); if (dateMatch) { const date = `${dateMatch[1]}-${dateMatch[2]}-${dateMatch[3]}`; return { period_from: date, period_to: date }; } return {}; } function buildLifecycleFilters(planner: AssistantMcpDiscoveryPlannerContract): AddressFilterSet { const meaning = planner.discovery_plan.turn_meaning_ref; const counterparty = firstEntityCandidate(planner); const organization = toNonEmptyString(meaning?.explicit_organization_scope); const dateScope = toNonEmptyString(meaning?.explicit_date_scope); return { ...dateScopeToFilters(dateScope), ...(counterparty ? { counterparty } : {}), ...(organization ? { organization } : {}), limit: planner.discovery_plan.execution_budget.max_rows_per_probe, sort: "period_asc" }; } function buildValueFlowFilters(planner: AssistantMcpDiscoveryPlannerContract): AddressFilterSet { const meaning = planner.discovery_plan.turn_meaning_ref; const counterparty = firstEntityCandidate(planner); const organization = toNonEmptyString(meaning?.explicit_organization_scope); const dateScope = toNonEmptyString(meaning?.explicit_date_scope); return { ...dateScopeToFilters(dateScope), ...(counterparty ? { counterparty } : {}), ...(organization ? { organization } : {}), limit: planner.discovery_plan.execution_budget.max_rows_per_probe, sort: "period_asc" }; } function isLifecyclePilotEligible(planner: AssistantMcpDiscoveryPlannerContract): boolean { const meaning = planner.discovery_plan.turn_meaning_ref; const domain = String(meaning?.asked_domain_family ?? "").toLowerCase(); const action = String(meaning?.asked_action_family ?? "").toLowerCase(); const combined = `${domain} ${action}`; return ( planner.proposed_primitives.includes("query_documents") && (combined.includes("lifecycle") || combined.includes("activity") || combined.includes("duration") || combined.includes("age")) ); } function isValueFlowPilotEligible(planner: AssistantMcpDiscoveryPlannerContract): boolean { const meaning = planner.discovery_plan.turn_meaning_ref; const domain = String(meaning?.asked_domain_family ?? "").toLowerCase(); const action = String(meaning?.asked_action_family ?? "").toLowerCase(); const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase(); const combined = `${domain} ${action} ${unsupported}`; return ( planner.proposed_primitives.includes("query_movements") && (combined.includes("turnover") || combined.includes("revenue") || combined.includes("payment") || combined.includes("payout") || combined.includes("value")) ); } interface ValueFlowPilotProfile { scope: Extract< AssistantMcpDiscoveryPilotScope, | "counterparty_value_flow_query_movements_v1" | "counterparty_supplier_payout_query_movements_v1" | "counterparty_bidirectional_value_flow_query_movements_v1" >; recipe_intent: Extract | null; direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"] | "bidirectional_net_value_flow"; } function valueFlowPilotProfile(planner: AssistantMcpDiscoveryPlannerContract): ValueFlowPilotProfile { const meaning = planner.discovery_plan.turn_meaning_ref; const action = String(meaning?.asked_action_family ?? "").toLowerCase(); const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase(); const combined = `${action} ${unsupported}`; if ( combined.includes("net_value_flow") || combined.includes("bidirectional") || combined.includes("netting") || combined.includes("net") ) { return { scope: "counterparty_bidirectional_value_flow_query_movements_v1", recipe_intent: null, direction: "bidirectional_net_value_flow" }; } if ( combined.includes("payout") || combined.includes("outflow") || combined.includes("supplier") || combined.includes("paid") ) { return { scope: "counterparty_supplier_payout_query_movements_v1", recipe_intent: "supplier_payouts_profile", direction: "outgoing_supplier_payout" }; } return { scope: "counterparty_value_flow_query_movements_v1", recipe_intent: "customer_revenue_and_payments", direction: "incoming_customer_revenue" }; } function skippedProbeResult(step: AssistantMcpDiscoveryRuntimeStepContract, limitation: string): AssistantMcpDiscoveryProbeResult { return { primitive_id: step.primitive_id, status: "skipped", rows_received: 0, rows_matched: 0, limitation }; } function queryResultToProbeResult( primitiveId: string, result: AddressMcpQueryExecutorResult ): AssistantMcpDiscoveryProbeResult { return { primitive_id: primitiveId, status: result.error ? "error" : "ok", rows_received: result.fetched_rows, rows_matched: result.matched_rows, limitation: result.error }; } function summarizeLifecycleRows(result: AddressMcpQueryExecutorResult): string | null { if (result.error) { return null; } if (result.fetched_rows <= 0) { return "0 MCP document rows fetched"; } return `${result.fetched_rows} MCP document rows fetched, ${result.matched_rows} matched lifecycle scope`; } function summarizeValueFlowRows(result: AddressMcpQueryExecutorResult): string | null { if (result.error) { return null; } if (result.fetched_rows <= 0) { return "0 MCP value-flow rows fetched"; } return `${result.fetched_rows} MCP value-flow rows fetched, ${result.matched_rows} matched value-flow scope`; } function rowDateValue(row: Record): string | null { const candidates = [ row["Период"], row["Period"], row["period"], row["Дата"], row["Date"], row["date"] ]; for (const candidate of candidates) { const text = toNonEmptyString(candidate); const match = text?.match(/(\d{4})-(\d{2})-(\d{2})/); if (match) { return `${match[1]}-${match[2]}-${match[3]}`; } } return null; } function rowAmountValue(row: Record): number | null { const candidates = [ row["Сумма"], row["РЎСѓРјРјР°"], row["СуммаДокумента"], row["СуммаДокумента"], row["Amount"], row["amount"], row["Total"], row["total"] ]; for (const candidate of candidates) { if (typeof candidate === "number" && Number.isFinite(candidate)) { return candidate; } const text = toNonEmptyString(candidate); if (!text) { continue; } const normalized = text .replace(/\s+/g, "") .replace(/\u00a0/g, "") .replace(",", ".") .replace(/[^\d.-]/g, ""); const parsed = Number(normalized); if (Number.isFinite(parsed)) { return parsed; } } return null; } function monthBucketFromIsoDate(isoDate: string | null): string | null { const match = isoDate?.match(/^(\d{4})-(\d{2})-\d{2}$/); return match ? `${match[1]}-${match[2]}` : null; } function netDirectionFromAmount(amount: number): AssistantMcpDiscoveryNetDirection { if (amount > 0) { return "net_incoming"; } if (amount < 0) { return "net_outgoing"; } return "balanced"; } function monthDiff(firstIsoDate: string, latestIsoDate: string): number { const first = new Date(`${firstIsoDate}T00:00:00.000Z`); const latest = new Date(`${latestIsoDate}T00:00:00.000Z`); if (Number.isNaN(first.getTime()) || Number.isNaN(latest.getTime()) || latest < first) { return 0; } let months = (latest.getUTCFullYear() - first.getUTCFullYear()) * 12; months += latest.getUTCMonth() - first.getUTCMonth(); if (latest.getUTCDate() < first.getUTCDate()) { months -= 1; } return Math.max(0, months); } function formatDurationHumanRu(totalMonths: number): string { const years = Math.floor(totalMonths / 12); const months = totalMonths % 12; const parts: string[] = []; if (years > 0) { parts.push(`${years} ${years === 1 ? "год" : years >= 2 && years <= 4 ? "года" : "лет"}`); } if (months > 0) { parts.push(`${months} ${months === 1 ? "месяц" : months >= 2 && months <= 4 ? "месяца" : "месяцев"}`); } return parts.length > 0 ? parts.join(" ") : "меньше месяца"; } function deriveActivityPeriod( result: AddressMcpQueryExecutorResult | null ): AssistantMcpDiscoveryDerivedActivityPeriod | null { if (!result || result.error || result.matched_rows <= 0) { return null; } const dates = result.rows .map((row) => rowDateValue(row)) .filter((value): value is string => Boolean(value)) .sort(); if (dates.length === 0) { return null; } const first = dates[0]; const latest = dates[dates.length - 1]; const totalMonths = monthDiff(first, latest); return { first_activity_date: first, latest_activity_date: latest, matched_rows: result.matched_rows, duration_total_months: totalMonths, duration_years: Math.floor(totalMonths / 12), duration_months_remainder: totalMonths % 12, duration_human_ru: formatDurationHumanRu(totalMonths), inference_basis: "first_and_latest_confirmed_1c_activity_rows" }; } function formatAmountHumanRu(amount: number): string { const formatted = new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 2, minimumFractionDigits: Number.isInteger(amount) ? 0 : 2 }) .format(amount) .replace(/\u00a0/g, " "); return `${formatted} руб.`; } function deriveValueFlowMonthBreakdown( result: AddressMcpQueryExecutorResult | null, aggregationAxis: AssistantMcpDiscoveryAggregationAxis | null ): AssistantMcpDiscoveryValueFlowMonthBucket[] { if (!result || result.error || aggregationAxis !== "month") { return []; } const buckets = new Map(); for (const row of result.rows) { const isoDate = rowDateValue(row); const monthBucket = monthBucketFromIsoDate(isoDate); const amount = rowAmountValue(row); if (!monthBucket || amount === null) { continue; } const current = buckets.get(monthBucket) ?? { rows_with_amount: 0, total_amount: 0 }; current.rows_with_amount += 1; current.total_amount += amount; buckets.set(monthBucket, current); } return Array.from(buckets.entries()) .sort(([left], [right]) => left.localeCompare(right)) .map(([monthBucket, bucket]) => ({ month_bucket: monthBucket, rows_with_amount: bucket.rows_with_amount, total_amount: bucket.total_amount, total_amount_human_ru: formatAmountHumanRu(bucket.total_amount) })); } function deriveBidirectionalValueFlowMonthBreakdown(input: { incomingResult: AddressMcpQueryExecutorResult | null; outgoingResult: AddressMcpQueryExecutorResult | null; aggregationAxis: AssistantMcpDiscoveryAggregationAxis | null; }): AssistantMcpDiscoveryBidirectionalValueFlowMonthBucket[] { if (input.aggregationAxis !== "month") { return []; } const incomingBuckets = deriveValueFlowMonthBreakdown(input.incomingResult, "month"); const outgoingBuckets = deriveValueFlowMonthBreakdown(input.outgoingResult, "month"); const allMonthBuckets = new Set(); for (const bucket of incomingBuckets) { allMonthBuckets.add(bucket.month_bucket); } for (const bucket of outgoingBuckets) { allMonthBuckets.add(bucket.month_bucket); } const incomingByMonth = new Map(incomingBuckets.map((bucket) => [bucket.month_bucket, bucket])); const outgoingByMonth = new Map(outgoingBuckets.map((bucket) => [bucket.month_bucket, bucket])); return Array.from(allMonthBuckets) .sort((left, right) => left.localeCompare(right)) .map((monthBucket) => { const incoming = incomingByMonth.get(monthBucket); const outgoing = outgoingByMonth.get(monthBucket); const incomingAmount = incoming?.total_amount ?? 0; const outgoingAmount = outgoing?.total_amount ?? 0; const netAmount = incomingAmount - outgoingAmount; return { month_bucket: monthBucket, incoming_total_amount: incomingAmount, incoming_total_amount_human_ru: formatAmountHumanRu(incomingAmount), incoming_rows_with_amount: incoming?.rows_with_amount ?? 0, outgoing_total_amount: outgoingAmount, outgoing_total_amount_human_ru: formatAmountHumanRu(outgoingAmount), outgoing_rows_with_amount: outgoing?.rows_with_amount ?? 0, net_amount: netAmount, net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)), net_direction: netDirectionFromAmount(netAmount) }; }); } function deriveValueFlow( result: AddressMcpQueryExecutorResult | null, counterparty: string | null, periodScope: string | null, direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"], probeLimit: number, aggregationAxis: AssistantMcpDiscoveryAggregationAxis | null ): AssistantMcpDiscoveryDerivedValueFlow | null { if (!result || result.error || result.matched_rows <= 0) { return null; } let totalAmount = 0; let rowsWithAmount = 0; for (const row of result.rows) { const amount = rowAmountValue(row); if (amount !== null) { totalAmount += amount; rowsWithAmount += 1; } } if (rowsWithAmount <= 0) { return null; } const dates = result.rows .map((row) => rowDateValue(row)) .filter((value): value is string => Boolean(value)) .sort(); return { value_flow_direction: direction, counterparty, period_scope: periodScope, aggregation_axis: aggregationAxis, rows_matched: result.matched_rows, rows_with_amount: rowsWithAmount, total_amount: totalAmount, 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, monthly_breakdown: deriveValueFlowMonthBreakdown(result, aggregationAxis), inference_basis: "sum_of_confirmed_1c_value_flow_rows" }; } function deriveValueFlowSideSummary( result: AddressMcpQueryExecutorResult | null, probeLimit: number ): AssistantMcpDiscoveryValueFlowSideSummary { if (!result || result.error || result.matched_rows <= 0) { return { rows_matched: 0, rows_with_amount: 0, total_amount: 0, total_amount_human_ru: formatAmountHumanRu(0), first_movement_date: null, latest_movement_date: null, coverage_limited_by_probe_limit: false }; } let totalAmount = 0; let rowsWithAmount = 0; for (const row of result.rows) { const amount = rowAmountValue(row); if (amount !== null) { totalAmount += amount; rowsWithAmount += 1; } } const dates = result.rows .map((row) => rowDateValue(row)) .filter((value): value is string => Boolean(value)) .sort(); return { rows_matched: result.matched_rows, rows_with_amount: rowsWithAmount, total_amount: totalAmount, 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 }; } function deriveBidirectionalValueFlow(input: { incomingResult: AddressMcpQueryExecutorResult | null; outgoingResult: AddressMcpQueryExecutorResult | null; counterparty: string | null; periodScope: string | null; probeLimit: number; aggregationAxis: AssistantMcpDiscoveryAggregationAxis | null; }): AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null { const incoming = deriveValueFlowSideSummary(input.incomingResult, input.probeLimit); const outgoing = deriveValueFlowSideSummary(input.outgoingResult, input.probeLimit); if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) { return null; } const netAmount = incoming.total_amount - outgoing.total_amount; return { counterparty: input.counterparty, period_scope: input.periodScope, aggregation_axis: input.aggregationAxis, incoming_customer_revenue: incoming, outgoing_supplier_payout: outgoing, net_amount: netAmount, 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, monthly_breakdown: deriveBidirectionalValueFlowMonthBreakdown({ incomingResult: input.incomingResult, outgoingResult: input.outgoingResult, aggregationAxis: input.aggregationAxis }), inference_basis: "incoming_minus_outgoing_confirmed_1c_value_flow_rows" }; } function summarizeBidirectionalValueFlowRows(input: { incomingResult: AddressMcpQueryExecutorResult | null; outgoingResult: AddressMcpQueryExecutorResult | null; }): string | null { const incoming = input.incomingResult; const outgoing = input.outgoingResult; if (!incoming && !outgoing) { return null; } const incomingSummary = incoming?.error ? "incoming value-flow query failed" : `${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`; return `${incomingSummary}; ${outgoingSummary}`; } function buildLifecycleConfirmedFacts(result: AddressMcpQueryExecutorResult, counterparty: string | null): string[] { if (result.error || result.matched_rows <= 0) { return []; } return [ counterparty ? `1C activity rows were found for counterparty ${counterparty}` : "1C activity rows were found for the requested counterparty scope" ]; } function buildValueFlowConfirmedFacts( result: AddressMcpQueryExecutorResult, counterparty: string | null, direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"] ): string[] { if (result.error || result.matched_rows <= 0) { return []; } if (direction === "outgoing_supplier_payout") { return [ counterparty ? `1C supplier-payout rows were found for counterparty ${counterparty}` : "1C supplier-payout rows were found for the requested counterparty scope" ]; } return [ counterparty ? `1C value-flow rows were found for counterparty ${counterparty}` : "1C value-flow rows were found for the requested counterparty scope" ]; } function buildBidirectionalValueFlowConfirmedFacts( derived: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null ): string[] { if (!derived) { return []; } const hasIncoming = derived.incoming_customer_revenue.rows_matched > 0; const hasOutgoing = derived.outgoing_supplier_payout.rows_matched > 0; if (derived.counterparty) { return [ `1C bidirectional value-flow rows were checked for counterparty ${derived.counterparty}: incoming=${hasIncoming ? "found" : "not_found"}, outgoing=${hasOutgoing ? "found" : "not_found"}` ]; } return [ `1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=${hasIncoming ? "found" : "not_found"}, outgoing=${hasOutgoing ? "found" : "not_found"}` ]; } function buildLifecycleInferredFacts(result: AddressMcpQueryExecutorResult): string[] { if (result.error || result.fetched_rows <= 0) { return []; } return ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"]; } function buildValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerivedValueFlow | null): string[] { if (!derived) { return []; } const facts: string[] = []; if (derived.value_flow_direction === "outgoing_supplier_payout") { facts.push("Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows"); } else { facts.push("Counterparty value-flow total was calculated from confirmed 1C movement rows"); } 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"); } return facts; } function buildBidirectionalValueFlowInferredFacts( derived: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null ): string[] { if (!derived) { return []; } const facts = ["Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"]; 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"); } return facts; } function buildLifecycleUnknownFacts(): string[] { return ["Legal registration date is not proven by this MCP discovery pilot"]; } function buildValueFlowUnknownFacts( periodScope: string | null, direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"], derived: AssistantMcpDiscoveryDerivedValueFlow | null ): string[] { const unknownFacts: string[] = []; if (derived?.coverage_limited_by_probe_limit) { unknownFacts.push("Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached"); } if (direction === "outgoing_supplier_payout") { unknownFacts.push( periodScope ? "Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot" : "Full all-time supplier-payout amount is not proven without an explicit checked period" ); return unknownFacts; } unknownFacts.push( periodScope ? "Full turnover outside the checked period is not proven by this MCP discovery pilot" : "Full all-time turnover is not proven without an explicit checked period" ); return unknownFacts; } function buildBidirectionalValueFlowUnknownFacts( periodScope: string | null, derived: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null ): string[] { const unknownFacts: string[] = []; if (derived?.coverage_limited_by_probe_limit) { unknownFacts.push( "Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached" ); } unknownFacts.push( periodScope ? "Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot" : "Full all-time bidirectional value-flow is not proven without an explicit checked period" ); return unknownFacts; } function buildEmptyEvidence( planner: AssistantMcpDiscoveryPlannerContract, dryRun: AssistantMcpDiscoveryRuntimeDryRunContract, probeResults: AssistantMcpDiscoveryProbeResult[], reason: string ): AssistantMcpDiscoveryEvidenceContract { return resolveAssistantMcpDiscoveryEvidence({ plan: planner.discovery_plan, probeResults, unknownFacts: [reason], queryLimitations: [reason], recommendedNextProbe: dryRun.user_facing_fallback }); } export async function executeAssistantMcpDiscoveryPilot( planner: AssistantMcpDiscoveryPlannerContract, deps: AssistantMcpDiscoveryPilotExecutorDeps = DEFAULT_DEPS ): Promise { const dryRun = buildAssistantMcpDiscoveryRuntimeDryRun(planner); const reasonCodes = [...dryRun.reason_codes]; const executedPrimitives: string[] = []; const skippedPrimitives: string[] = []; const probeResults: AssistantMcpDiscoveryProbeResult[] = []; const queryLimitations: string[] = []; if (dryRun.adapter_status === "blocked") { pushReason(reasonCodes, "pilot_blocked_before_mcp_execution"); const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "MCP discovery pilot was blocked before execution"); return { schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPilotExecutor", pilot_status: "blocked", pilot_scope: "counterparty_lifecycle_query_documents_v1", dry_run: dryRun, mcp_execution_performed: false, executed_primitives: executedPrimitives, skipped_primitives: skippedPrimitives, probe_results: probeResults, evidence, source_rows_summary: null, derived_activity_period: null, derived_value_flow: null, derived_bidirectional_value_flow: null, query_limitations: ["MCP discovery pilot was blocked before execution"], reason_codes: reasonCodes }; } if (dryRun.adapter_status !== "dry_run_ready") { pushReason(reasonCodes, "pilot_needs_clarification_before_mcp_execution"); const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "MCP discovery pilot needs more scope before execution"); return { schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPilotExecutor", pilot_status: "skipped_needs_clarification", pilot_scope: "counterparty_lifecycle_query_documents_v1", dry_run: dryRun, mcp_execution_performed: false, executed_primitives: executedPrimitives, skipped_primitives: skippedPrimitives, probe_results: probeResults, evidence, source_rows_summary: null, derived_activity_period: null, derived_value_flow: null, derived_bidirectional_value_flow: null, query_limitations: ["MCP discovery pilot needs more scope before execution"], reason_codes: reasonCodes }; } const lifecyclePilotEligible = isLifecyclePilotEligible(planner); const valueFlowPilotEligible = isValueFlowPilotEligible(planner); if (!lifecyclePilotEligible && !valueFlowPilotEligible) { pushReason(reasonCodes, "pilot_scope_unsupported_for_live_execution"); for (const step of dryRun.execution_steps) { skippedPrimitives.push(step.primitive_id); probeResults.push(skippedProbeResult(step, "pilot_scope_unsupported_for_live_execution")); } const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "MCP discovery pilot scope is not implemented yet"); return { schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPilotExecutor", pilot_status: "unsupported", pilot_scope: "counterparty_lifecycle_query_documents_v1", dry_run: dryRun, mcp_execution_performed: false, executed_primitives: executedPrimitives, skipped_primitives: skippedPrimitives, probe_results: probeResults, evidence, source_rows_summary: null, derived_activity_period: null, derived_value_flow: null, derived_bidirectional_value_flow: null, query_limitations: ["MCP discovery pilot scope is not implemented yet"], reason_codes: reasonCodes }; } const counterparty = firstEntityCandidate(planner); const dateScope = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.explicit_date_scope); const aggregationAxis = aggregationAxisForPlanner(planner); if (valueFlowPilotEligible) { let queryResult: AddressMcpQueryExecutorResult | null = null; const filters = buildValueFlowFilters(planner); const valueFlowProfile = valueFlowPilotProfile(planner); if (valueFlowProfile.direction === "bidirectional_net_value_flow") { let incomingResult: AddressMcpQueryExecutorResult | null = null; let outgoingResult: AddressMcpQueryExecutorResult | null = null; const incomingSelection = selectAddressRecipe("customer_revenue_and_payments", filters); const outgoingSelection = selectAddressRecipe("supplier_payouts_profile", filters); if (!incomingSelection.selected_recipe || !outgoingSelection.selected_recipe) { pushReason(reasonCodes, "pilot_bidirectional_value_flow_recipe_not_available"); const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Bidirectional value-flow recipes are not available"); return { schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPilotExecutor", pilot_status: "unsupported", pilot_scope: valueFlowProfile.scope, dry_run: dryRun, mcp_execution_performed: false, executed_primitives: executedPrimitives, skipped_primitives: skippedPrimitives, probe_results: probeResults, evidence, source_rows_summary: null, derived_activity_period: null, derived_value_flow: null, derived_bidirectional_value_flow: null, query_limitations: ["Bidirectional value-flow recipes are not available"], reason_codes: reasonCodes }; } pushReason(reasonCodes, "pilot_bidirectional_value_flow_recipes_selected"); const incomingRecipePlan = buildAddressRecipePlan(incomingSelection.selected_recipe, filters); const outgoingRecipePlan = 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 }); outgoingResult = await deps.executeAddressMcpQuery({ query: outgoingRecipePlan.query, limit: outgoingRecipePlan.limit, account_scope: outgoingRecipePlan.account_scope }); 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); pushReason(reasonCodes, "pilot_bidirectional_incoming_query_movements_mcp_error"); } if (outgoingResult.error) { pushUnique(queryLimitations, outgoingResult.error); pushReason(reasonCodes, "pilot_bidirectional_outgoing_query_movements_mcp_error"); } if (!incomingResult.error || !outgoingResult.error) { pushReason(reasonCodes, "pilot_bidirectional_query_movements_mcp_executed"); } } const sourceRowsSummary = summarizeBidirectionalValueFlowRows({ incomingResult, outgoingResult }); const derivedBidirectionalValueFlow = deriveBidirectionalValueFlow({ incomingResult, outgoingResult, counterparty, periodScope: dateScope, probeLimit: planner.discovery_plan.execution_budget.max_rows_per_probe, aggregationAxis }); if (derivedBidirectionalValueFlow) { pushReason(reasonCodes, "pilot_derived_bidirectional_value_flow_from_confirmed_rows"); if (aggregationAxis === "month" && derivedBidirectionalValueFlow.monthly_breakdown.length > 0) { pushReason(reasonCodes, "pilot_derived_bidirectional_monthly_breakdown_from_confirmed_rows"); } } const evidence = resolveAssistantMcpDiscoveryEvidence({ plan: planner.discovery_plan, probeResults, confirmedFacts: buildBidirectionalValueFlowConfirmedFacts(derivedBidirectionalValueFlow), inferredFacts: buildBidirectionalValueFlowInferredFacts(derivedBidirectionalValueFlow), unknownFacts: buildBidirectionalValueFlowUnknownFacts(dateScope, derivedBidirectionalValueFlow), sourceRowsSummary, queryLimitations, recommendedNextProbe: "explain_evidence_basis" }); return { schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPilotExecutor", pilot_status: "executed", pilot_scope: valueFlowProfile.scope, dry_run: dryRun, mcp_execution_performed: executedPrimitives.length > 0, executed_primitives: executedPrimitives, skipped_primitives: skippedPrimitives, probe_results: probeResults, evidence, source_rows_summary: sourceRowsSummary, derived_activity_period: null, derived_value_flow: null, derived_bidirectional_value_flow: derivedBidirectionalValueFlow, query_limitations: queryLimitations, reason_codes: reasonCodes }; } const recipeIntent = valueFlowProfile.recipe_intent; const selection = recipeIntent ? selectAddressRecipe(recipeIntent, filters) : { selected_recipe: null }; if (!selection.selected_recipe) { pushReason(reasonCodes, "pilot_value_flow_recipe_not_available"); const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Value-flow recipe is not available"); return { schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPilotExecutor", pilot_status: "unsupported", pilot_scope: valueFlowProfile.scope, dry_run: dryRun, mcp_execution_performed: false, executed_primitives: executedPrimitives, skipped_primitives: skippedPrimitives, probe_results: probeResults, evidence, source_rows_summary: null, derived_activity_period: null, derived_value_flow: null, derived_bidirectional_value_flow: null, query_limitations: ["Value-flow recipe is not available"], reason_codes: reasonCodes }; } pushReason( reasonCodes, valueFlowProfile.direction === "outgoing_supplier_payout" ? "pilot_supplier_payout_recipe_selected" : "pilot_customer_revenue_recipe_selected" ); const recipePlan = 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 }); executedPrimitives.push(step.primitive_id); probeResults.push(queryResultToProbeResult(step.primitive_id, queryResult)); if (queryResult.error) { pushUnique(queryLimitations, queryResult.error); pushReason(reasonCodes, "pilot_query_movements_mcp_error"); } else { pushReason(reasonCodes, "pilot_query_movements_mcp_executed"); } } const sourceRowsSummary = queryResult ? summarizeValueFlowRows(queryResult) : null; const derivedValueFlow = deriveValueFlow( queryResult, counterparty, dateScope, valueFlowProfile.direction, planner.discovery_plan.execution_budget.max_rows_per_probe, aggregationAxis ); if (derivedValueFlow) { pushReason(reasonCodes, "pilot_derived_value_flow_from_confirmed_rows"); if (aggregationAxis === "month" && derivedValueFlow.monthly_breakdown.length > 0) { pushReason(reasonCodes, "pilot_derived_value_flow_monthly_breakdown_from_confirmed_rows"); } } const evidence = resolveAssistantMcpDiscoveryEvidence({ plan: planner.discovery_plan, probeResults, confirmedFacts: queryResult ? buildValueFlowConfirmedFacts(queryResult, counterparty, valueFlowProfile.direction) : [], inferredFacts: buildValueFlowInferredFacts(derivedValueFlow), unknownFacts: buildValueFlowUnknownFacts(dateScope, valueFlowProfile.direction, derivedValueFlow), sourceRowsSummary, queryLimitations, recommendedNextProbe: "explain_evidence_basis" }); return { schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPilotExecutor", pilot_status: "executed", pilot_scope: valueFlowProfile.scope, dry_run: dryRun, mcp_execution_performed: executedPrimitives.length > 0, executed_primitives: executedPrimitives, skipped_primitives: skippedPrimitives, probe_results: probeResults, evidence, source_rows_summary: sourceRowsSummary, derived_activity_period: null, derived_value_flow: derivedValueFlow, derived_bidirectional_value_flow: null, query_limitations: queryLimitations, reason_codes: reasonCodes }; } let queryResult: AddressMcpQueryExecutorResult | null = null; const filters = buildLifecycleFilters(planner); const selection = selectAddressRecipe("counterparty_activity_lifecycle", filters); if (!selection.selected_recipe) { pushReason(reasonCodes, "pilot_lifecycle_recipe_not_available"); const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Lifecycle recipe is not available"); return { schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPilotExecutor", pilot_status: "unsupported", pilot_scope: "counterparty_lifecycle_query_documents_v1", dry_run: dryRun, mcp_execution_performed: false, executed_primitives: executedPrimitives, skipped_primitives: skippedPrimitives, probe_results: probeResults, evidence, source_rows_summary: null, derived_activity_period: null, derived_value_flow: null, derived_bidirectional_value_flow: null, query_limitations: ["Lifecycle recipe is not available"], reason_codes: reasonCodes }; } const recipePlan = buildAddressRecipePlan(selection.selected_recipe, filters); for (const step of dryRun.execution_steps) { if (step.primitive_id !== "query_documents") { skippedPrimitives.push(step.primitive_id); probeResults.push(skippedProbeResult(step, "pilot_only_executes_query_documents")); continue; } queryResult = await deps.executeAddressMcpQuery({ query: recipePlan.query, limit: recipePlan.limit, account_scope: recipePlan.account_scope }); executedPrimitives.push(step.primitive_id); probeResults.push(queryResultToProbeResult(step.primitive_id, queryResult)); if (queryResult.error) { pushUnique(queryLimitations, queryResult.error); pushReason(reasonCodes, "pilot_query_documents_mcp_error"); } else { pushReason(reasonCodes, "pilot_query_documents_mcp_executed"); } } const sourceRowsSummary = queryResult ? summarizeLifecycleRows(queryResult) : null; const derivedActivityPeriod = deriveActivityPeriod(queryResult); if (derivedActivityPeriod) { pushReason(reasonCodes, "pilot_derived_activity_period_from_confirmed_rows"); } const evidence = resolveAssistantMcpDiscoveryEvidence({ plan: planner.discovery_plan, probeResults, confirmedFacts: queryResult ? buildLifecycleConfirmedFacts(queryResult, counterparty) : [], inferredFacts: queryResult ? buildLifecycleInferredFacts(queryResult) : [], unknownFacts: buildLifecycleUnknownFacts(), sourceRowsSummary, queryLimitations, recommendedNextProbe: "explain_evidence_basis" }); return { schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPilotExecutor", pilot_status: "executed", pilot_scope: "counterparty_lifecycle_query_documents_v1", dry_run: dryRun, mcp_execution_performed: executedPrimitives.length > 0, executed_primitives: executedPrimitives, skipped_primitives: skippedPrimitives, probe_results: probeResults, evidence, source_rows_summary: sourceRowsSummary, derived_activity_period: derivedActivityPeriod, derived_value_flow: null, derived_bidirectional_value_flow: null, query_limitations: queryLimitations, reason_codes: reasonCodes }; } export type AssistantMcpDiscoveryPilotMetadataResult = AddressMcpMetadataRowsResult;