NODEDC_1C/llm_normalizer/backend/dist/services/assistantMcpDiscoveryPilotE...

628 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION = void 0;
exports.executeAssistantMcpDiscoveryPilot = executeAssistantMcpDiscoveryPilot;
const addressMcpClient_1 = require("./addressMcpClient");
const assistantMcpDiscoveryRuntimeAdapter_1 = require("./assistantMcpDiscoveryRuntimeAdapter");
const assistantMcpDiscoveryPolicy_1 = require("./assistantMcpDiscoveryPolicy");
const addressRecipeCatalog_1 = require("./addressRecipeCatalog");
exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION = "assistant_mcp_discovery_pilot_executor_v1";
const DEFAULT_DEPS = {
executeAddressMcpQuery: addressMcpClient_1.executeAddressMcpQuery
};
function toNonEmptyString(value) {
if (value === null || value === undefined) {
return null;
}
const text = String(value).trim();
return text.length > 0 ? text : null;
}
function normalizeReasonCode(value) {
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, value) {
const normalized = normalizeReasonCode(value);
if (normalized && !target.includes(normalized)) {
target.push(normalized);
}
}
function pushUnique(target, value) {
const text = value.trim();
if (text && !target.includes(text)) {
target.push(text);
}
}
function firstEntityCandidate(planner) {
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) {
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) {
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) {
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) {
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) {
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")));
}
function valueFlowPilotProfile(planner) {
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("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, limitation) {
return {
primitive_id: step.primitive_id,
status: "skipped",
rows_received: 0,
rows_matched: 0,
limitation
};
}
function queryResultToProbeResult(primitiveId, result) {
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) {
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) {
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) {
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) {
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 monthDiff(firstIsoDate, latestIsoDate) {
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) {
const years = Math.floor(totalMonths / 12);
const months = totalMonths % 12;
const parts = [];
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) {
if (!result || result.error || result.matched_rows <= 0) {
return null;
}
const dates = result.rows
.map((row) => rowDateValue(row))
.filter((value) => 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) {
const formatted = new Intl.NumberFormat("ru-RU", {
maximumFractionDigits: 2,
minimumFractionDigits: Number.isInteger(amount) ? 0 : 2
})
.format(amount)
.replace(/\u00a0/g, " ");
return `${formatted} руб.`;
}
function deriveValueFlow(result, counterparty, periodScope, direction, probeLimit) {
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) => Boolean(value))
.sort();
return {
value_flow_direction: direction,
counterparty,
period_scope: periodScope,
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,
inference_basis: "sum_of_confirmed_1c_value_flow_rows"
};
}
function buildLifecycleConfirmedFacts(result, counterparty) {
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, counterparty, direction) {
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 buildLifecycleInferredFacts(result) {
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) {
if (!derived) {
return [];
}
if (derived.value_flow_direction === "outgoing_supplier_payout") {
return ["Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows"];
}
return ["Counterparty value-flow total was calculated from confirmed 1C movement rows"];
}
function buildLifecycleUnknownFacts() {
return ["Legal registration date is not proven by this MCP discovery pilot"];
}
function buildValueFlowUnknownFacts(periodScope, direction, derived) {
const unknownFacts = [];
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 buildEmptyEvidence(planner, dryRun, probeResults, reason) {
return (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
plan: planner.discovery_plan,
probeResults,
unknownFacts: [reason],
queryLimitations: [reason],
recommendedNextProbe: dryRun.user_facing_fallback
});
}
async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
const dryRun = (0, assistantMcpDiscoveryRuntimeAdapter_1.buildAssistantMcpDiscoveryRuntimeDryRun)(planner);
const reasonCodes = [...dryRun.reason_codes];
const executedPrimitives = [];
const skippedPrimitives = [];
const probeResults = [];
const queryLimitations = [];
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: exports.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,
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: exports.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,
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: exports.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,
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);
if (valueFlowPilotEligible) {
let queryResult = null;
const filters = buildValueFlowFilters(planner);
const valueFlowProfile = valueFlowPilotProfile(planner);
const selection = (0, addressRecipeCatalog_1.selectAddressRecipe)(valueFlowProfile.recipe_intent, filters);
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: exports.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,
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 = (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
});
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);
if (derivedValueFlow) {
pushReason(reasonCodes, "pilot_derived_value_flow_from_confirmed_rows");
}
const evidence = (0, assistantMcpDiscoveryPolicy_1.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: exports.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,
query_limitations: queryLimitations,
reason_codes: reasonCodes
};
}
let queryResult = null;
const filters = buildLifecycleFilters(planner);
const selection = (0, addressRecipeCatalog_1.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: exports.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,
query_limitations: ["Lifecycle recipe is not available"],
reason_codes: reasonCodes
};
}
const recipePlan = (0, addressRecipeCatalog_1.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 = (0, assistantMcpDiscoveryPolicy_1.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: exports.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,
query_limitations: queryLimitations,
reason_codes: reasonCodes
};
}