Связать evidence planner с route candidate handoff

This commit is contained in:
2026-05-22 18:48:51 +03:00
parent f34ae5d6df
commit d64d7f84cc
11 changed files with 256 additions and 16 deletions
@@ -57,6 +57,24 @@ function providedAxesFromMeaning(meaning) {
function missingAxes(requiredAxes, providedAxes) {
return requiredAxes.filter((axis) => !providedAxes.includes(axis));
}
const USER_ACTIONABLE_AXIS_SET = new Set([
"counterparty",
"business_entity",
"organization",
"period",
"as_of_date",
"item",
"supplier",
"buyer",
"warehouse",
"document",
"contract",
"metadata_scope",
"lane_family_choice"
]);
function userActionableMissingAxes(axes) {
return axes.filter((axis) => USER_ACTIONABLE_AXIS_SET.has(axis));
}
function coverageExpectationFor(graph) {
if (graph?.proof_expectation === "bounded_inference") {
return "bounded_inference";
@@ -100,7 +118,9 @@ function buildAssistantEvidencePlanner(input) {
const requiredAxes = uniqueStrings(plan.required_axes);
const providedAxes = providedAxesFromMeaning(turnMeaning);
const graphClarificationGaps = uniqueStrings(graph?.clarification_gaps ?? []);
const axisGaps = missingAxes(requiredAxes, providedAxes);
const additionalAxisGaps = uniqueStrings(input.additionalMissingAxes ?? []).filter((axis) => !providedAxes.includes(axis) && (requiredAxes.includes(axis) || USER_ACTIONABLE_AXIS_SET.has(axis)));
const axisGaps = uniqueStrings([...additionalAxisGaps, ...missingAxes(requiredAxes, providedAxes)]);
const actionableAxisGaps = userActionableMissingAxes(axisGaps);
const clarificationGaps = uniqueStrings([...graphClarificationGaps, ...axisGaps]);
const coverageExpectation = coverageExpectationFor(graph);
const answerMode = answerModeFor({
@@ -135,6 +155,7 @@ function buildAssistantEvidencePlanner(input) {
required_axes: requiredAxes,
provided_axes: providedAxes,
missing_axes: axisGaps,
user_actionable_missing_axes: actionableAxisGaps,
clarification_gaps: clarificationGaps
},
primitive_plan: {
@@ -83,6 +83,9 @@ function buildAssistantMcpDiscoveryDebugAttachmentFields(input) {
mcp_discovery_route_candidate_fact_family: toNonEmptyString(routeCandidate?.business_fact_family),
mcp_discovery_route_candidate_action_family: toNonEmptyString(routeCandidate?.action_family),
mcp_discovery_route_candidate_proof_expectation: toNonEmptyString(routeCandidate?.proof_expectation),
mcp_discovery_route_candidate_evidence_plan_status: toNonEmptyString(routeCandidate?.evidence_plan_status),
mcp_discovery_route_candidate_evidence_answer_mode: toNonEmptyString(routeCandidate?.evidence_answer_mode),
mcp_discovery_route_candidate_evidence_expected_coverage: toNonEmptyString(routeCandidate?.evidence_expected_coverage),
mcp_discovery_route_candidate_missing_axes: toStringArray(routeCandidate?.missing_axes),
mcp_discovery_route_candidate_provided_axes: toStringArray(routeCandidate?.provided_axes),
mcp_discovery_route_candidate_executable_now: routeCandidate?.executable_now === true,
@@ -42,6 +42,16 @@ function pushAllUnique(target, values) {
pushUnique(target, value);
}
}
function uniqueStringList(values) {
const result = [];
for (const value of values) {
const text = toNonEmptyString(value);
if (text) {
pushUnique(result, text);
}
}
return result;
}
function pushCatalogChainTemplateAlignmentReasons(target, alignment) {
if (alignment.alignment_status === "selected_matches_top") {
pushReason(target, "planner_catalog_chain_template_alignment_evaluated");
@@ -1078,6 +1088,55 @@ function statusFrom(plan, review) {
}
return "ready_for_execution";
}
function providedAxesFromPlanTurnMeaning(plan) {
const result = [];
const meaning = plan.turn_meaning_ref;
if ((meaning?.explicit_entity_candidates?.length ?? 0) > 0) {
pushUnique(result, "counterparty");
pushUnique(result, "business_entity");
}
if (toNonEmptyString(meaning?.explicit_organization_scope)) {
pushUnique(result, "organization");
}
if (toNonEmptyString(meaning?.explicit_date_scope)) {
pushUnique(result, "period");
}
if (toNonEmptyString(meaning?.asked_aggregation_axis)) {
pushUnique(result, "aggregate_axis");
}
if (toNonEmptyString(meaning?.metadata_scope_hint)) {
pushUnique(result, "metadata_scope");
}
return result;
}
function preferredClarificationAxesForRecipe(input) {
if (input.recipe.chainId === "value_flow_ranking" ||
input.recipe.chainId === "value_flow_comparison" ||
input.recipe.chainId === "business_overview") {
return ["organization"];
}
if (input.dataNeedGraph?.business_fact_family === "value_flow" && !hasSubjectCandidates(input.dataNeedGraph)) {
return ["organization"];
}
return [];
}
function missingAxesFromCatalogReview(review, input) {
const result = [];
for (const options of Object.values(review.missing_axes_by_primitive)) {
const candidates = options
.map((option) => uniqueStringList(option).filter((axis) => !input.providedAxes.includes(axis)))
.filter((option) => option.length > 0);
const preferredCandidate = candidates.find((option) => option.some((axis) => input.preferredAxes.includes(axis)));
const candidate = preferredCandidate ?? candidates.sort((left, right) => left.length - right.length)[0];
if (!candidate) {
continue;
}
for (const axis of candidate) {
pushUnique(result, axis);
}
}
return result;
}
function planAssistantMcpDiscovery(input) {
const recipe = recipeFor(input);
const budgetOverride = budgetOverrideFor(input, recipe);
@@ -1145,7 +1204,11 @@ function planAssistantMcpDiscovery(input) {
plannerStatus,
semanticDataNeed,
dataNeedGraph,
discoveryPlan: plan
discoveryPlan: plan,
additionalMissingAxes: missingAxesFromCatalogReview(adjustedReview, {
providedAxes: providedAxesFromPlanTurnMeaning(plan),
preferredAxes: preferredClarificationAxesForRecipe({ recipe, dataNeedGraph })
})
});
return {
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PLANNER_SCHEMA_VERSION,
@@ -185,8 +185,13 @@ function routeCandidateNextAction(status) {
}
function buildRouteCandidate(planner, pilot, bridgeStatus) {
const plannerClarificationGaps = planner.discovery_plan.clarification_gaps ?? [];
const providedAxes = flattenAxes(pilot, "provided_axes");
const missingAxes = plannerClarificationGaps.length > 0 ? plannerClarificationGaps : flattenAxes(pilot, "missing_axis_options");
const evidenceAxes = planner.evidence_plan.evidence_axes;
const providedAxes = uniqueStrings([...evidenceAxes.provided_axes, ...flattenAxes(pilot, "provided_axes")]);
const missingAxes = plannerClarificationGaps.length > 0
? plannerClarificationGaps
: evidenceAxes.user_actionable_missing_axes.length > 0
? evidenceAxes.user_actionable_missing_axes
: flattenAxes(pilot, "missing_axis_options");
const missingProofFamily = routeCandidateMissingProofFamily(planner, pilot);
const candidateStatus = routeCandidateStatusFor(bridgeStatus, pilot, missingProofFamily);
return {
@@ -197,9 +202,12 @@ function buildRouteCandidate(planner, pilot, bridgeStatus) {
selected_chain_summary: planner.selected_chain_summary,
nearest_catalog_chain_template: planner.catalog_chain_template_alignment.top_chain_template_match,
catalog_alignment_status: planner.catalog_chain_template_alignment.alignment_status,
business_fact_family: planner.data_need_graph?.business_fact_family ?? null,
action_family: planner.data_need_graph?.action_family ?? null,
proof_expectation: planner.data_need_graph?.proof_expectation ?? null,
business_fact_family: planner.evidence_plan.data_need.business_fact_family,
action_family: planner.evidence_plan.data_need.action_family,
proof_expectation: planner.evidence_plan.data_need.proof_expectation,
evidence_plan_status: planner.evidence_plan.planner_status,
evidence_answer_mode: planner.evidence_plan.answer_contract.answer_mode,
evidence_expected_coverage: planner.evidence_plan.coverage_gate.expected_coverage,
required_axes: [...planner.required_axes],
provided_axes: providedAxes,
missing_axes: missingAxes,
@@ -207,7 +215,7 @@ function buildRouteCandidate(planner, pilot, bridgeStatus) {
enablement_reason: routeCandidateEnablementReason(candidateStatus, pilot, missingAxes, missingProofFamily),
recommended_next_action: routeCandidateNextAction(candidateStatus),
forbidden_overclaim_flags: uniqueStrings([
...(planner.data_need_graph?.forbidden_overclaim_flags ?? []),
...planner.evidence_plan.answer_contract.forbidden_overclaim_flags,
...(missingProofFamily ? [missingProofFamily.must_not_claim] : [])
])
};