Planner Autonomy: закрепить bounded inference и semantic bridges

This commit is contained in:
2026-05-01 12:53:21 +03:00
parent 3634404b1c
commit c7c85e9b42
26 changed files with 3496 additions and 55 deletions
@@ -283,13 +283,13 @@ const CHAIN_TEMPLATES = [
},
{
chain_id: "lifecycle",
semantic_data_need: "counterparty lifecycle evidence",
chain_summary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.",
semantic_data_need: "counterparty lifecycle evidence with bounded activity-window inference",
chain_summary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window without presenting it as legal registration age.",
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
base_required_axes: ["document_date", "coverage_target", "evidence_basis"],
base_required_axes: ["document_date", "coverage_target", "evidence_basis", "activity_window", "legal_fact_boundary"],
supported_fact_families: ["activity_lifecycle"],
supported_action_families: ["activity_duration"],
planning_tags: ["document", "explanation", "coverage"],
planning_tags: ["document", "explanation", "coverage", "bounded_inference", "activity_window", "legal_fact_boundary"],
safe_for_model_planning: true,
requires_evidence_gate: true
}
@@ -1596,8 +1596,8 @@ function buildLifecycleConfirmedFacts(result, counterparty) {
}
return [
counterparty
? `1C activity rows were found for counterparty ${counterparty}`
: "1C activity rows were found for the requested counterparty scope"
? `1C activity rows were found for counterparty ${counterparty}; matched_rows=${result.matched_rows}`
: `1C activity rows were found for the requested counterparty scope; matched_rows=${result.matched_rows}`
];
}
function checkedCounterpartySuffixRu(counterparty) {
@@ -1667,7 +1667,15 @@ 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"];
const period = deriveActivityPeriod(result);
if (!period) {
return ["Business activity duration may be inferred only when confirmed 1C activity row dates are available"];
}
return [
"Business activity duration may be inferred from first and latest confirmed 1C activity rows",
`Activity window is bounded by first=${period.first_activity_date}, latest=${period.latest_activity_date}, matched_rows=${period.matched_rows}`,
"Activity-window inference is not legal registration age"
];
}
function buildDocumentInferredFacts(result, counterparty, periodScope) {
if (result.error || result.fetched_rows <= 0) {
@@ -1707,7 +1715,7 @@ function buildValueFlowInferredFacts(derived) {
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");
facts.push("Requested period coverage was recovered through monthly 1C value-flow probes");
}
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");
@@ -1720,7 +1728,7 @@ function buildRankedValueFlowInferredFacts(derived) {
}
const facts = ["Counterparty ranking was calculated from confirmed 1C movement rows grouped by counterparty"];
if (derived.coverage_recovered_by_period_chunking && derived.period_chunking_granularity === "month") {
facts.push("Requested period coverage for counterparty ranking was recovered through monthly 1C probes after a broad probe hit the row limit");
facts.push("Requested period coverage for counterparty ranking was recovered through monthly 1C probes");
}
return facts;
}
@@ -1730,7 +1738,7 @@ function buildBidirectionalValueFlowInferredFacts(derived) {
}
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");
facts.push("Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes");
}
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");
@@ -1738,7 +1746,10 @@ function buildBidirectionalValueFlowInferredFacts(derived) {
return facts;
}
function buildLifecycleUnknownFacts() {
return ["Legal registration date is not proven by this MCP discovery pilot"];
return [
"Legal registration date is not proven by this MCP discovery pilot",
"Business activity before the first confirmed 1C activity row is not proven by this MCP discovery pilot"
];
}
function buildDocumentUnknownFacts(periodScope, counterparty) {
return [
@@ -1753,7 +1764,7 @@ function buildMovementUnknownFacts(periodScope, counterparty) {
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");
unknownFacts.push("Complete requested-period coverage is not proven by the available checked rows");
}
if (direction === "outgoing_supplier_payout") {
unknownFacts.push(periodScope
@@ -1769,7 +1780,7 @@ function buildValueFlowUnknownFacts(periodScope, direction, derived) {
function buildRankedValueFlowUnknownFacts(periodScope, derived) {
const unknownFacts = [];
if (derived?.coverage_limited_by_probe_limit) {
unknownFacts.push("Complete requested-period ranking coverage is not proven because the MCP discovery probe row limit was reached");
unknownFacts.push("Complete requested-period ranking coverage is not proven by the available checked rows");
}
unknownFacts.push(periodScope
? "Full ranking outside the checked period is not proven by this MCP discovery pilot"
@@ -1779,7 +1790,7 @@ function buildRankedValueFlowUnknownFacts(periodScope, derived) {
function buildBidirectionalValueFlowUnknownFacts(periodScope, derived) {
const unknownFacts = [];
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("Complete requested-period coverage for bidirectional value-flow is not proven by the available checked rows");
}
unknownFacts.push(periodScope
? "Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"
@@ -40,6 +40,10 @@ function pushAllUnique(target, values) {
pushUnique(target, value);
}
}
const LIFECYCLE_BOUNDED_INFERENCE_REASON_CODES = [
"planner_lifecycle_bounded_activity_window_template",
"planner_lifecycle_legal_fact_boundary_required"
];
function recipeFromCatalogChainTemplate(input) {
const template = (0, assistantMcpCatalogIndex_1.getAssistantMcpCatalogChainTemplate)(input.chainId);
const axes = [...input.axes];
@@ -523,7 +527,7 @@ function recipeFor(input) {
axes,
primitives: primitiveSelection.primitives,
reason: "planner_selected_lifecycle_from_data_need_graph",
extraReasons: primitiveSelection.reasonCodes
extraReasons: [...primitiveSelection.reasonCodes, ...LIFECYCLE_BOUNDED_INFERENCE_REASON_CODES]
});
}
if (graphFactFamily === "schema_surface") {
@@ -676,7 +680,8 @@ function recipeFor(input) {
return recipeFromCatalogChainTemplate({
chainId: "lifecycle",
axes,
reason: "planner_selected_lifecycle_recipe"
reason: "planner_selected_lifecycle_recipe",
extraReasons: LIFECYCLE_BOUNDED_INFERENCE_REASON_CODES
});
}
if (includesAny(combined, ["metadata", "schema", "catalog"])) {
@@ -245,6 +245,24 @@ function localizeLine(value) {
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С после того, как общая выборка уперлась в лимит строк хотя бы по одной стороне.";
}
if (/^Requested period coverage was recovered through monthly 1C value-flow probes$/i.test(value)) {
return "Покрытие запрошенного периода восстановлено помесячными проверками 1С.";
}
if (/^Requested period coverage for counterparty ranking was recovered through monthly 1C probes$/i.test(value)) {
return "Покрытие запрошенного периода для рейтинга контрагентов восстановлено помесячными проверками 1С.";
}
if (/^Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes$/i.test(value)) {
return "Покрытие запрошенного периода по двустороннему денежному потоку восстановлено помесячными проверками 1С.";
}
if (/^Complete requested-period coverage is not proven by the available checked rows$/i.test(value)) {
return "Полное покрытие запрошенного периода не подтверждено доступными проверенными строками.";
}
if (/^Complete requested-period ranking coverage is not proven by the available checked rows$/i.test(value)) {
return "Полное покрытие рейтинга за запрошенный период не подтверждено доступными проверенными строками.";
}
if (/^Complete requested-period coverage for bidirectional value-flow is not proven by the available checked rows$/i.test(value)) {
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено доступными проверенными строками.";
}
return value;
}
function section(title, lines) {
@@ -514,6 +514,9 @@ function hasLifecycleSignal(text) {
function hasValueFlowSignal(text) {
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(text);
}
function hasValueFlowAggregateQuestionSignal(text) {
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u0443\u043c\u043c|\u0438\u0442\u043e\u0433|\u0440\u0430\u0441\u0441\u0447\u0438\u0442|\u043d\u0435\u0442\u0442\u043e|\u0441\u0430\u043b\u044c\u0434\u043e|how\s+much|total|sum|net)/iu.test(text);
}
function hasPayoutSignal(text) {
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(text);
}
@@ -836,6 +839,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
hasMetadataSignal(rawText);
const rawEntityResolutionSignal = !rawLifecycleSignal && !rawValueFlowSignal && !rawMetadataSignal && hasEntityResolutionSignal(rawText);
const rawPayoutSignal = rawValueFlowSignal && !rawBidirectionalValueFlowSignal && hasPayoutSignal(rawText);
const rawValueFlowAggregateQuestionSignal = rawValueFlowSignal && hasValueFlowAggregateQuestionSignal(rawText);
const monthlyAggregationSignal = hasMonthlyAggregationSignal(rawText);
const rawAllTimeScopeSignal = hasAllTimeScopeHint(rawText);
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(rawText);
@@ -853,6 +857,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
const unsupported = toNonEmptyString(assistantTurnMeaning?.unsupported_but_understood_family);
const broadBusinessEvaluationUnsupported = unsupported === "broad_business_evaluation";
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
const currentTurnDocumentLaneSignal = rawAction === "list_documents";
const currentTurnMovementLaneSignal = rawAction === "list_movements";
@@ -1179,7 +1184,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
: semanticNeedFor({
domain: rawDomain ?? seededDomain,
action: rawAction ?? seededAction,
unsupported: unsupported ?? seededUnsupported,
unsupported: broadBusinessEvaluationUnsupported ? seededUnsupported : unsupported ?? seededUnsupported,
lifecycleSignal,
valueFlowSignal,
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
@@ -1444,8 +1449,13 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
if (turnMeaning.stale_replay_forbidden) {
cleanTurnMeaning.stale_replay_forbidden = true;
}
const currentTurnValueFlowExactOverrideApplicable = Boolean(valueFlowSignal &&
explicitIntentCandidate &&
rawValueFlowAggregateQuestionSignal &&
semanticDataNeed &&
(entityCandidates.length > 0 || explicitOrganizationScope || openScopeValueFlowWithoutResolvedCounterparty));
const runDiscovery = shouldRunDiscovery({
unsupported: unsupported ?? seededUnsupported,
unsupported: broadBusinessEvaluationUnsupported ? seededUnsupported : unsupported ?? seededUnsupported,
lifecycleSignal,
valueFlowSignal,
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
@@ -1465,7 +1475,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
metadataAmbiguityLaneClarificationApplicable ||
metadataGroundedMovementLaneApplicable ||
metadataGroundedDocumentLaneApplicable ||
groundedValueFlowFollowupApplicable
groundedValueFlowFollowupApplicable ||
currentTurnValueFlowExactOverrideApplicable
});
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
const sourceSignal = rawEntitySearchOverridesStaleScope
@@ -1576,6 +1587,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
if (groundedValueFlowFollowupApplicable) {
pushReason(reasonCodes, "mcp_discovery_grounded_value_flow_followup");
}
if (currentTurnValueFlowExactOverrideApplicable) {
pushReason(reasonCodes, "mcp_discovery_current_turn_value_flow_overrides_supported_exact");
}
if (documentEvidenceGroundedMovementFollowupApplicable) {
pushReason(reasonCodes, "mcp_discovery_document_evidence_grounded_movement_followup");
}
@@ -1606,6 +1620,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
if (unsupported) {
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
}
if (broadBusinessEvaluationUnsupported) {
pushReason(reasonCodes, "mcp_discovery_broad_business_evaluation_kept_in_living_chat");
}
if (!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
normalizedPredecomposeCounterparty) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
@@ -348,14 +348,14 @@ const CHAIN_TEMPLATES: AssistantMcpCatalogChainTemplateContract[] = [
},
{
chain_id: "lifecycle",
semantic_data_need: "counterparty lifecycle evidence",
semantic_data_need: "counterparty lifecycle evidence with bounded activity-window inference",
chain_summary:
"Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.",
"Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window without presenting it as legal registration age.",
fallback_primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"],
base_required_axes: ["document_date", "coverage_target", "evidence_basis"],
base_required_axes: ["document_date", "coverage_target", "evidence_basis", "activity_window", "legal_fact_boundary"],
supported_fact_families: ["activity_lifecycle"],
supported_action_families: ["activity_duration"],
planning_tags: ["document", "explanation", "coverage"],
planning_tags: ["document", "explanation", "coverage", "bounded_inference", "activity_window", "legal_fact_boundary"],
safe_for_model_planning: true,
requires_evidence_gate: true
}
@@ -2127,8 +2127,8 @@ function buildLifecycleConfirmedFacts(result: AddressMcpQueryExecutorResult, cou
}
return [
counterparty
? `1C activity rows were found for counterparty ${counterparty}`
: "1C activity rows were found for the requested counterparty scope"
? `1C activity rows were found for counterparty ${counterparty}; matched_rows=${result.matched_rows}`
: `1C activity rows were found for the requested counterparty scope; matched_rows=${result.matched_rows}`
];
}
@@ -2222,7 +2222,15 @@ function buildLifecycleInferredFacts(result: AddressMcpQueryExecutorResult): str
if (result.error || result.fetched_rows <= 0) {
return [];
}
return ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"];
const period = deriveActivityPeriod(result);
if (!period) {
return ["Business activity duration may be inferred only when confirmed 1C activity row dates are available"];
}
return [
"Business activity duration may be inferred from first and latest confirmed 1C activity rows",
`Activity window is bounded by first=${period.first_activity_date}, latest=${period.latest_activity_date}, matched_rows=${period.matched_rows}`,
"Activity-window inference is not legal registration age"
];
}
function buildDocumentInferredFacts(
@@ -2273,7 +2281,7 @@ function buildValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerivedValueF
}
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"
"Requested period coverage was recovered through monthly 1C value-flow probes"
);
}
if (derived.aggregation_axis === "month" && derived.monthly_breakdown.length > 0) {
@@ -2289,7 +2297,7 @@ function buildRankedValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerived
const facts = ["Counterparty ranking was calculated from confirmed 1C movement rows grouped by counterparty"];
if (derived.coverage_recovered_by_period_chunking && derived.period_chunking_granularity === "month") {
facts.push(
"Requested period coverage for counterparty ranking was recovered through monthly 1C probes after a broad probe hit the row limit"
"Requested period coverage for counterparty ranking was recovered through monthly 1C probes"
);
}
return facts;
@@ -2304,7 +2312,7 @@ function buildBidirectionalValueFlowInferredFacts(
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"
"Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes"
);
}
if (derived.aggregation_axis === "month" && derived.monthly_breakdown.length > 0) {
@@ -2314,7 +2322,10 @@ function buildBidirectionalValueFlowInferredFacts(
}
function buildLifecycleUnknownFacts(): string[] {
return ["Legal registration date is not proven by this MCP discovery pilot"];
return [
"Legal registration date is not proven by this MCP discovery pilot",
"Business activity before the first confirmed 1C activity row is not proven by this MCP discovery pilot"
];
}
function buildDocumentUnknownFacts(periodScope: string | null, counterparty: string | null): string[] {
@@ -2336,7 +2347,7 @@ function buildValueFlowUnknownFacts(
): 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");
unknownFacts.push("Complete requested-period coverage is not proven by the available checked rows");
}
if (direction === "outgoing_supplier_payout") {
unknownFacts.push(
@@ -2360,7 +2371,7 @@ function buildRankedValueFlowUnknownFacts(
): string[] {
const unknownFacts: string[] = [];
if (derived?.coverage_limited_by_probe_limit) {
unknownFacts.push("Complete requested-period ranking coverage is not proven because the MCP discovery probe row limit was reached");
unknownFacts.push("Complete requested-period ranking coverage is not proven by the available checked rows");
}
unknownFacts.push(
periodScope
@@ -2377,7 +2388,7 @@ function buildBidirectionalValueFlowUnknownFacts(
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"
"Complete requested-period coverage for bidirectional value-flow is not proven by the available checked rows"
);
}
unknownFacts.push(
@@ -127,6 +127,11 @@ function pushAllUnique(target: string[], values: string[]): void {
}
}
const LIFECYCLE_BOUNDED_INFERENCE_REASON_CODES = [
"planner_lifecycle_bounded_activity_window_template",
"planner_lifecycle_legal_fact_boundary_required"
];
function recipeFromCatalogChainTemplate(input: {
chainId: AssistantMcpCatalogChainTemplateId;
axes: string[];
@@ -711,7 +716,7 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
axes,
primitives: primitiveSelection.primitives,
reason: "planner_selected_lifecycle_from_data_need_graph",
extraReasons: primitiveSelection.reasonCodes
extraReasons: [...primitiveSelection.reasonCodes, ...LIFECYCLE_BOUNDED_INFERENCE_REASON_CODES]
});
}
@@ -874,7 +879,8 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
return recipeFromCatalogChainTemplate({
chainId: "lifecycle",
axes,
reason: "planner_selected_lifecycle_recipe"
reason: "planner_selected_lifecycle_recipe",
extraReasons: LIFECYCLE_BOUNDED_INFERENCE_REASON_CODES
});
}
@@ -300,6 +300,24 @@ function localizeLine(value: string): string {
) {
return "Покрытие запрошенного периода по двустороннему денежному потоку восстановлено помесячными проверками 1С после того, как общая выборка уперлась в лимит строк хотя бы по одной стороне.";
}
if (/^Requested period coverage was recovered through monthly 1C value-flow probes$/i.test(value)) {
return "Покрытие запрошенного периода восстановлено помесячными проверками 1С.";
}
if (/^Requested period coverage for counterparty ranking was recovered through monthly 1C probes$/i.test(value)) {
return "Покрытие запрошенного периода для рейтинга контрагентов восстановлено помесячными проверками 1С.";
}
if (/^Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes$/i.test(value)) {
return "Покрытие запрошенного периода по двустороннему денежному потоку восстановлено помесячными проверками 1С.";
}
if (/^Complete requested-period coverage is not proven by the available checked rows$/i.test(value)) {
return "Полное покрытие запрошенного периода не подтверждено доступными проверенными строками.";
}
if (/^Complete requested-period ranking coverage is not proven by the available checked rows$/i.test(value)) {
return "Полное покрытие рейтинга за запрошенный период не подтверждено доступными проверенными строками.";
}
if (/^Complete requested-period coverage for bidirectional value-flow is not proven by the available checked rows$/i.test(value)) {
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено доступными проверенными строками.";
}
return value;
}
@@ -688,6 +688,12 @@ function hasValueFlowSignal(text: string): boolean {
);
}
function hasValueFlowAggregateQuestionSignal(text: string): boolean {
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u0443\u043c\u043c|\u0438\u0442\u043e\u0433|\u0440\u0430\u0441\u0441\u0447\u0438\u0442|\u043d\u0435\u0442\u0442\u043e|\u0441\u0430\u043b\u044c\u0434\u043e|how\s+much|total|sum|net)/iu.test(
text
);
}
function hasPayoutSignal(text: string): boolean {
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(
text
@@ -1128,6 +1134,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
const rawEntityResolutionSignal =
!rawLifecycleSignal && !rawValueFlowSignal && !rawMetadataSignal && hasEntityResolutionSignal(rawText);
const rawPayoutSignal = rawValueFlowSignal && !rawBidirectionalValueFlowSignal && hasPayoutSignal(rawText);
const rawValueFlowAggregateQuestionSignal =
rawValueFlowSignal && hasValueFlowAggregateQuestionSignal(rawText);
const monthlyAggregationSignal = hasMonthlyAggregationSignal(rawText);
const rawAllTimeScopeSignal = hasAllTimeScopeHint(rawText);
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(rawText);
@@ -1150,6 +1158,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
const unsupported = toNonEmptyString(assistantTurnMeaning?.unsupported_but_understood_family);
const broadBusinessEvaluationUnsupported = unsupported === "broad_business_evaluation";
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
const currentTurnDocumentLaneSignal = rawAction === "list_documents";
const currentTurnMovementLaneSignal = rawAction === "list_movements";
@@ -1549,7 +1558,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
: semanticNeedFor({
domain: rawDomain ?? seededDomain,
action: rawAction ?? seededAction,
unsupported: unsupported ?? seededUnsupported,
unsupported: broadBusinessEvaluationUnsupported ? seededUnsupported : unsupported ?? seededUnsupported,
lifecycleSignal,
valueFlowSignal,
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
@@ -1856,8 +1865,16 @@ export function buildAssistantMcpDiscoveryTurnInput(
cleanTurnMeaning.stale_replay_forbidden = true;
}
const currentTurnValueFlowExactOverrideApplicable = Boolean(
valueFlowSignal &&
explicitIntentCandidate &&
rawValueFlowAggregateQuestionSignal &&
semanticDataNeed &&
(entityCandidates.length > 0 || explicitOrganizationScope || openScopeValueFlowWithoutResolvedCounterparty)
);
const runDiscovery = shouldRunDiscovery({
unsupported: unsupported ?? seededUnsupported,
unsupported: broadBusinessEvaluationUnsupported ? seededUnsupported : unsupported ?? seededUnsupported,
lifecycleSignal,
valueFlowSignal,
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
@@ -1879,7 +1896,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
metadataAmbiguityLaneClarificationApplicable ||
metadataGroundedMovementLaneApplicable ||
metadataGroundedDocumentLaneApplicable ||
groundedValueFlowFollowupApplicable
groundedValueFlowFollowupApplicable ||
currentTurnValueFlowExactOverrideApplicable
});
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = rawEntitySearchOverridesStaleScope
@@ -1991,6 +2009,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
if (groundedValueFlowFollowupApplicable) {
pushReason(reasonCodes, "mcp_discovery_grounded_value_flow_followup");
}
if (currentTurnValueFlowExactOverrideApplicable) {
pushReason(reasonCodes, "mcp_discovery_current_turn_value_flow_overrides_supported_exact");
}
if (documentEvidenceGroundedMovementFollowupApplicable) {
pushReason(reasonCodes, "mcp_discovery_document_evidence_grounded_movement_followup");
}
@@ -2021,6 +2042,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
if (unsupported) {
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
}
if (broadBusinessEvaluationUnsupported) {
pushReason(reasonCodes, "mcp_discovery_broad_business_evaluation_kept_in_living_chat");
}
if (
!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
normalizedPredecomposeCounterparty
@@ -57,6 +57,7 @@ describe("assistant MCP catalog index", () => {
const valueFlowTemplate = getAssistantMcpCatalogChainTemplate("value_flow");
const valueFlowComparisonTemplate = getAssistantMcpCatalogChainTemplate("value_flow_comparison");
const valueFlowRankingTemplate = getAssistantMcpCatalogChainTemplate("value_flow_ranking");
const lifecycleTemplate = getAssistantMcpCatalogChainTemplate("lifecycle");
expect(documentTemplate.fallback_primitives).toEqual([
"resolve_entity_reference",
@@ -75,6 +76,16 @@ describe("assistant MCP catalog index", () => {
"aggregate_by_axis",
"probe_coverage"
]);
expect(lifecycleTemplate.base_required_axes).toEqual([
"document_date",
"coverage_target",
"evidence_basis",
"activity_window",
"legal_fact_boundary"
]);
expect(lifecycleTemplate.planning_tags).toEqual(
expect.arrayContaining(["bounded_inference", "activity_window", "legal_fact_boundary"])
);
});
it("can search reviewed primitives from data-need decomposition candidates", () => {
@@ -80,10 +80,18 @@ describe("assistant MCP discovery answer adapter", () => {
expect(draft.internal_mechanics_allowed).toBe(false);
expect(draft.headline).toContain("подтвержденная активность");
expect(draft.confirmed_lines[0]).toContain("SVK");
expect(draft.confirmed_lines[0]).toContain("matched_rows=1");
expect(draft.inference_lines[0]).toContain("меньше месяца");
expect(draft.inference_lines.join("\n")).toContain("Первая найденная активность: 2020-01-15");
expect(draft.inference_lines.join("\n")).toContain("не юридически подтвержденный возраст регистрации");
expect(pilot.evidence.inferred_facts).toContain(
"Activity window is bounded by first=2020-01-15, latest=2020-01-15, matched_rows=1"
);
expect(pilot.evidence.inferred_facts).toContain("Activity-window inference is not legal registration age");
expect(draft.unknown_lines).toContain("Legal registration date is not proven by this MCP discovery pilot");
expect(draft.unknown_lines).toContain(
"Business activity before the first confirmed 1C activity row is not proven by this MCP discovery pilot"
);
expect(draft.must_not_claim).toContain("Do not present inferred activity duration as a formally confirmed legal fact.");
expect(draft.reason_codes).toContain("answer_contains_unknown_fact_boundary");
});
@@ -912,10 +920,10 @@ describe("assistant MCP discovery answer adapter", () => {
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
expect(draft.inference_lines).toContain(
"Requested period coverage was recovered through monthly 1C value-flow probes after the broad probe hit the row limit"
"Requested period coverage was recovered through monthly 1C value-flow probes"
);
expect(draft.unknown_lines).not.toContain(
"Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached"
"Complete requested-period coverage is not proven by the available checked rows"
);
});
@@ -971,6 +979,9 @@ describe("assistant MCP discovery answer adapter", () => {
expect(inferenceText).toContain("2020-01-15");
expect(inferenceText).toContain("2023-12-20");
expect(inferenceText).toContain("не юридически подтвержденный возраст регистрации");
expect(pilot.evidence.inferred_facts).toContain(
"Activity window is bounded by first=2020-01-15, latest=2023-12-20, matched_rows=2"
);
expect(draft.reason_codes).toContain("pilot_derived_activity_period_from_confirmed_rows");
});
@@ -714,7 +714,7 @@ describe("assistant MCP discovery pilot executor", () => {
expect(result.derived_value_flow?.coverage_limited_by_probe_limit).toBe(true);
expect(result.evidence.unknown_facts).toContain(
"Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached"
"Complete requested-period coverage is not proven by the available checked rows"
);
});
@@ -760,10 +760,10 @@ describe("assistant MCP discovery pilot executor", () => {
latest_movement_date: "2020-12-05"
});
expect(result.evidence.inferred_facts).toContain(
"Requested period coverage was recovered through monthly 1C value-flow probes after the broad probe hit the row limit"
"Requested period coverage was recovered through monthly 1C value-flow probes"
);
expect(result.evidence.unknown_facts).not.toContain(
"Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached"
"Complete requested-period coverage is not proven by the available checked rows"
);
expect(result.reason_codes).toContain("pilot_monthly_period_chunking_recovered_coverage");
});
@@ -942,10 +942,10 @@ describe("assistant MCP discovery pilot executor", () => {
}
});
expect(result.evidence.inferred_facts).toContain(
"Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes after a broad probe hit the row limit"
"Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes"
);
expect(result.evidence.unknown_facts).not.toContain(
"Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached"
"Complete requested-period coverage for bidirectional value-flow is not proven by the available checked rows"
);
expect(result.reason_codes).toContain("pilot_bidirectional_outgoing_monthly_period_chunking_recovered_coverage");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(14);
@@ -671,7 +671,16 @@ describe("assistant MCP discovery planner", () => {
"probe_coverage",
"explain_evidence_basis"
]);
expect(result.required_axes).toEqual(["counterparty", "document_date", "coverage_target", "evidence_basis"]);
expect(result.required_axes).toEqual([
"counterparty",
"document_date",
"coverage_target",
"evidence_basis",
"activity_window",
"legal_fact_boundary"
]);
expect(result.reason_codes).toContain("planner_lifecycle_bounded_activity_window_template");
expect(result.reason_codes).toContain("planner_lifecycle_legal_fact_boundary_required");
});
it("uses metadata-only planning when the user asks about available schema surface", () => {
@@ -133,6 +133,68 @@ describe("assistant MCP discovery turn input adapter", () => {
expect(result.reason_codes).not.toContain("mcp_discovery_payout_signal_detected");
});
it("overrides a supported exact current-turn payout route when the question asks for a payment amount", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage:
"\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u044b \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438 \u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a \u0437\u0430 2020 \u0433\u043e\u0434?",
effectiveMessage:
"\u0421\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u044b\u043b\u043e \u043e\u043f\u043b\u0430\u0447\u0435\u043d\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0443 '\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a' \u0432 2020 \u0433\u043e\u0434\u0443?",
assistantTurnMeaning: {
asked_domain_family: "counterparty",
asked_action_family: "counterparty_population_and_roles",
explicit_intent_candidate: "counterparty_population_and_roles",
explicit_entity_candidates: [{ value: "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a" }]
},
predecomposeContract: {
entities: { counterparty: "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a" },
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "payout",
explicit_entity_candidates: ["\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_payouts_or_outflow",
stale_replay_forbidden: true
});
expect(result.reason_codes).toContain("mcp_discovery_current_turn_value_flow_overrides_supported_exact");
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
});
it("overrides a supported exact current-turn bank list when the question asks for net cash flow", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage:
"\u043a\u0430\u043a\u043e\u0435 \u043d\u0435\u0442\u0442\u043e \u043f\u043e \u0434\u0435\u043d\u044c\u0433\u0430\u043c \u0441 \u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a \u0437\u0430 2020 \u0433\u043e\u0434: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0438 \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438?",
assistantTurnMeaning: {
asked_domain_family: "counterparty",
asked_action_family: "bank_operations_by_counterparty",
explicit_intent_candidate: "bank_operations_by_counterparty",
explicit_entity_candidates: [{ value: "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a" }]
},
predecomposeContract: {
entities: { counterparty: "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a" },
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
explicit_entity_candidates: ["\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting",
stale_replay_forbidden: true
});
expect(result.reason_codes).toContain("mcp_discovery_current_turn_value_flow_overrides_supported_exact");
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
});
it("prefers the explicit current-turn counterparty over stale organization-scoped carryover in net follow-up discovery", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
@@ -1347,6 +1409,26 @@ describe("assistant MCP discovery turn input adapter", () => {
expect(result.reason_codes).toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
});
it("does not replace broad business evaluation with metadata discovery", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage:
"\u041a\u0430\u043a \u0442\u044b \u043e\u0446\u0435\u043d\u0438\u0448\u044c \u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438?",
assistantTurnMeaning: {
asked_domain_family: "business_summary",
asked_action_family: "broad_evaluation",
explicit_date_scope: "2026-05-01",
unsupported_but_understood_family: "broad_business_evaluation",
stale_replay_forbidden: true
}
});
expect(result.adapter_status).toBe("not_applicable");
expect(result.should_run_discovery).toBe(false);
expect(result.turn_meaning_ref).toBeNull();
expect(result.reason_codes).toContain("mcp_discovery_broad_business_evaluation_kept_in_living_chat");
expect(result.reason_codes).toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
});
it("does not bootstrap metadata discovery from a referential document exclusion follow-up over exact document context", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "кроме этого документа есть еще что-то?",