ARCH: удержать open-scope ranking через org clarification и year-switch

This commit is contained in:
2026-04-22 22:30:33 +03:00
parent aa0e4ec37e
commit 70325dacb6
18 changed files with 538 additions and 29 deletions
@@ -142,6 +142,14 @@ function readAssistantMcpDiscoveryTurnMeaning(
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(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
@@ -248,6 +256,13 @@ export function readAssistantMcpDiscoveryPilotScope(
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(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
@@ -326,6 +326,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
const rawUtterance = lower(input.rawUtterance);
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
@@ -339,7 +340,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
});
const aggregationNeed = aggregationNeedFor(aggregationAxis);
const comparisonNeed = comparisonNeedFor(action);
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance);
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance) ?? seededRankingNeed;
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
const openScopeWithoutSubject =
subjectCandidates.length === 0 &&
@@ -359,9 +360,6 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
subjectCandidates.length === 0 &&
businessFactFamily === "value_flow" &&
openScopeWithoutSubject &&
!rankingNeed &&
!comparisonNeed &&
oneSidedOpenScopeTotalHint &&
!explicitOrganizationScope
) {
pushUnique(clarificationGaps, "organization");
@@ -24,6 +24,7 @@ export interface AssistantMcpDiscoveryTurnMeaningRef {
asked_domain_family?: string | null;
asked_action_family?: string | null;
asked_aggregation_axis?: string | null;
seeded_ranking_need?: string | null;
explicit_entity_candidates?: string[];
metadata_ambiguity_entity_sets?: string[];
explicit_organization_scope?: string | null;
@@ -167,6 +168,7 @@ function normalizeTurnMeaning(
const domain = toNonEmptyString(value.asked_domain_family);
const action = toNonEmptyString(value.asked_action_family);
const aggregationAxis = toNonEmptyString(value.asked_aggregation_axis);
const seededRankingNeed = toNonEmptyString(value.seeded_ranking_need);
const organization = toNonEmptyString(value.explicit_organization_scope);
const dateScope = toNonEmptyString(value.explicit_date_scope);
const unsupported = toNonEmptyString(value.unsupported_but_understood_family);
@@ -181,6 +183,9 @@ function normalizeTurnMeaning(
if (aggregationAxis) {
result.asked_aggregation_axis = aggregationAxis;
}
if (seededRankingNeed) {
result.seeded_ranking_need = seededRankingNeed;
}
if (entities.length > 0) {
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 {
const directShape = toRecordObject(input.addressRuntimeMeta?.answer_shape_contract);
if (directShape) {
@@ -335,6 +358,12 @@ function hasSemanticConflictWithDiscoveryTurnMeaning(
if (!detectedIntent || (!askedDomain && !askedAction && !unsupportedFamily)) {
return false;
}
if (isOpenScopeValueFlowRanking(entryPoint)) {
return true;
}
if (needsOpenScopeValueFlowOrganizationClarification(entryPoint)) {
return true;
}
if (
detectedIntent === "customer_revenue_and_payments" &&
isOpenScopeValueFlowWithoutSubject(entryPoint)
@@ -317,6 +317,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
discoveryEntity: string | null;
entityResolutionStatus: string | null;
entityResolutionAmbiguityCandidates: string[];
rankingNeed: string | null;
organization: string | null;
dateScope: string | null;
metadataRouteFamily: string | null;
@@ -365,6 +366,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
entityResolutionStatus,
entityResolutionAmbiguityCandidates,
rankingNeed: toNonEmptyString(followupContext?.previous_discovery_ranking_need),
organization,
dateScope,
metadataRouteFamily: toNonEmptyString(followupContext?.previous_discovery_metadata_route_family),
@@ -819,15 +821,13 @@ export function buildAssistantMcpDiscoveryTurnInput(
const assistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
const explicitOrganizationScopeSignal = Boolean(
rawOrganizationMentionSignal &&
(rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope)
);
const currentTurnOrganizationScope =
rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope;
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope);
const organizationClarificationFollowupApplicable = Boolean(
followupSeed.domain === "counterparty_value" &&
!followupSeed.counterparty &&
rawOrganizationMentionSignal &&
(rawOrganizationScope || followupSeed.organization) &&
currentTurnOrganizationScope &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal
@@ -1150,6 +1150,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
hasValueRankingSignal(rawText) ||
rawOpenScopeValueFlowOrganizationSignal ||
explicitOrganizationScopeSignal ||
organizationClarificationFollowupApplicable ||
followupSeed.organization
);
if (openScopeValueFlowWithoutCounterparty && !valueFlowOrganizationStaysScope) {
@@ -1158,10 +1159,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
}
const explicitOrganizationScope =
valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
? rawOrganizationScope ??
predecomposeEntities.organization ??
assistantTurnMeaningOrganizationScope ??
followupSeed.organization
? currentTurnOrganizationScope ?? followupSeed.organization
: null;
if (valueFlowOrganizationStaysScope && explicitOrganizationScope) {
for (let index = entityCandidates.length - 1; index >= 0; index -= 1) {
@@ -1205,6 +1203,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
? metadataActionFromRawText(rawText) ?? seededAction
: rawAction ?? seededAction,
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
seeded_ranking_need:
valueFlowSignal && followupSeed.rankingNeed ? followupSeed.rankingNeed : undefined,
explicit_entity_candidates: entityCandidates,
metadata_ambiguity_entity_sets:
metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
@@ -1260,6 +1260,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
if (toNonEmptyString(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) {
cleanTurnMeaning.explicit_entity_candidates = turnMeaning.explicit_entity_candidates;
}
@@ -17,6 +17,7 @@ import {
readAssistantMcpDiscoveryEntityResolutionStatus,
readAssistantMcpDiscoveryMetadataRouteFamily,
readAssistantMcpDiscoveryMetadataSelectedEntitySet,
readAssistantMcpDiscoveryRankingNeed,
readAddressDebugTemporalScope,
readAssistantMcpDiscoveryPilotScope,
resolveOrganizationClarificationContinuation,
@@ -683,6 +684,10 @@ export function createAssistantTransitionPolicy(deps) {
carryoverSourceDebug,
deps.toNonEmptyString
);
const sourceDiscoveryRankingNeed = readAssistantMcpDiscoveryRankingNeed(
carryoverSourceDebug,
deps.toNonEmptyString
);
const sourceDiscoveryEntityAmbiguityCandidates = readAssistantMcpDiscoveryEntityAmbiguityCandidates(
carryoverSourceDebug,
deps.toNonEmptyString
@@ -1026,6 +1031,7 @@ export function createAssistantTransitionPolicy(deps) {
previous_discovery_entity_resolution_status: sourceDiscoveryEntityResolutionStatus ?? undefined,
previous_discovery_entity_candidates:
sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
previous_discovery_entity_ambiguity_candidates:
sourceDiscoveryEntityAmbiguityCandidates.length > 0
? sourceDiscoveryEntityAmbiguityCandidates