ARCH: удержать open-scope ranking через org clarification и year-switch
This commit is contained in:
parent
aa0e4ec37e
commit
70325dacb6
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"schema_version": "domain_truth_harness_spec_v1",
|
||||||
|
"scenario_id": "address_truth_harness_phase38_open_scope_net_org_clarification",
|
||||||
|
"domain": "address_phase38_open_scope_net_org_clarification",
|
||||||
|
"title": "Phase 38 open-scope net organization clarification",
|
||||||
|
"description": "Targeted AGENT replay for Big Block D where an open-scope net money-flow question must ask for organization, then resume the same bidirectional contour after an organization-only clarification and preserve it across a short year switch.",
|
||||||
|
"bindings": {},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "step_01_open_scope_net_requires_organization",
|
||||||
|
"title": "Generic net value-flow question asks for organization first",
|
||||||
|
"question": "Какое нетто по деньгам за 2020 год: сколько получили и сколько заплатили?",
|
||||||
|
"allowed_reply_types": ["clarification_required", "partial_coverage"],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)уточн|нужно",
|
||||||
|
"(?i)организац"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)уточните контрагента",
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)по какому контрагенту",
|
||||||
|
"(?i)не найдено контрагента"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": ["value_flow_net", "open_scope", "organization_clarification", "bounded_autonomy"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_02_org_clarification_resumes_net",
|
||||||
|
"title": "Organization-only clarification resumes the same net contour for 2020",
|
||||||
|
"question": "по ООО Альтернатива Плюс",
|
||||||
|
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)2020",
|
||||||
|
"(?i)получ|входящ|поступ",
|
||||||
|
"(?i)заплат|исходящ|списан|платеж",
|
||||||
|
"(?i)нетто|сальдо|разниц",
|
||||||
|
"(?i)руб"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)альтернатива",
|
||||||
|
"(?i)проверенн|найденн"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)уточните контрагента",
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)по какому контрагенту",
|
||||||
|
"(?i)не найдено контрагента"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": ["value_flow_net", "organization_followup_reuse", "bounded_autonomy"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_03_year_switch_reuses_org_and_net",
|
||||||
|
"title": "Short year-switch follow-up keeps the same organization and bidirectional net contour",
|
||||||
|
"question": "а за 2021?",
|
||||||
|
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)2021",
|
||||||
|
"(?i)получ|входящ|поступ",
|
||||||
|
"(?i)заплат|исходящ|списан|платеж",
|
||||||
|
"(?i)нетто|сальдо|разниц",
|
||||||
|
"(?i)руб"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)альтернатива",
|
||||||
|
"(?i)проверенн|найденн"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)уточните контрагента",
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)по какому контрагенту",
|
||||||
|
"(?i)не найдено контрагента",
|
||||||
|
"(?i)уточните организацию"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": ["value_flow_net", "year_switch", "organization_followup_reuse", "bounded_autonomy"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
{
|
||||||
|
"schema_version": "domain_truth_harness_spec_v1",
|
||||||
|
"scenario_id": "address_truth_harness_phase39_open_scope_ranking_org_clarification",
|
||||||
|
"domain": "address_phase39_open_scope_ranking_org_clarification",
|
||||||
|
"title": "Phase 39 open-scope ranking organization clarification",
|
||||||
|
"description": "Targeted AGENT replay for Big Block D where an open-scope top-value-flow question must ask for organization, then resume the same ranking contour after an organization-only clarification and preserve it across a short year switch.",
|
||||||
|
"bindings": {},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "step_01_open_scope_ranking_requires_organization",
|
||||||
|
"title": "Generic top customer question asks for organization before ranking",
|
||||||
|
"question": "Кто больше всего принес денег за 2020 год?",
|
||||||
|
"allowed_reply_types": ["clarification_required", "partial_coverage"],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)уточн|нужно",
|
||||||
|
"(?i)организац"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)уточните контрагента",
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)по какому контрагенту",
|
||||||
|
"(?i)не найдено контрагента"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": ["value_flow_ranking", "open_scope", "organization_clarification", "bounded_autonomy"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_02_org_clarification_resumes_ranking",
|
||||||
|
"title": "Organization-only clarification resumes the same ranking contour for 2020",
|
||||||
|
"question": "по ООО Альтернатива Плюс",
|
||||||
|
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)2020",
|
||||||
|
"(?i)клиент|контрагент|заказчик",
|
||||||
|
"(?i)руб"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)больше всего|топ|самый доходный|наибол",
|
||||||
|
"(?i)альтернатива",
|
||||||
|
"(?i)проверенн|найденн"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)уточните контрагента",
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)по какому контрагенту",
|
||||||
|
"(?i)не найдено контрагента"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": ["value_flow_ranking", "organization_followup_reuse", "bounded_autonomy"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_03_year_switch_reuses_org_and_ranking",
|
||||||
|
"title": "Short year-switch follow-up keeps the same organization and ranking contour",
|
||||||
|
"question": "а за 2021?",
|
||||||
|
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)2021",
|
||||||
|
"(?i)клиент|контрагент|заказчик",
|
||||||
|
"(?i)руб"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)больше всего|топ|самый доходный|наибол",
|
||||||
|
"(?i)альтернатива",
|
||||||
|
"(?i)проверенн|найденн"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)уточните контрагента",
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)по какому контрагенту",
|
||||||
|
"(?i)не найдено контрагента",
|
||||||
|
"(?i)уточните организацию"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": ["value_flow_ranking", "year_switch", "organization_followup_reuse", "bounded_autonomy"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ exports.readAssistantMcpDiscoveryEntityResolutionStatus = readAssistantMcpDiscov
|
||||||
exports.readAssistantMcpDiscoveryEntityAmbiguityCandidates = readAssistantMcpDiscoveryEntityAmbiguityCandidates;
|
exports.readAssistantMcpDiscoveryEntityAmbiguityCandidates = readAssistantMcpDiscoveryEntityAmbiguityCandidates;
|
||||||
exports.readAssistantMcpDiscoveryEntityCandidates = readAssistantMcpDiscoveryEntityCandidates;
|
exports.readAssistantMcpDiscoveryEntityCandidates = readAssistantMcpDiscoveryEntityCandidates;
|
||||||
exports.readAssistantMcpDiscoveryPilotScope = readAssistantMcpDiscoveryPilotScope;
|
exports.readAssistantMcpDiscoveryPilotScope = readAssistantMcpDiscoveryPilotScope;
|
||||||
|
exports.readAssistantMcpDiscoveryRankingNeed = readAssistantMcpDiscoveryRankingNeed;
|
||||||
exports.readAssistantMcpDiscoveryMetadataRouteFamily = readAssistantMcpDiscoveryMetadataRouteFamily;
|
exports.readAssistantMcpDiscoveryMetadataRouteFamily = readAssistantMcpDiscoveryMetadataRouteFamily;
|
||||||
exports.readAssistantMcpDiscoveryMetadataSelectedEntitySet = readAssistantMcpDiscoveryMetadataSelectedEntitySet;
|
exports.readAssistantMcpDiscoveryMetadataSelectedEntitySet = readAssistantMcpDiscoveryMetadataSelectedEntitySet;
|
||||||
exports.readAssistantMcpDiscoveryMetadataAmbiguityDetected = readAssistantMcpDiscoveryMetadataAmbiguityDetected;
|
exports.readAssistantMcpDiscoveryMetadataAmbiguityDetected = readAssistantMcpDiscoveryMetadataAmbiguityDetected;
|
||||||
|
|
@ -76,6 +77,11 @@ function readAssistantMcpDiscoveryTurnMeaning(debug) {
|
||||||
const turnInput = toRecordObject(entry?.turn_input);
|
const turnInput = toRecordObject(entry?.turn_input);
|
||||||
return toRecordObject(turnInput?.turn_meaning_ref);
|
return toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
}
|
}
|
||||||
|
function readAssistantMcpDiscoveryDataNeedGraph(debug) {
|
||||||
|
const entry = readAssistantMcpDiscoveryEntry(debug);
|
||||||
|
const turnInput = toRecordObject(entry?.turn_input);
|
||||||
|
return toRecordObject(turnInput?.data_need_graph);
|
||||||
|
}
|
||||||
function readAssistantMcpDiscoveryTurnMeaningMetadataAmbiguityEntitySets(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
function readAssistantMcpDiscoveryTurnMeaningMetadataAmbiguityEntitySets(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
const values = readAssistantMcpDiscoveryTurnMeaning(debug)?.metadata_ambiguity_entity_sets;
|
const values = readAssistantMcpDiscoveryTurnMeaning(debug)?.metadata_ambiguity_entity_sets;
|
||||||
if (!Array.isArray(values)) {
|
if (!Array.isArray(values)) {
|
||||||
|
|
@ -142,6 +148,9 @@ function readAssistantMcpDiscoveryPilotScope(debug, toNonEmptyString = fallbackT
|
||||||
const pilot = toRecordObject(bridge?.pilot);
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
return toNonEmptyString(pilot?.pilot_scope);
|
return toNonEmptyString(pilot?.pilot_scope);
|
||||||
}
|
}
|
||||||
|
function readAssistantMcpDiscoveryRankingNeed(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.ranking_need);
|
||||||
|
}
|
||||||
function readAssistantMcpDiscoveryMetadataRouteFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
function readAssistantMcpDiscoveryMetadataRouteFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
return toNonEmptyString(readAssistantMcpDiscoveryDerivedMetadataSurface(debug)?.downstream_route_family);
|
return toNonEmptyString(readAssistantMcpDiscoveryDerivedMetadataSurface(debug)?.downstream_route_family);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
||||||
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
||||||
const rawUtterance = lower(input.rawUtterance);
|
const rawUtterance = lower(input.rawUtterance);
|
||||||
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
|
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
|
||||||
|
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
||||||
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
||||||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||||
|
|
@ -252,7 +253,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
||||||
});
|
});
|
||||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||||
const comparisonNeed = comparisonNeedFor(action);
|
const comparisonNeed = comparisonNeedFor(action);
|
||||||
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance);
|
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance) ?? seededRankingNeed;
|
||||||
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
||||||
const openScopeWithoutSubject = subjectCandidates.length === 0 &&
|
const openScopeWithoutSubject = subjectCandidates.length === 0 &&
|
||||||
allowsOpenScopeWithoutSubject({
|
allowsOpenScopeWithoutSubject({
|
||||||
|
|
@ -270,9 +271,6 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
||||||
if (subjectCandidates.length === 0 &&
|
if (subjectCandidates.length === 0 &&
|
||||||
businessFactFamily === "value_flow" &&
|
businessFactFamily === "value_flow" &&
|
||||||
openScopeWithoutSubject &&
|
openScopeWithoutSubject &&
|
||||||
!rankingNeed &&
|
|
||||||
!comparisonNeed &&
|
|
||||||
oneSidedOpenScopeTotalHint &&
|
|
||||||
!explicitOrganizationScope) {
|
!explicitOrganizationScope) {
|
||||||
pushUnique(clarificationGaps, "organization");
|
pushUnique(clarificationGaps, "organization");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ function normalizeTurnMeaning(value) {
|
||||||
const domain = toNonEmptyString(value.asked_domain_family);
|
const domain = toNonEmptyString(value.asked_domain_family);
|
||||||
const action = toNonEmptyString(value.asked_action_family);
|
const action = toNonEmptyString(value.asked_action_family);
|
||||||
const aggregationAxis = toNonEmptyString(value.asked_aggregation_axis);
|
const aggregationAxis = toNonEmptyString(value.asked_aggregation_axis);
|
||||||
|
const seededRankingNeed = toNonEmptyString(value.seeded_ranking_need);
|
||||||
const organization = toNonEmptyString(value.explicit_organization_scope);
|
const organization = toNonEmptyString(value.explicit_organization_scope);
|
||||||
const dateScope = toNonEmptyString(value.explicit_date_scope);
|
const dateScope = toNonEmptyString(value.explicit_date_scope);
|
||||||
const unsupported = toNonEmptyString(value.unsupported_but_understood_family);
|
const unsupported = toNonEmptyString(value.unsupported_but_understood_family);
|
||||||
|
|
@ -89,6 +90,9 @@ function normalizeTurnMeaning(value) {
|
||||||
if (aggregationAxis) {
|
if (aggregationAxis) {
|
||||||
result.asked_aggregation_axis = aggregationAxis;
|
result.asked_aggregation_axis = aggregationAxis;
|
||||||
}
|
}
|
||||||
|
if (seededRankingNeed) {
|
||||||
|
result.seeded_ranking_need = seededRankingNeed;
|
||||||
|
}
|
||||||
if (entities.length > 0) {
|
if (entities.length > 0) {
|
||||||
result.explicit_entity_candidates = entities;
|
result.explicit_entity_candidates = entities;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,21 @@ function isOpenScopeValueFlowWithoutSubject(entryPoint) {
|
||||||
subjectCandidates.length === 0 &&
|
subjectCandidates.length === 0 &&
|
||||||
reasonCodes.some((reason) => toNonEmptyString(reason) === "data_need_graph_open_scope_total_without_subject"));
|
reasonCodes.some((reason) => toNonEmptyString(reason) === "data_need_graph_open_scope_total_without_subject"));
|
||||||
}
|
}
|
||||||
|
function needsOpenScopeValueFlowOrganizationClarification(entryPoint) {
|
||||||
|
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||||
|
const businessFactFamily = toNonEmptyString(graph?.business_fact_family);
|
||||||
|
const subjectCandidates = Array.isArray(graph?.subject_candidates) ? graph.subject_candidates : [];
|
||||||
|
const clarificationGaps = Array.isArray(graph?.clarification_gaps) ? graph.clarification_gaps : [];
|
||||||
|
return (businessFactFamily === "value_flow" &&
|
||||||
|
subjectCandidates.length === 0 &&
|
||||||
|
clarificationGaps.some((gap) => toNonEmptyString(gap) === "organization"));
|
||||||
|
}
|
||||||
|
function isOpenScopeValueFlowRanking(entryPoint) {
|
||||||
|
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||||
|
const businessFactFamily = toNonEmptyString(graph?.business_fact_family);
|
||||||
|
const subjectCandidates = Array.isArray(graph?.subject_candidates) ? graph.subject_candidates : [];
|
||||||
|
return businessFactFamily === "value_flow" && subjectCandidates.length === 0 && Boolean(toNonEmptyString(graph?.ranking_need));
|
||||||
|
}
|
||||||
function readTruthAnswerShape(input) {
|
function readTruthAnswerShape(input) {
|
||||||
const directShape = toRecordObject(input.addressRuntimeMeta?.answer_shape_contract);
|
const directShape = toRecordObject(input.addressRuntimeMeta?.answer_shape_contract);
|
||||||
if (directShape) {
|
if (directShape) {
|
||||||
|
|
@ -234,6 +249,12 @@ function hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint) {
|
||||||
if (!detectedIntent || (!askedDomain && !askedAction && !unsupportedFamily)) {
|
if (!detectedIntent || (!askedDomain && !askedAction && !unsupportedFamily)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (isOpenScopeValueFlowRanking(entryPoint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (needsOpenScopeValueFlowOrganizationClarification(entryPoint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (detectedIntent === "customer_revenue_and_payments" &&
|
if (detectedIntent === "customer_revenue_and_payments" &&
|
||||||
isOpenScopeValueFlowWithoutSubject(entryPoint)) {
|
isOpenScopeValueFlowWithoutSubject(entryPoint)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,7 @@ function collectFollowupDiscoverySeed(followupContext) {
|
||||||
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
||||||
entityResolutionStatus,
|
entityResolutionStatus,
|
||||||
entityResolutionAmbiguityCandidates,
|
entityResolutionAmbiguityCandidates,
|
||||||
|
rankingNeed: toNonEmptyString(followupContext?.previous_discovery_ranking_need),
|
||||||
organization,
|
organization,
|
||||||
dateScope,
|
dateScope,
|
||||||
metadataRouteFamily: toNonEmptyString(followupContext?.previous_discovery_metadata_route_family),
|
metadataRouteFamily: toNonEmptyString(followupContext?.previous_discovery_metadata_route_family),
|
||||||
|
|
@ -603,12 +604,11 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
const assistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
|
const assistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
|
||||||
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
||||||
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
||||||
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal &&
|
const currentTurnOrganizationScope = rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope;
|
||||||
(rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope));
|
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope);
|
||||||
const organizationClarificationFollowupApplicable = Boolean(followupSeed.domain === "counterparty_value" &&
|
const organizationClarificationFollowupApplicable = Boolean(followupSeed.domain === "counterparty_value" &&
|
||||||
!followupSeed.counterparty &&
|
!followupSeed.counterparty &&
|
||||||
rawOrganizationMentionSignal &&
|
currentTurnOrganizationScope &&
|
||||||
(rawOrganizationScope || followupSeed.organization) &&
|
|
||||||
!rawLifecycleSignal &&
|
!rawLifecycleSignal &&
|
||||||
!rawValueFlowSignal &&
|
!rawValueFlowSignal &&
|
||||||
!rawMetadataSignal);
|
!rawMetadataSignal);
|
||||||
|
|
@ -873,16 +873,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
hasValueRankingSignal(rawText) ||
|
hasValueRankingSignal(rawText) ||
|
||||||
rawOpenScopeValueFlowOrganizationSignal ||
|
rawOpenScopeValueFlowOrganizationSignal ||
|
||||||
explicitOrganizationScopeSignal ||
|
explicitOrganizationScopeSignal ||
|
||||||
|
organizationClarificationFollowupApplicable ||
|
||||||
followupSeed.organization);
|
followupSeed.organization);
|
||||||
if (openScopeValueFlowWithoutCounterparty && !valueFlowOrganizationStaysScope) {
|
if (openScopeValueFlowWithoutCounterparty && !valueFlowOrganizationStaysScope) {
|
||||||
pushUnique(entityCandidates, predecomposeEntities.organization);
|
pushUnique(entityCandidates, predecomposeEntities.organization);
|
||||||
pushUnique(entityCandidates, followupSeed.organization);
|
pushUnique(entityCandidates, followupSeed.organization);
|
||||||
}
|
}
|
||||||
const explicitOrganizationScope = valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
|
const explicitOrganizationScope = valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
|
||||||
? rawOrganizationScope ??
|
? currentTurnOrganizationScope ?? followupSeed.organization
|
||||||
predecomposeEntities.organization ??
|
|
||||||
assistantTurnMeaningOrganizationScope ??
|
|
||||||
followupSeed.organization
|
|
||||||
: null;
|
: null;
|
||||||
if (valueFlowOrganizationStaysScope && explicitOrganizationScope) {
|
if (valueFlowOrganizationStaysScope && explicitOrganizationScope) {
|
||||||
for (let index = entityCandidates.length - 1; index >= 0; index -= 1) {
|
for (let index = entityCandidates.length - 1; index >= 0; index -= 1) {
|
||||||
|
|
@ -924,6 +922,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
? metadataActionFromRawText(rawText) ?? seededAction
|
? metadataActionFromRawText(rawText) ?? seededAction
|
||||||
: rawAction ?? seededAction,
|
: rawAction ?? seededAction,
|
||||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||||
|
seeded_ranking_need: valueFlowSignal && followupSeed.rankingNeed ? followupSeed.rankingNeed : undefined,
|
||||||
explicit_entity_candidates: entityCandidates,
|
explicit_entity_candidates: entityCandidates,
|
||||||
metadata_ambiguity_entity_sets: metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
metadata_ambiguity_entity_sets: metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
||||||
? followupSeed.metadataAmbiguityEntitySets
|
? followupSeed.metadataAmbiguityEntitySets
|
||||||
|
|
@ -974,6 +973,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
if (toNonEmptyString(turnMeaning.asked_aggregation_axis)) {
|
if (toNonEmptyString(turnMeaning.asked_aggregation_axis)) {
|
||||||
cleanTurnMeaning.asked_aggregation_axis = turnMeaning.asked_aggregation_axis;
|
cleanTurnMeaning.asked_aggregation_axis = turnMeaning.asked_aggregation_axis;
|
||||||
}
|
}
|
||||||
|
if (toNonEmptyString(turnMeaning.seeded_ranking_need)) {
|
||||||
|
cleanTurnMeaning.seeded_ranking_need = turnMeaning.seeded_ranking_need;
|
||||||
|
}
|
||||||
if ((turnMeaning.explicit_entity_candidates?.length ?? 0) > 0) {
|
if ((turnMeaning.explicit_entity_candidates?.length ?? 0) > 0) {
|
||||||
cleanTurnMeaning.explicit_entity_candidates = turnMeaning.explicit_entity_candidates;
|
cleanTurnMeaning.explicit_entity_candidates = turnMeaning.explicit_entity_candidates;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -505,6 +505,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
const sourceDiscoveryMetadataAmbiguityEntitySets = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataAmbiguityEntitySets)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryMetadataAmbiguityEntitySets = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataAmbiguityEntitySets)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryEntityResolutionStatus = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityResolutionStatus)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryEntityResolutionStatus = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityResolutionStatus)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryEntityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryEntityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryRankingNeed = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryRankingNeed)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryEntityAmbiguityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityAmbiguityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryEntityAmbiguityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityAmbiguityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||||
const llmSelectedObjectScopeDetected = llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
const llmSelectedObjectScopeDetected = llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
||||||
|
|
@ -742,6 +743,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
previous_discovery_pilot_scope: sourceDiscoveryPilotScope ?? undefined,
|
previous_discovery_pilot_scope: sourceDiscoveryPilotScope ?? undefined,
|
||||||
previous_discovery_entity_resolution_status: sourceDiscoveryEntityResolutionStatus ?? undefined,
|
previous_discovery_entity_resolution_status: sourceDiscoveryEntityResolutionStatus ?? undefined,
|
||||||
previous_discovery_entity_candidates: sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
previous_discovery_entity_candidates: sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
||||||
|
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
|
||||||
previous_discovery_entity_ambiguity_candidates: sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
previous_discovery_entity_ambiguity_candidates: sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
||||||
? sourceDiscoveryEntityAmbiguityCandidates
|
? sourceDiscoveryEntityAmbiguityCandidates
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,14 @@ function readAssistantMcpDiscoveryTurnMeaning(
|
||||||
return toRecordObject(turnInput?.turn_meaning_ref);
|
return toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readAssistantMcpDiscoveryDataNeedGraph(
|
||||||
|
debug: Record<string, unknown> | null
|
||||||
|
): Record<string, unknown> | null {
|
||||||
|
const entry = readAssistantMcpDiscoveryEntry(debug);
|
||||||
|
const turnInput = toRecordObject(entry?.turn_input);
|
||||||
|
return toRecordObject(turnInput?.data_need_graph);
|
||||||
|
}
|
||||||
|
|
||||||
function readAssistantMcpDiscoveryTurnMeaningMetadataAmbiguityEntitySets(
|
function readAssistantMcpDiscoveryTurnMeaningMetadataAmbiguityEntitySets(
|
||||||
debug: Record<string, unknown> | null,
|
debug: Record<string, unknown> | null,
|
||||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
|
@ -248,6 +256,13 @@ export function readAssistantMcpDiscoveryPilotScope(
|
||||||
return toNonEmptyString(pilot?.pilot_scope);
|
return toNonEmptyString(pilot?.pilot_scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readAssistantMcpDiscoveryRankingNeed(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string | null {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.ranking_need);
|
||||||
|
}
|
||||||
|
|
||||||
export function readAssistantMcpDiscoveryMetadataRouteFamily(
|
export function readAssistantMcpDiscoveryMetadataRouteFamily(
|
||||||
debug: Record<string, unknown> | null,
|
debug: Record<string, unknown> | null,
|
||||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
|
|
||||||
|
|
@ -326,6 +326,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||||
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
||||||
const rawUtterance = lower(input.rawUtterance);
|
const rawUtterance = lower(input.rawUtterance);
|
||||||
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
|
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
|
||||||
|
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
||||||
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
||||||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||||
|
|
@ -339,7 +340,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||||
});
|
});
|
||||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||||
const comparisonNeed = comparisonNeedFor(action);
|
const comparisonNeed = comparisonNeedFor(action);
|
||||||
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance);
|
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance) ?? seededRankingNeed;
|
||||||
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
||||||
const openScopeWithoutSubject =
|
const openScopeWithoutSubject =
|
||||||
subjectCandidates.length === 0 &&
|
subjectCandidates.length === 0 &&
|
||||||
|
|
@ -359,9 +360,6 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||||
subjectCandidates.length === 0 &&
|
subjectCandidates.length === 0 &&
|
||||||
businessFactFamily === "value_flow" &&
|
businessFactFamily === "value_flow" &&
|
||||||
openScopeWithoutSubject &&
|
openScopeWithoutSubject &&
|
||||||
!rankingNeed &&
|
|
||||||
!comparisonNeed &&
|
|
||||||
oneSidedOpenScopeTotalHint &&
|
|
||||||
!explicitOrganizationScope
|
!explicitOrganizationScope
|
||||||
) {
|
) {
|
||||||
pushUnique(clarificationGaps, "organization");
|
pushUnique(clarificationGaps, "organization");
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ export interface AssistantMcpDiscoveryTurnMeaningRef {
|
||||||
asked_domain_family?: string | null;
|
asked_domain_family?: string | null;
|
||||||
asked_action_family?: string | null;
|
asked_action_family?: string | null;
|
||||||
asked_aggregation_axis?: string | null;
|
asked_aggregation_axis?: string | null;
|
||||||
|
seeded_ranking_need?: string | null;
|
||||||
explicit_entity_candidates?: string[];
|
explicit_entity_candidates?: string[];
|
||||||
metadata_ambiguity_entity_sets?: string[];
|
metadata_ambiguity_entity_sets?: string[];
|
||||||
explicit_organization_scope?: string | null;
|
explicit_organization_scope?: string | null;
|
||||||
|
|
@ -167,6 +168,7 @@ function normalizeTurnMeaning(
|
||||||
const domain = toNonEmptyString(value.asked_domain_family);
|
const domain = toNonEmptyString(value.asked_domain_family);
|
||||||
const action = toNonEmptyString(value.asked_action_family);
|
const action = toNonEmptyString(value.asked_action_family);
|
||||||
const aggregationAxis = toNonEmptyString(value.asked_aggregation_axis);
|
const aggregationAxis = toNonEmptyString(value.asked_aggregation_axis);
|
||||||
|
const seededRankingNeed = toNonEmptyString(value.seeded_ranking_need);
|
||||||
const organization = toNonEmptyString(value.explicit_organization_scope);
|
const organization = toNonEmptyString(value.explicit_organization_scope);
|
||||||
const dateScope = toNonEmptyString(value.explicit_date_scope);
|
const dateScope = toNonEmptyString(value.explicit_date_scope);
|
||||||
const unsupported = toNonEmptyString(value.unsupported_but_understood_family);
|
const unsupported = toNonEmptyString(value.unsupported_but_understood_family);
|
||||||
|
|
@ -181,6 +183,9 @@ function normalizeTurnMeaning(
|
||||||
if (aggregationAxis) {
|
if (aggregationAxis) {
|
||||||
result.asked_aggregation_axis = aggregationAxis;
|
result.asked_aggregation_axis = aggregationAxis;
|
||||||
}
|
}
|
||||||
|
if (seededRankingNeed) {
|
||||||
|
result.seeded_ranking_need = seededRankingNeed;
|
||||||
|
}
|
||||||
if (entities.length > 0) {
|
if (entities.length > 0) {
|
||||||
result.explicit_entity_candidates = entities;
|
result.explicit_entity_candidates = entities;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,29 @@ function isOpenScopeValueFlowWithoutSubject(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function needsOpenScopeValueFlowOrganizationClarification(
|
||||||
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||||
|
): boolean {
|
||||||
|
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||||
|
const businessFactFamily = toNonEmptyString(graph?.business_fact_family);
|
||||||
|
const subjectCandidates = Array.isArray(graph?.subject_candidates) ? graph.subject_candidates : [];
|
||||||
|
const clarificationGaps = Array.isArray(graph?.clarification_gaps) ? graph.clarification_gaps : [];
|
||||||
|
return (
|
||||||
|
businessFactFamily === "value_flow" &&
|
||||||
|
subjectCandidates.length === 0 &&
|
||||||
|
clarificationGaps.some((gap) => toNonEmptyString(gap) === "organization")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOpenScopeValueFlowRanking(
|
||||||
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||||
|
): boolean {
|
||||||
|
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||||
|
const businessFactFamily = toNonEmptyString(graph?.business_fact_family);
|
||||||
|
const subjectCandidates = Array.isArray(graph?.subject_candidates) ? graph.subject_candidates : [];
|
||||||
|
return businessFactFamily === "value_flow" && subjectCandidates.length === 0 && Boolean(toNonEmptyString(graph?.ranking_need));
|
||||||
|
}
|
||||||
|
|
||||||
function readTruthAnswerShape(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): Record<string, unknown> | null {
|
function readTruthAnswerShape(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): Record<string, unknown> | null {
|
||||||
const directShape = toRecordObject(input.addressRuntimeMeta?.answer_shape_contract);
|
const directShape = toRecordObject(input.addressRuntimeMeta?.answer_shape_contract);
|
||||||
if (directShape) {
|
if (directShape) {
|
||||||
|
|
@ -335,6 +358,12 @@ function hasSemanticConflictWithDiscoveryTurnMeaning(
|
||||||
if (!detectedIntent || (!askedDomain && !askedAction && !unsupportedFamily)) {
|
if (!detectedIntent || (!askedDomain && !askedAction && !unsupportedFamily)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (isOpenScopeValueFlowRanking(entryPoint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (needsOpenScopeValueFlowOrganizationClarification(entryPoint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
detectedIntent === "customer_revenue_and_payments" &&
|
detectedIntent === "customer_revenue_and_payments" &&
|
||||||
isOpenScopeValueFlowWithoutSubject(entryPoint)
|
isOpenScopeValueFlowWithoutSubject(entryPoint)
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
discoveryEntity: string | null;
|
discoveryEntity: string | null;
|
||||||
entityResolutionStatus: string | null;
|
entityResolutionStatus: string | null;
|
||||||
entityResolutionAmbiguityCandidates: string[];
|
entityResolutionAmbiguityCandidates: string[];
|
||||||
|
rankingNeed: string | null;
|
||||||
organization: string | null;
|
organization: string | null;
|
||||||
dateScope: string | null;
|
dateScope: string | null;
|
||||||
metadataRouteFamily: string | null;
|
metadataRouteFamily: string | null;
|
||||||
|
|
@ -365,6 +366,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
||||||
entityResolutionStatus,
|
entityResolutionStatus,
|
||||||
entityResolutionAmbiguityCandidates,
|
entityResolutionAmbiguityCandidates,
|
||||||
|
rankingNeed: toNonEmptyString(followupContext?.previous_discovery_ranking_need),
|
||||||
organization,
|
organization,
|
||||||
dateScope,
|
dateScope,
|
||||||
metadataRouteFamily: toNonEmptyString(followupContext?.previous_discovery_metadata_route_family),
|
metadataRouteFamily: toNonEmptyString(followupContext?.previous_discovery_metadata_route_family),
|
||||||
|
|
@ -819,15 +821,13 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
const assistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
|
const assistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
|
||||||
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
||||||
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
||||||
const explicitOrganizationScopeSignal = Boolean(
|
const currentTurnOrganizationScope =
|
||||||
rawOrganizationMentionSignal &&
|
rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope;
|
||||||
(rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope)
|
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope);
|
||||||
);
|
|
||||||
const organizationClarificationFollowupApplicable = Boolean(
|
const organizationClarificationFollowupApplicable = Boolean(
|
||||||
followupSeed.domain === "counterparty_value" &&
|
followupSeed.domain === "counterparty_value" &&
|
||||||
!followupSeed.counterparty &&
|
!followupSeed.counterparty &&
|
||||||
rawOrganizationMentionSignal &&
|
currentTurnOrganizationScope &&
|
||||||
(rawOrganizationScope || followupSeed.organization) &&
|
|
||||||
!rawLifecycleSignal &&
|
!rawLifecycleSignal &&
|
||||||
!rawValueFlowSignal &&
|
!rawValueFlowSignal &&
|
||||||
!rawMetadataSignal
|
!rawMetadataSignal
|
||||||
|
|
@ -1150,6 +1150,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
hasValueRankingSignal(rawText) ||
|
hasValueRankingSignal(rawText) ||
|
||||||
rawOpenScopeValueFlowOrganizationSignal ||
|
rawOpenScopeValueFlowOrganizationSignal ||
|
||||||
explicitOrganizationScopeSignal ||
|
explicitOrganizationScopeSignal ||
|
||||||
|
organizationClarificationFollowupApplicable ||
|
||||||
followupSeed.organization
|
followupSeed.organization
|
||||||
);
|
);
|
||||||
if (openScopeValueFlowWithoutCounterparty && !valueFlowOrganizationStaysScope) {
|
if (openScopeValueFlowWithoutCounterparty && !valueFlowOrganizationStaysScope) {
|
||||||
|
|
@ -1158,10 +1159,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
}
|
}
|
||||||
const explicitOrganizationScope =
|
const explicitOrganizationScope =
|
||||||
valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
|
valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
|
||||||
? rawOrganizationScope ??
|
? currentTurnOrganizationScope ?? followupSeed.organization
|
||||||
predecomposeEntities.organization ??
|
|
||||||
assistantTurnMeaningOrganizationScope ??
|
|
||||||
followupSeed.organization
|
|
||||||
: null;
|
: null;
|
||||||
if (valueFlowOrganizationStaysScope && explicitOrganizationScope) {
|
if (valueFlowOrganizationStaysScope && explicitOrganizationScope) {
|
||||||
for (let index = entityCandidates.length - 1; index >= 0; index -= 1) {
|
for (let index = entityCandidates.length - 1; index >= 0; index -= 1) {
|
||||||
|
|
@ -1205,6 +1203,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
? metadataActionFromRawText(rawText) ?? seededAction
|
? metadataActionFromRawText(rawText) ?? seededAction
|
||||||
: rawAction ?? seededAction,
|
: rawAction ?? seededAction,
|
||||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||||
|
seeded_ranking_need:
|
||||||
|
valueFlowSignal && followupSeed.rankingNeed ? followupSeed.rankingNeed : undefined,
|
||||||
explicit_entity_candidates: entityCandidates,
|
explicit_entity_candidates: entityCandidates,
|
||||||
metadata_ambiguity_entity_sets:
|
metadata_ambiguity_entity_sets:
|
||||||
metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
||||||
|
|
@ -1260,6 +1260,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
if (toNonEmptyString(turnMeaning.asked_aggregation_axis)) {
|
if (toNonEmptyString(turnMeaning.asked_aggregation_axis)) {
|
||||||
cleanTurnMeaning.asked_aggregation_axis = turnMeaning.asked_aggregation_axis;
|
cleanTurnMeaning.asked_aggregation_axis = turnMeaning.asked_aggregation_axis;
|
||||||
}
|
}
|
||||||
|
if (toNonEmptyString(turnMeaning.seeded_ranking_need)) {
|
||||||
|
cleanTurnMeaning.seeded_ranking_need = turnMeaning.seeded_ranking_need;
|
||||||
|
}
|
||||||
if ((turnMeaning.explicit_entity_candidates?.length ?? 0) > 0) {
|
if ((turnMeaning.explicit_entity_candidates?.length ?? 0) > 0) {
|
||||||
cleanTurnMeaning.explicit_entity_candidates = turnMeaning.explicit_entity_candidates;
|
cleanTurnMeaning.explicit_entity_candidates = turnMeaning.explicit_entity_candidates;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import {
|
||||||
readAssistantMcpDiscoveryEntityResolutionStatus,
|
readAssistantMcpDiscoveryEntityResolutionStatus,
|
||||||
readAssistantMcpDiscoveryMetadataRouteFamily,
|
readAssistantMcpDiscoveryMetadataRouteFamily,
|
||||||
readAssistantMcpDiscoveryMetadataSelectedEntitySet,
|
readAssistantMcpDiscoveryMetadataSelectedEntitySet,
|
||||||
|
readAssistantMcpDiscoveryRankingNeed,
|
||||||
readAddressDebugTemporalScope,
|
readAddressDebugTemporalScope,
|
||||||
readAssistantMcpDiscoveryPilotScope,
|
readAssistantMcpDiscoveryPilotScope,
|
||||||
resolveOrganizationClarificationContinuation,
|
resolveOrganizationClarificationContinuation,
|
||||||
|
|
@ -683,6 +684,10 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
carryoverSourceDebug,
|
carryoverSourceDebug,
|
||||||
deps.toNonEmptyString
|
deps.toNonEmptyString
|
||||||
);
|
);
|
||||||
|
const sourceDiscoveryRankingNeed = readAssistantMcpDiscoveryRankingNeed(
|
||||||
|
carryoverSourceDebug,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
);
|
||||||
const sourceDiscoveryEntityAmbiguityCandidates = readAssistantMcpDiscoveryEntityAmbiguityCandidates(
|
const sourceDiscoveryEntityAmbiguityCandidates = readAssistantMcpDiscoveryEntityAmbiguityCandidates(
|
||||||
carryoverSourceDebug,
|
carryoverSourceDebug,
|
||||||
deps.toNonEmptyString
|
deps.toNonEmptyString
|
||||||
|
|
@ -1026,6 +1031,7 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
previous_discovery_entity_resolution_status: sourceDiscoveryEntityResolutionStatus ?? undefined,
|
previous_discovery_entity_resolution_status: sourceDiscoveryEntityResolutionStatus ?? undefined,
|
||||||
previous_discovery_entity_candidates:
|
previous_discovery_entity_candidates:
|
||||||
sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
||||||
|
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
|
||||||
previous_discovery_entity_ambiguity_candidates:
|
previous_discovery_entity_ambiguity_candidates:
|
||||||
sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
||||||
? sourceDiscoveryEntityAmbiguityCandidates
|
? sourceDiscoveryEntityAmbiguityCandidates
|
||||||
|
|
|
||||||
|
|
@ -84,13 +84,34 @@ describe("assistant MCP discovery data need graph", () => {
|
||||||
|
|
||||||
expect(result.business_fact_family).toBe("value_flow");
|
expect(result.business_fact_family).toBe("value_flow");
|
||||||
expect(result.ranking_need).toBe("top_desc");
|
expect(result.ranking_need).toBe("top_desc");
|
||||||
expect(result.clarification_gaps).toEqual([]);
|
expect(result.clarification_gaps).toEqual(["organization"]);
|
||||||
|
expect(result.proof_expectation).toBe("clarification_required");
|
||||||
expect(result.decomposition_candidates).toEqual([
|
expect(result.decomposition_candidates).toEqual([
|
||||||
"collect_scoped_movements",
|
"collect_scoped_movements",
|
||||||
"aggregate_ranked_axis_values",
|
"aggregate_ranked_axis_values",
|
||||||
"probe_coverage"
|
"probe_coverage"
|
||||||
]);
|
]);
|
||||||
expect(result.reason_codes).toContain("data_need_graph_ranking_top_desc");
|
expect(result.reason_codes).toContain("data_need_graph_ranking_top_desc");
|
||||||
|
expect(result.reason_codes).toContain("data_need_graph_open_scope_total_needs_organization");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps organization-scoped ranking executable when the ranking axis comes from follow-up context", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||||
|
semanticDataNeed: "counterparty value-flow evidence",
|
||||||
|
rawUtterance: "по ООО Альтернатива Плюс",
|
||||||
|
turnMeaning: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
seeded_ranking_need: "top_desc"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.business_fact_family).toBe("value_flow");
|
||||||
|
expect(result.ranking_need).toBe("top_desc");
|
||||||
|
expect(result.clarification_gaps).toEqual([]);
|
||||||
|
expect(result.proof_expectation).toBe("coverage_checked_fact");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("treats incoming-vs-outgoing comparison as an open-scope value need rather than a missing-subject fact ask", () => {
|
it("treats incoming-vs-outgoing comparison as an open-scope value need rather than a missing-subject fact ask", () => {
|
||||||
|
|
@ -106,7 +127,7 @@ describe("assistant MCP discovery data need graph", () => {
|
||||||
|
|
||||||
expect(result.business_fact_family).toBe("value_flow");
|
expect(result.business_fact_family).toBe("value_flow");
|
||||||
expect(result.comparison_need).toBe("incoming_vs_outgoing");
|
expect(result.comparison_need).toBe("incoming_vs_outgoing");
|
||||||
expect(result.clarification_gaps).toEqual([]);
|
expect(result.clarification_gaps).toEqual(["organization"]);
|
||||||
expect(result.decomposition_candidates).toEqual([
|
expect(result.decomposition_candidates).toEqual([
|
||||||
"collect_incoming_movements",
|
"collect_incoming_movements",
|
||||||
"collect_outgoing_movements",
|
"collect_outgoing_movements",
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,121 @@ describe("assistant MCP discovery response policy", () => {
|
||||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_keep_factual_address_continuation_target");
|
expect(result.reason_codes).toContain("mcp_discovery_response_policy_keep_factual_address_continuation_target");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("overrides an exact ranking-shaped address reply when open-scope ranking still needs organization", () => {
|
||||||
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
|
currentReply:
|
||||||
|
"Самый доходный год по подтвержденным поступлениям: 2020 (15 744 052,48 ₽ по 20 операциям).",
|
||||||
|
currentReplySource: "address_query_runtime_v1",
|
||||||
|
currentReplyType: "factual",
|
||||||
|
addressRuntimeMeta: {
|
||||||
|
detected_intent: "customer_revenue_and_payments",
|
||||||
|
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
should_run_discovery: true,
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "value_flow",
|
||||||
|
subject_candidates: [],
|
||||||
|
ranking_need: "top_desc",
|
||||||
|
clarification_gaps: ["organization"],
|
||||||
|
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||||
|
},
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
seeded_ranking_need: "top_desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
user_facing_response_allowed: true,
|
||||||
|
business_fact_answer_allowed: false,
|
||||||
|
requires_user_clarification: true,
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "needs_clarification",
|
||||||
|
headline: "Нужно уточнить организацию.",
|
||||||
|
confirmed_lines: [],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: ["Без организации поиск по контрагентам не запустить."],
|
||||||
|
limitation_lines: [],
|
||||||
|
next_step_line: "Уточните организацию, и я продолжу поиск по контрагентам."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.applied).toBe(true);
|
||||||
|
expect(result.decision).toBe("apply_candidate");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_aligned_factual_address_reply");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_factual_address_continuation_target");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_full_confirmed_factual_address_reply");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overrides an exact ranking-shaped address reply when bounded open-scope ranking already has organization and period", () => {
|
||||||
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
|
currentReply:
|
||||||
|
"Самый доходный клиент за доступное время по подтвержденным поступлениям: Группа СВК (12 224 925,00 ₽ по 16 операциям).",
|
||||||
|
currentReplySource: "address_query_runtime_v1",
|
||||||
|
currentReplyType: "factual",
|
||||||
|
addressRuntimeMeta: {
|
||||||
|
detected_intent: "customer_revenue_and_payments",
|
||||||
|
dialogContinuationContract: {
|
||||||
|
target_intent: "customer_revenue_and_payments"
|
||||||
|
},
|
||||||
|
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
should_run_discovery: true,
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "value_flow",
|
||||||
|
subject_candidates: [],
|
||||||
|
ranking_need: "top_desc",
|
||||||
|
clarification_gaps: [],
|
||||||
|
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||||
|
},
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
seeded_ranking_need: "top_desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
user_facing_response_allowed: true,
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
requires_user_clarification: false,
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference",
|
||||||
|
headline: "Рейтинг по контрагентам построен по подтвержденным строкам 1С.",
|
||||||
|
confirmed_lines: [
|
||||||
|
"Больше всего денег принёс контрагент СБЕРБАНК, ПАО по организации ООО Альтернатива Плюс за период 2020: 12 792 194,31 руб. по 9 строкам с суммой."
|
||||||
|
],
|
||||||
|
inference_lines: [
|
||||||
|
"Рейтинг по контрагентам по организации ООО Альтернатива Плюс за период 2020 рассчитан только по подтвержденным строкам 1С."
|
||||||
|
],
|
||||||
|
unknown_lines: ["Полный исторический рейтинг вне проверенного окна не доказан."],
|
||||||
|
limitation_lines: [],
|
||||||
|
next_step_line: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.applied).toBe(true);
|
||||||
|
expect(result.decision).toBe("apply_candidate");
|
||||||
|
expect(result.reply_text).toContain("ООО Альтернатива Плюс");
|
||||||
|
expect(result.reply_text).toContain("2020");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_aligned_factual_address_reply");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_factual_address_continuation_target");
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps full-confirmed factual address replies even when discovery has a guarded candidate", () => {
|
it("keeps full-confirmed factual address replies even when discovery has a guarded candidate", () => {
|
||||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
currentReply: "ООО Ромашка | сумма: 128000 | операций: 3",
|
currentReply: "ООО Ромашка | сумма: 128000 | операций: 3",
|
||||||
|
|
|
||||||
|
|
@ -1273,7 +1273,7 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||||
expect(result.data_need_graph?.business_fact_family).toBe("value_flow");
|
expect(result.data_need_graph?.business_fact_family).toBe("value_flow");
|
||||||
expect(result.data_need_graph?.ranking_need).toBe("top_desc");
|
expect(result.data_need_graph?.ranking_need).toBe("top_desc");
|
||||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
expect(result.data_need_graph?.clarification_gaps).toEqual(["organization"]);
|
||||||
expect(result.data_need_graph?.decomposition_candidates).toEqual([
|
expect(result.data_need_graph?.decomposition_candidates).toEqual([
|
||||||
"collect_scoped_movements",
|
"collect_scoped_movements",
|
||||||
"aggregate_ranked_axis_values",
|
"aggregate_ranked_axis_values",
|
||||||
|
|
@ -1476,6 +1476,73 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.reason_codes).toContain("mcp_discovery_seeded_from_followup_context");
|
expect(result.reason_codes).toContain("mcp_discovery_seeded_from_followup_context");
|
||||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("resumes an open-scope ranking from follow-up context when the user clarifies only the organization", () => {
|
||||||
|
const orgName = "ООО Альтернатива Плюс";
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "по ООО Альтернатива Плюс",
|
||||||
|
predecomposeContract: {
|
||||||
|
entities: { organization: orgName }
|
||||||
|
},
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
|
||||||
|
previous_discovery_ranking_need: "top_desc",
|
||||||
|
previous_filters: {
|
||||||
|
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.source_signal).toBe("followup_context");
|
||||||
|
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
seeded_ranking_need: "top_desc",
|
||||||
|
explicit_organization_scope: orgName,
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||||
|
expect(result.data_need_graph?.ranking_need).toBe("top_desc");
|
||||||
|
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps seeded ranking through a year-switch follow-up after organization clarification", () => {
|
||||||
|
const orgName = "ООО Альтернатива Плюс";
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "Р° Р·Р° 2021?",
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
|
||||||
|
previous_discovery_ranking_need: "top_desc",
|
||||||
|
previous_filters: {
|
||||||
|
organization: orgName,
|
||||||
|
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.source_signal).toBe("followup_context");
|
||||||
|
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
seeded_ranking_need: "top_desc",
|
||||||
|
explicit_organization_scope: orgName,
|
||||||
|
explicit_date_scope: "2021",
|
||||||
|
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.data_need_graph?.ranking_need).toBe("top_desc");
|
||||||
|
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||||
|
});
|
||||||
it("forces discovery over a supported exact intent when organization-only follow-up resolves an open-scope total", () => {
|
it("forces discovery over a supported exact intent when organization-only follow-up resolves an open-scope total", () => {
|
||||||
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
|
|
||||||
|
|
@ -1274,6 +1274,64 @@ describe("assistantTransitionPolicy", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("carries ranking need from grounded discovery into followup context", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: () => null,
|
||||||
|
hasAddressFollowupContextSignal: () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
"по ООО Альтернатива Плюс",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
text: "Нужно уточнить организацию, чтобы продолжить поиск по контрагентам.",
|
||||||
|
debug: {
|
||||||
|
execution_lane: "living_chat",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
turn_input: {
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "value_flow",
|
||||||
|
ranking_need: "top_desc",
|
||||||
|
subject_candidates: [],
|
||||||
|
clarification_gaps: ["organization"]
|
||||||
|
},
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
seeded_ranking_need: "top_desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
business_fact_answer_allowed: false,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "counterparty_value_flow_query_movements_v1"
|
||||||
|
},
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "needs_clarification"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe(
|
||||||
|
"counterparty_value_flow_query_movements_v1"
|
||||||
|
);
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_ranking_need).toBe("top_desc");
|
||||||
|
expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments");
|
||||||
|
});
|
||||||
|
|
||||||
it("carries grounded metadata downstream route hints into followup context", () => {
|
it("carries grounded metadata downstream route hints into followup context", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
findLastAddressAssistantItem: () => null,
|
findLastAddressAssistantItem: () => null,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue