ARCH: ввести broad business evaluation bridge
This commit is contained in:
@@ -284,6 +284,7 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
|
||||
const mcpDiscoveryResponsePolicy = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: guardedResponse.assistantReply,
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: guardedResponse.replyType,
|
||||
addressRuntimeMeta: debugWithResponseGuard
|
||||
});
|
||||
const finalAssistantReply = mcpDiscoveryResponsePolicy.applied
|
||||
|
||||
@@ -285,7 +285,7 @@ export async function buildAssistantAddressOrchestrationRuntime(
|
||||
);
|
||||
}
|
||||
|
||||
const followupContext = carryover?.followupContext ?? null;
|
||||
const followupContext = toRecordObject(carryover?.followupContext);
|
||||
const routePolicyRuntime = runAssistantRoutePolicyRuntime({
|
||||
rawUserMessage: input.userMessage,
|
||||
effectiveAddressUserMessage: addressInputMessage,
|
||||
@@ -313,7 +313,8 @@ export async function buildAssistantAddressOrchestrationRuntime(
|
||||
userMessage: input.userMessage,
|
||||
effectiveMessage: addressInputMessage,
|
||||
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
||||
predecomposeContract
|
||||
predecomposeContract,
|
||||
followupContext
|
||||
})) as Record<string, unknown>;
|
||||
} catch (error) {
|
||||
mcpDiscoveryRuntimeEntryPointError = String(error instanceof Error ? error.message : error ?? "unknown_error").slice(0, 280);
|
||||
|
||||
@@ -108,6 +108,23 @@ function toRecordObject(value: unknown): Record<string, unknown> | null {
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function candidateValue(value: unknown): string | null {
|
||||
const direct = fallbackToNonEmptyString(value);
|
||||
if (direct && direct !== "[object Object]") {
|
||||
return direct;
|
||||
}
|
||||
const record = toRecordObject(value);
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
fallbackToNonEmptyString(record.value) ??
|
||||
fallbackToNonEmptyString(record.name) ??
|
||||
fallbackToNonEmptyString(record.ref) ??
|
||||
fallbackToNonEmptyString(record.text)
|
||||
);
|
||||
}
|
||||
|
||||
function readAssistantMcpDiscoveryEntry(
|
||||
debug: Record<string, unknown> | null
|
||||
): Record<string, unknown> | null {
|
||||
@@ -125,6 +142,13 @@ function readAssistantMcpDiscoveryTurnMeaning(
|
||||
return toRecordObject(turnInput?.turn_meaning_ref);
|
||||
}
|
||||
|
||||
function readAssistantMcpDiscoveryActionFamily(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
): string | null {
|
||||
return toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.asked_action_family);
|
||||
}
|
||||
|
||||
function readAssistantMcpDiscoveryBridge(
|
||||
debug: Record<string, unknown> | null
|
||||
): Record<string, unknown> | null {
|
||||
@@ -140,6 +164,94 @@ export function readAssistantMcpDiscoveryPilotScope(
|
||||
return toNonEmptyString(pilot?.pilot_scope);
|
||||
}
|
||||
|
||||
function mapAssistantMcpDiscoveryPilotScopeToAddressIntent(
|
||||
pilotScope: string | null,
|
||||
actionFamily: string | null
|
||||
): string | null {
|
||||
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||||
return "counterparty_activity_lifecycle";
|
||||
}
|
||||
if (pilotScope === "counterparty_supplier_payout_query_movements_v1") {
|
||||
return "supplier_payouts_profile";
|
||||
}
|
||||
if (pilotScope === "counterparty_value_flow_query_movements_v1") {
|
||||
return "customer_revenue_and_payments";
|
||||
}
|
||||
if (pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1") {
|
||||
return null;
|
||||
}
|
||||
if (actionFamily === "activity_duration") {
|
||||
return "counterparty_activity_lifecycle";
|
||||
}
|
||||
if (actionFamily === "payout") {
|
||||
return "supplier_payouts_profile";
|
||||
}
|
||||
if (actionFamily === "turnover") {
|
||||
return "customer_revenue_and_payments";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readDiscoveryDateScopeFilters(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
): {
|
||||
asOfDate: string | null;
|
||||
periodFrom: string | null;
|
||||
periodTo: string | null;
|
||||
} {
|
||||
const explicitDateScope = toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.explicit_date_scope);
|
||||
if (!explicitDateScope) {
|
||||
return {
|
||||
asOfDate: null,
|
||||
periodFrom: null,
|
||||
periodTo: null
|
||||
};
|
||||
}
|
||||
const isoDateMatch = explicitDateScope.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||||
if (isoDateMatch) {
|
||||
return {
|
||||
asOfDate: explicitDateScope,
|
||||
periodFrom: null,
|
||||
periodTo: null
|
||||
};
|
||||
}
|
||||
const monthMatch = explicitDateScope.match(/^(\d{4})-(\d{2})$/);
|
||||
if (monthMatch) {
|
||||
const year = Number(monthMatch[1]);
|
||||
const month = Number(monthMatch[2]);
|
||||
if (Number.isFinite(year) && Number.isFinite(month) && month >= 1 && month <= 12) {
|
||||
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
||||
return {
|
||||
asOfDate: null,
|
||||
periodFrom: `${monthMatch[1]}-${monthMatch[2]}-01`,
|
||||
periodTo: `${monthMatch[1]}-${monthMatch[2]}-${String(lastDay).padStart(2, "0")}`
|
||||
};
|
||||
}
|
||||
}
|
||||
const yearMatch = explicitDateScope.match(/^(\d{4})$/);
|
||||
if (yearMatch) {
|
||||
return {
|
||||
asOfDate: null,
|
||||
periodFrom: `${yearMatch[1]}-01-01`,
|
||||
periodTo: `${yearMatch[1]}-12-31`
|
||||
};
|
||||
}
|
||||
const rangeMatch = explicitDateScope.match(/^(\d{4}-\d{2}-\d{2})\.\.(\d{4}-\d{2}-\d{2})$/);
|
||||
if (rangeMatch) {
|
||||
return {
|
||||
asOfDate: null,
|
||||
periodFrom: rangeMatch[1],
|
||||
periodTo: rangeMatch[2]
|
||||
};
|
||||
}
|
||||
return {
|
||||
asOfDate: null,
|
||||
periodFrom: null,
|
||||
periodTo: null
|
||||
};
|
||||
}
|
||||
|
||||
function formatDiscoveryDateScopeForReply(value: unknown): string | null {
|
||||
const text = fallbackToNonEmptyString(value);
|
||||
if (!text) {
|
||||
@@ -220,7 +332,7 @@ export function readAddressDebugCounterparty(
|
||||
? discoveryMeaning?.explicit_entity_candidates
|
||||
: [];
|
||||
for (const entity of explicitEntities) {
|
||||
const text = toNonEmptyString(entity);
|
||||
const text = candidateValue(entity);
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
@@ -228,6 +340,20 @@ export function readAddressDebugCounterparty(
|
||||
return null;
|
||||
}
|
||||
|
||||
export function readAddressDebugIntent(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
): string | null {
|
||||
const detectedIntent = toNonEmptyString(debug?.detected_intent);
|
||||
if (detectedIntent && detectedIntent !== "unknown") {
|
||||
return detectedIntent;
|
||||
}
|
||||
return mapAssistantMcpDiscoveryPilotScopeToAddressIntent(
|
||||
readAssistantMcpDiscoveryPilotScope(debug, toNonEmptyString),
|
||||
readAssistantMcpDiscoveryActionFamily(debug, toNonEmptyString)
|
||||
);
|
||||
}
|
||||
|
||||
export function readAddressDebugOrganization(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
@@ -261,10 +387,20 @@ export function readAddressDebugTemporalScope(
|
||||
): AssistantAddressDebugTemporalScope {
|
||||
const extractedFilters = readAddressDebugFilters(debug);
|
||||
const rootFrameContext = toRecordObject(debug?.address_root_frame_context);
|
||||
const discoveryDateScope = readDiscoveryDateScopeFilters(debug, toNonEmptyString);
|
||||
return {
|
||||
asOfDate: toNonEmptyString(extractedFilters?.as_of_date) ?? toNonEmptyString(rootFrameContext?.as_of_date),
|
||||
periodFrom: toNonEmptyString(extractedFilters?.period_from) ?? toNonEmptyString(rootFrameContext?.period_from),
|
||||
periodTo: toNonEmptyString(extractedFilters?.period_to) ?? toNonEmptyString(rootFrameContext?.period_to)
|
||||
asOfDate:
|
||||
toNonEmptyString(extractedFilters?.as_of_date) ??
|
||||
toNonEmptyString(rootFrameContext?.as_of_date) ??
|
||||
discoveryDateScope.asOfDate,
|
||||
periodFrom:
|
||||
toNonEmptyString(extractedFilters?.period_from) ??
|
||||
toNonEmptyString(rootFrameContext?.period_from) ??
|
||||
discoveryDateScope.periodFrom,
|
||||
periodTo:
|
||||
toNonEmptyString(extractedFilters?.period_to) ??
|
||||
toNonEmptyString(rootFrameContext?.period_to) ??
|
||||
discoveryDateScope.periodTo
|
||||
};
|
||||
}
|
||||
|
||||
@@ -364,6 +500,24 @@ export function resolveAddressDebugCarryoverFilters(
|
||||
): Record<string, unknown> {
|
||||
const extractedFilters = readAddressDebugFilters(debug);
|
||||
const nextFilters = extractedFilters ? { ...extractedFilters } : {};
|
||||
const discoveryDateScope = readDiscoveryDateScopeFilters(debug, toNonEmptyString);
|
||||
const counterparty = readAddressDebugCounterparty(debug, toNonEmptyString);
|
||||
const organization = readAddressDebugOrganization(debug, toNonEmptyString);
|
||||
if (counterparty && !toNonEmptyString(nextFilters.counterparty)) {
|
||||
nextFilters.counterparty = counterparty;
|
||||
}
|
||||
if (organization && !toNonEmptyString(nextFilters.organization)) {
|
||||
nextFilters.organization = organization;
|
||||
}
|
||||
if (discoveryDateScope.asOfDate && !toNonEmptyString(nextFilters.as_of_date)) {
|
||||
nextFilters.as_of_date = discoveryDateScope.asOfDate;
|
||||
}
|
||||
if (discoveryDateScope.periodFrom && !toNonEmptyString(nextFilters.period_from)) {
|
||||
nextFilters.period_from = discoveryDateScope.periodFrom;
|
||||
}
|
||||
if (discoveryDateScope.periodTo && !toNonEmptyString(nextFilters.period_to)) {
|
||||
nextFilters.period_to = discoveryDateScope.periodTo;
|
||||
}
|
||||
const inventoryRootFrame = buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString);
|
||||
const rootFilters =
|
||||
inventoryRootFrame?.filters && typeof inventoryRootFrame.filters === "object"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
buildAddressMemoryRecapReply as buildAddressMemoryRecapReplyFromPolicy,
|
||||
buildBroadBusinessEvaluationReply as buildBroadBusinessEvaluationReplyFromPolicy,
|
||||
buildSelectedObjectAnswerInspectionReply as buildSelectedObjectAnswerInspectionReplyFromPolicy,
|
||||
buildInventoryHistoryCapabilityFollowupReply as buildInventoryHistoryCapabilityFollowupReplyFromPolicy,
|
||||
resolveAssistantLivingChatMemoryContext
|
||||
@@ -191,10 +192,26 @@ export async function runAssistantLivingChatRuntime(
|
||||
? "deterministic_data_scope_contract_live"
|
||||
: "deterministic_data_scope_contract";
|
||||
} else if (unsupportedCurrentTurnMeaningBoundary) {
|
||||
chatText = buildUnsupportedCurrentTurnMeaningBoundaryReply({
|
||||
assistantTurnMeaning
|
||||
});
|
||||
livingChatSource = "deterministic_unsupported_current_turn_boundary";
|
||||
const unsupportedFamily =
|
||||
typeof assistantTurnMeaning?.unsupported_but_understood_family === "string"
|
||||
? assistantTurnMeaning.unsupported_but_understood_family
|
||||
: null;
|
||||
if (unsupportedFamily === "broad_business_evaluation") {
|
||||
const scopedOrganization = selectedOrganization ?? activeOrganization ?? continuityActiveOrganization ?? null;
|
||||
chatText = buildBroadBusinessEvaluationReplyFromPolicy({
|
||||
organization: scopedOrganization,
|
||||
addressDebug: continuitySnapshot.lastGroundedAddressDebug,
|
||||
sessionItems: input.sessionItems,
|
||||
toNonEmptyString: input.toNonEmptyString
|
||||
});
|
||||
activeOrganization = scopedOrganization ?? activeOrganization;
|
||||
livingChatSource = "deterministic_broad_business_evaluation_contract";
|
||||
} else {
|
||||
chatText = buildUnsupportedCurrentTurnMeaningBoundaryReply({
|
||||
assistantTurnMeaning
|
||||
});
|
||||
livingChatSource = "deterministic_unsupported_current_turn_boundary";
|
||||
}
|
||||
} else if ((selectedOrganization || activeOrganization) && input.hasOrganizationFactLookupSignal(userMessage)) {
|
||||
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
||||
chatText = input.buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
|
||||
|
||||
@@ -111,6 +111,13 @@ function isUnsupportedCurrentTurnBoundary(input: ApplyAssistantMcpDiscoveryRespo
|
||||
);
|
||||
}
|
||||
|
||||
function isDeterministicBroadBusinessEvaluationReply(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
|
||||
return (
|
||||
input.livingChatSource === "deterministic_broad_business_evaluation_contract" ||
|
||||
input.currentReplySource === "deterministic_broad_business_evaluation_contract"
|
||||
);
|
||||
}
|
||||
|
||||
function isDiscoveryReadyChatCandidate(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||
@@ -295,6 +302,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(entryPoint);
|
||||
const reasonCodes = [...candidate.reason_codes];
|
||||
const unsupportedBoundary = isUnsupportedCurrentTurnBoundary(input);
|
||||
const deterministicBroadBusinessEvaluationReply = isDeterministicBroadBusinessEvaluationReply(input);
|
||||
const discoveryReadyChatCandidate = isDiscoveryReadyChatCandidate(input, entryPoint);
|
||||
const discoveryReadyDeepCandidate = isDiscoveryReadyDeepCandidate(input, entryPoint);
|
||||
const discoveryReadyAddressCandidate = isDiscoveryReadyAddressCandidate(input, entryPoint);
|
||||
@@ -330,6 +338,12 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||
if (fullConfirmedFactualAddressReply) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_full_confirmed_factual_address_reply");
|
||||
}
|
||||
if (deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") {
|
||||
pushReason(
|
||||
reasonCodes,
|
||||
"mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate"
|
||||
);
|
||||
}
|
||||
if (!ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status)) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_candidate_status_not_allowed");
|
||||
}
|
||||
@@ -349,6 +363,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||
!alignedFactualAddressReply &&
|
||||
!matchedFactualAddressContinuationTarget &&
|
||||
!fullConfirmedFactualAddressReply &&
|
||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||
candidate.eligible_for_future_hot_runtime &&
|
||||
Boolean(toNonEmptyString(candidate.reply_text)) &&
|
||||
|
||||
@@ -7,12 +7,14 @@ export type AssistantMcpDiscoveryTurnInputStatus = "ready" | "needs_more_context
|
||||
export type AssistantMcpDiscoveryTurnInputSource =
|
||||
| "assistant_turn_meaning"
|
||||
| "predecompose_contract"
|
||||
| "followup_context"
|
||||
| "raw_text"
|
||||
| "none";
|
||||
|
||||
export interface BuildAssistantMcpDiscoveryTurnInputAdapterInput {
|
||||
assistantTurnMeaning?: Record<string, unknown> | null;
|
||||
predecomposeContract?: Record<string, unknown> | null;
|
||||
followupContext?: Record<string, unknown> | null;
|
||||
userMessage?: string | null;
|
||||
effectiveMessage?: string | null;
|
||||
}
|
||||
@@ -132,6 +134,148 @@ function collectDateScope(predecompose: Record<string, unknown> | null): string
|
||||
return periodFrom ?? periodTo ?? null;
|
||||
}
|
||||
|
||||
function collectDateScopeFromFilters(filters: Record<string, unknown> | null): string | null {
|
||||
if (!filters) {
|
||||
return null;
|
||||
}
|
||||
const asOfDate = toNonEmptyString(filters.as_of_date);
|
||||
const periodFrom = toNonEmptyString(filters.period_from);
|
||||
const periodTo = toNonEmptyString(filters.period_to);
|
||||
if (asOfDate) {
|
||||
return asOfDate;
|
||||
}
|
||||
const yearFrom = periodFrom?.match(/^(\d{4})-01-01$/);
|
||||
const yearTo = periodTo?.match(/^(\d{4})-12-31$/);
|
||||
if (yearFrom && yearTo && yearFrom[1] === yearTo[1]) {
|
||||
return yearFrom[1];
|
||||
}
|
||||
if (periodFrom && periodTo) {
|
||||
return `${periodFrom}..${periodTo}`;
|
||||
}
|
||||
return periodFrom ?? periodTo ?? null;
|
||||
}
|
||||
|
||||
function mapPilotScopeToFollowupMeaning(
|
||||
pilotScope: string | null
|
||||
): {
|
||||
domain: string | null;
|
||||
action: string | null;
|
||||
unsupported: string | null;
|
||||
} {
|
||||
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||||
return {
|
||||
domain: "counterparty_lifecycle",
|
||||
action: "activity_duration",
|
||||
unsupported: "counterparty_lifecycle"
|
||||
};
|
||||
}
|
||||
if (pilotScope === "counterparty_supplier_payout_query_movements_v1") {
|
||||
return {
|
||||
domain: "counterparty_value",
|
||||
action: "payout",
|
||||
unsupported: "counterparty_payouts_or_outflow"
|
||||
};
|
||||
}
|
||||
if (pilotScope === "counterparty_value_flow_query_movements_v1") {
|
||||
return {
|
||||
domain: "counterparty_value",
|
||||
action: "turnover",
|
||||
unsupported: "counterparty_value_or_turnover"
|
||||
};
|
||||
}
|
||||
if (pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1") {
|
||||
return {
|
||||
domain: "counterparty_value",
|
||||
action: "net_value_flow",
|
||||
unsupported: "counterparty_bidirectional_value_flow_or_netting"
|
||||
};
|
||||
}
|
||||
return {
|
||||
domain: null,
|
||||
action: null,
|
||||
unsupported: null
|
||||
};
|
||||
}
|
||||
|
||||
function mapAddressIntentToFollowupMeaning(
|
||||
intent: string | null
|
||||
): {
|
||||
domain: string | null;
|
||||
action: string | null;
|
||||
unsupported: string | null;
|
||||
} {
|
||||
if (intent === "counterparty_activity_lifecycle") {
|
||||
return {
|
||||
domain: "counterparty_lifecycle",
|
||||
action: "activity_duration",
|
||||
unsupported: "counterparty_lifecycle"
|
||||
};
|
||||
}
|
||||
if (intent === "supplier_payouts_profile") {
|
||||
return {
|
||||
domain: "counterparty_value",
|
||||
action: "payout",
|
||||
unsupported: "counterparty_payouts_or_outflow"
|
||||
};
|
||||
}
|
||||
if (intent === "customer_revenue_and_payments") {
|
||||
return {
|
||||
domain: "counterparty_value",
|
||||
action: "turnover",
|
||||
unsupported: "counterparty_value_or_turnover"
|
||||
};
|
||||
}
|
||||
return {
|
||||
domain: null,
|
||||
action: null,
|
||||
unsupported: null
|
||||
};
|
||||
}
|
||||
|
||||
function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> | null): {
|
||||
pilotScope: string | null;
|
||||
domain: string | null;
|
||||
action: string | null;
|
||||
unsupported: string | null;
|
||||
counterparty: string | null;
|
||||
organization: string | null;
|
||||
dateScope: string | null;
|
||||
} {
|
||||
const previousFilters = toRecordObject(followupContext?.previous_filters);
|
||||
const rootFilters = toRecordObject(followupContext?.root_filters);
|
||||
const pilotScope = toNonEmptyString(followupContext?.previous_discovery_pilot_scope);
|
||||
const previousIntent =
|
||||
toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
|
||||
const mapped =
|
||||
mapPilotScopeToFollowupMeaning(pilotScope).domain !== null
|
||||
? mapPilotScopeToFollowupMeaning(pilotScope)
|
||||
: mapAddressIntentToFollowupMeaning(previousIntent);
|
||||
const counterparty =
|
||||
toNonEmptyString(previousFilters?.counterparty) ??
|
||||
toNonEmptyString(rootFilters?.counterparty) ??
|
||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
||||
? toNonEmptyString(followupContext?.previous_anchor_value)
|
||||
: null);
|
||||
const organization =
|
||||
toNonEmptyString(previousFilters?.organization) ??
|
||||
toNonEmptyString(rootFilters?.organization) ??
|
||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "organization"
|
||||
? toNonEmptyString(followupContext?.previous_anchor_value)
|
||||
: null);
|
||||
const dateScope =
|
||||
collectDateScopeFromFilters(previousFilters) ??
|
||||
collectDateScopeFromFilters(rootFilters);
|
||||
return {
|
||||
pilotScope,
|
||||
domain: mapped.domain,
|
||||
action: mapped.action,
|
||||
unsupported: mapped.unsupported,
|
||||
counterparty,
|
||||
organization,
|
||||
dateScope
|
||||
};
|
||||
}
|
||||
|
||||
function hasLifecycleSignal(text: string): boolean {
|
||||
return /(?:сколько\s+лет|как\s+давно|давно\s+ли|возраст|перв(?:ая|ый)\s+актив|когда\s+начал|когда\s+появ|lifecycle|activity\s+duration|business\s+age|how\s+long)/iu.test(
|
||||
text
|
||||
@@ -162,6 +306,26 @@ function hasMonthlyAggregationSignal(text: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function hasExplicitDateScopeLiteral(text: string): boolean {
|
||||
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b)/iu.test(text);
|
||||
}
|
||||
|
||||
function collectDateScopeFromRawText(text: string): string | null {
|
||||
const isoDate = text.match(/\b(\d{4}-\d{2}-\d{2})\b/u);
|
||||
if (isoDate?.[1]) {
|
||||
return isoDate[1];
|
||||
}
|
||||
const yearMonth = text.match(/\b(\d{4}-\d{2})\b/u);
|
||||
if (yearMonth?.[1]) {
|
||||
return yearMonth[1];
|
||||
}
|
||||
const year = text.match(/\b((?:19|20)\d{2})\b/u);
|
||||
if (year?.[1]) {
|
||||
return year[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function semanticNeedFor(input: {
|
||||
domain: string | null;
|
||||
action: string | null;
|
||||
@@ -191,6 +355,7 @@ function shouldRunDiscovery(input: {
|
||||
valueFlowSignal: boolean;
|
||||
semanticDataNeed: string | null;
|
||||
explicitIntentCandidate: string | null;
|
||||
followupDiscoverySeedApplicable: boolean;
|
||||
}): boolean {
|
||||
if (input.lifecycleSignal || input.unsupported) {
|
||||
return true;
|
||||
@@ -198,6 +363,9 @@ function shouldRunDiscovery(input: {
|
||||
if (input.valueFlowSignal && !input.explicitIntentCandidate) {
|
||||
return true;
|
||||
}
|
||||
if (input.followupDiscoverySeedApplicable && !input.explicitIntentCandidate && input.semanticDataNeed) {
|
||||
return true;
|
||||
}
|
||||
if (!input.explicitIntentCandidate && input.semanticDataNeed) {
|
||||
return true;
|
||||
}
|
||||
@@ -209,37 +377,75 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
): AssistantMcpDiscoveryTurnInputContract {
|
||||
const assistantTurnMeaning = toRecordObject(input.assistantTurnMeaning);
|
||||
const predecomposeContract = toRecordObject(input.predecomposeContract);
|
||||
const followupContext = toRecordObject(input.followupContext);
|
||||
const predecomposeEntities = collectPredecomposeEntities(predecomposeContract);
|
||||
const followupSeed = collectFollowupDiscoverySeed(followupContext);
|
||||
const reasonCodes: string[] = [];
|
||||
const rawText = compactLower(`${input.userMessage ?? ""} ${input.effectiveMessage ?? ""}`);
|
||||
const lifecycleSignal = hasLifecycleSignal(rawText);
|
||||
const bidirectionalValueFlowSignal = !lifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
|
||||
const valueFlowSignal = !lifecycleSignal && (hasValueFlowSignal(rawText) || bidirectionalValueFlowSignal);
|
||||
const payoutSignal = valueFlowSignal && !bidirectionalValueFlowSignal && hasPayoutSignal(rawText);
|
||||
const monthlyAggregationSignal = valueFlowSignal && hasMonthlyAggregationSignal(rawText);
|
||||
const rawLifecycleSignal = hasLifecycleSignal(rawText);
|
||||
const rawBidirectionalValueFlowSignal = !rawLifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
|
||||
const rawValueFlowSignal =
|
||||
!rawLifecycleSignal && (hasValueFlowSignal(rawText) || rawBidirectionalValueFlowSignal);
|
||||
const rawPayoutSignal = rawValueFlowSignal && !rawBidirectionalValueFlowSignal && hasPayoutSignal(rawText);
|
||||
const monthlyAggregationSignal = hasMonthlyAggregationSignal(rawText);
|
||||
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(rawText);
|
||||
const rawDateScope = collectDateScopeFromRawText(rawText);
|
||||
|
||||
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
|
||||
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
|
||||
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
|
||||
const unsupported = toNonEmptyString(assistantTurnMeaning?.unsupported_but_understood_family);
|
||||
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
|
||||
const assistantTurnMeaningDateScope = toNonEmptyString(assistantTurnMeaning?.explicit_date_scope);
|
||||
const assistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
|
||||
const predecomposeDateScope = collectDateScope(predecomposeContract);
|
||||
const followupDiscoverySeedApplicable = Boolean(
|
||||
followupSeed.domain &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
(monthlyAggregationSignal || explicitDateScopeLiteralDetected || predecomposeDateScope)
|
||||
);
|
||||
const seededDomain = followupDiscoverySeedApplicable ? followupSeed.domain : null;
|
||||
const seededAction = followupDiscoverySeedApplicable ? followupSeed.action : null;
|
||||
const seededUnsupported = followupDiscoverySeedApplicable ? followupSeed.unsupported : null;
|
||||
const lifecycleSignal =
|
||||
rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
||||
const bidirectionalValueFlowSignal =
|
||||
!lifecycleSignal &&
|
||||
(rawBidirectionalValueFlowSignal || seededAction === "net_value_flow");
|
||||
const valueFlowSignal =
|
||||
!lifecycleSignal && (rawValueFlowSignal || seededDomain === "counterparty_value");
|
||||
const payoutSignal =
|
||||
valueFlowSignal &&
|
||||
!bidirectionalValueFlowSignal &&
|
||||
(rawPayoutSignal || seededAction === "payout");
|
||||
const semanticDataNeed = semanticNeedFor({
|
||||
domain: rawDomain,
|
||||
action: rawAction,
|
||||
unsupported,
|
||||
domain: rawDomain ?? seededDomain,
|
||||
action: rawAction ?? seededAction,
|
||||
unsupported: unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal
|
||||
});
|
||||
const entityCandidates = collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
||||
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
||||
if (valueFlowSignal && !predecomposeEntities.counterparty) {
|
||||
pushUnique(entityCandidates, followupSeed.counterparty);
|
||||
if (valueFlowSignal && !predecomposeEntities.counterparty && !followupSeed.counterparty) {
|
||||
pushUnique(entityCandidates, predecomposeEntities.organization);
|
||||
pushUnique(entityCandidates, followupSeed.organization);
|
||||
}
|
||||
const explicitOrganizationScope =
|
||||
valueFlowSignal && !predecomposeEntities.counterparty ? null : predecomposeEntities.organization;
|
||||
valueFlowSignal && !predecomposeEntities.counterparty && !followupSeed.counterparty
|
||||
? null
|
||||
: predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope ?? followupSeed.organization;
|
||||
const explicitDateScope = assistantTurnMeaningDateScope ?? predecomposeDateScope ?? rawDateScope ?? followupSeed.dateScope;
|
||||
|
||||
const turnMeaning: AssistantMcpDiscoveryTurnMeaningRef = {
|
||||
asked_domain_family: lifecycleSignal ? "counterparty_lifecycle" : valueFlowSignal ? "counterparty_value" : rawDomain,
|
||||
asked_domain_family:
|
||||
lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? "counterparty_value"
|
||||
: rawDomain ?? seededDomain,
|
||||
asked_action_family: lifecycleSignal
|
||||
? "activity_duration"
|
||||
: valueFlowSignal
|
||||
@@ -247,12 +453,12 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
? "net_value_flow"
|
||||
: payoutSignal
|
||||
? "payout"
|
||||
: "turnover"
|
||||
: rawAction,
|
||||
: rawAction ?? seededAction ?? "turnover"
|
||||
: rawAction ?? seededAction,
|
||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
explicit_organization_scope: explicitOrganizationScope,
|
||||
explicit_date_scope: collectDateScope(predecomposeContract),
|
||||
explicit_date_scope: explicitDateScope,
|
||||
unsupported_but_understood_family:
|
||||
unsupported ??
|
||||
(lifecycleSignal
|
||||
@@ -262,9 +468,17 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
? "counterparty_bidirectional_value_flow_or_netting"
|
||||
: payoutSignal
|
||||
? "counterparty_payouts_or_outflow"
|
||||
: "counterparty_value_or_turnover"
|
||||
: null),
|
||||
stale_replay_forbidden: Boolean(assistantTurnMeaning?.stale_replay_forbidden || unsupported || lifecycleSignal || valueFlowSignal)
|
||||
: seededUnsupported ?? "counterparty_value_or_turnover"
|
||||
: followupDiscoverySeedApplicable
|
||||
? seededUnsupported
|
||||
: null),
|
||||
stale_replay_forbidden: Boolean(
|
||||
assistantTurnMeaning?.stale_replay_forbidden ||
|
||||
unsupported ||
|
||||
lifecycleSignal ||
|
||||
valueFlowSignal ||
|
||||
followupDiscoverySeedApplicable
|
||||
)
|
||||
};
|
||||
|
||||
const cleanTurnMeaning: AssistantMcpDiscoveryTurnMeaningRef = {};
|
||||
@@ -294,15 +508,18 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
}
|
||||
|
||||
const runDiscovery = shouldRunDiscovery({
|
||||
unsupported,
|
||||
unsupported: unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
semanticDataNeed,
|
||||
explicitIntentCandidate
|
||||
explicitIntentCandidate,
|
||||
followupDiscoverySeedApplicable
|
||||
});
|
||||
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
||||
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = assistantTurnMeaning
|
||||
? "assistant_turn_meaning"
|
||||
: followupDiscoverySeedApplicable
|
||||
? "followup_context"
|
||||
: predecomposeContract
|
||||
? "predecompose_contract"
|
||||
: lifecycleSignal
|
||||
@@ -326,12 +543,21 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
if (monthlyAggregationSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_monthly_aggregation_signal_detected");
|
||||
}
|
||||
if (followupDiscoverySeedApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
||||
}
|
||||
if (unsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||
}
|
||||
if (predecomposeEntities.counterparty) {
|
||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
|
||||
}
|
||||
if (followupSeed.counterparty) {
|
||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_followup_context");
|
||||
}
|
||||
if (followupSeed.dateScope) {
|
||||
pushReason(reasonCodes, "mcp_discovery_date_scope_from_followup_context");
|
||||
}
|
||||
if (entityCandidates.length > 0) {
|
||||
pushReason(reasonCodes, "mcp_discovery_entity_scope_available");
|
||||
}
|
||||
|
||||
@@ -62,6 +62,14 @@ function toRecordObject(value: unknown): Record<string, unknown> | null {
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function ensureSentence(value: string): string {
|
||||
const text = String(value ?? "").trim();
|
||||
if (!text) {
|
||||
return "";
|
||||
}
|
||||
return /[.!?]$/.test(text) ? text : `${text}.`;
|
||||
}
|
||||
|
||||
function periodPartForRecap(scopedDate: string | null): string {
|
||||
if (!scopedDate) {
|
||||
return "";
|
||||
@@ -353,6 +361,38 @@ export function buildAddressMemoryRecapReply(input: {
|
||||
return "Да, помню предыдущий адресный контур. Могу кратко напомнить, что мы уже подтвердили, или сразу продолжить следующий шаг.";
|
||||
}
|
||||
|
||||
export function buildBroadBusinessEvaluationReply(input: {
|
||||
organization: string | null;
|
||||
addressDebug: Record<string, unknown> | null;
|
||||
sessionItems?: unknown[];
|
||||
toNonEmptyString: (value: unknown) => string | null;
|
||||
}): string {
|
||||
const contextFacts = resolveAddressDebugContextFacts(input.addressDebug, input.toNonEmptyString);
|
||||
const organization = input.organization ?? contextFacts.organization;
|
||||
const recapFacts = collectRecentRecapFacts({
|
||||
sessionItems: input.sessionItems,
|
||||
item: null,
|
||||
organization,
|
||||
toNonEmptyString: input.toNonEmptyString
|
||||
});
|
||||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||||
|
||||
if (recapFacts.length > 0) {
|
||||
return [
|
||||
`Коротко: по тому, что мы уже подтвердили в 1С${organizationPart}, компания выглядит операционно живой, но это пока только частичная оценка бизнеса.`,
|
||||
"Сейчас я опираюсь на такие подтвержденные факты:",
|
||||
...recapFacts.map((fact) => `- ${ensureSentence(fact)}`),
|
||||
"Это еще не полная диагностика всего бизнеса и не вывод о прибыли: я честно суммирую только те контуры, которые мы уже проверили в диалоге.",
|
||||
"Если хочешь, следующим шагом могу сузить оценку до денежного потока, долгов, НДС или ключевых контрагентов."
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
return [
|
||||
`Коротко: по нынешнему контексту 1С${organizationPart} я вижу признаки операционной активности, но для содержательной оценки бизнеса нужно еще несколько опорных срезов.`,
|
||||
"Если хочешь, я быстро доберу основу для такой оценки: денежный поток, дебиторка/кредиторка, НДС или ключевые контрагенты."
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
export function buildSelectedObjectAnswerInspectionReply(input: {
|
||||
addressDebug: Record<string, unknown> | null;
|
||||
toNonEmptyString: (value: unknown) => string | null;
|
||||
|
||||
@@ -7,9 +7,11 @@ import {
|
||||
buildRootScopedCarryoverFilters,
|
||||
buildInventoryRootFrameFromAddressDebug,
|
||||
hydrateInventoryRootFrameState,
|
||||
readAddressDebugIntent,
|
||||
readAddressDebugFilters,
|
||||
readAddressDebugItem,
|
||||
readAddressDebugTemporalScope,
|
||||
readAssistantMcpDiscoveryPilotScope,
|
||||
resolveOrganizationClarificationContinuation,
|
||||
resolveNavigationSessionContextState,
|
||||
resolveAddressDebugCarryoverFilters,
|
||||
@@ -171,19 +173,23 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
return false;
|
||||
}
|
||||
const executionLane = deps.toNonEmptyString(debug.execution_lane);
|
||||
const detectedIntent = deps.toNonEmptyString(debug.detected_intent);
|
||||
const detectedIntent = readAddressDebugIntent(debug, deps.toNonEmptyString);
|
||||
const selectedRecipe = deps.toNonEmptyString(debug.selected_recipe);
|
||||
const answerGroundingCheck =
|
||||
debug.answer_grounding_check && typeof debug.answer_grounding_check === "object"
|
||||
? debug.answer_grounding_check
|
||||
: null;
|
||||
const groundingStatus = deps.toNonEmptyString(answerGroundingCheck?.status);
|
||||
const discoveryPilotScope = readAssistantMcpDiscoveryPilotScope(debug, deps.toNonEmptyString);
|
||||
if (groundingStatus === "grounded") {
|
||||
return true;
|
||||
}
|
||||
if (selectedRecipe) {
|
||||
return true;
|
||||
}
|
||||
if (debug.mcp_discovery_response_applied === true && discoveryPilotScope) {
|
||||
return true;
|
||||
}
|
||||
return executionLane === "address_query" && Boolean(detectedIntent && detectedIntent !== "unknown");
|
||||
}
|
||||
|
||||
@@ -438,7 +444,7 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
(deps.toNonEmptyString(alternateMessage)
|
||||
? deps.isImplicitAddressContinuationByLlm(alternateMessage, llmPreDecomposeMeta)
|
||||
: false));
|
||||
const sourceIntentHint = deps.toNonEmptyString(carryoverSourceDebug?.detected_intent);
|
||||
const sourceIntentHint = readAddressDebugIntent(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const navigationSessionState = resolveNavigationSessionContextState(
|
||||
addressNavigationState,
|
||||
deps.toNonEmptyString,
|
||||
@@ -599,7 +605,8 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
if (!carryoverSourceDebug) {
|
||||
return null;
|
||||
}
|
||||
const sourceIntent = deps.toNonEmptyString(carryoverSourceDebug.detected_intent);
|
||||
const sourceIntent = readAddressDebugIntent(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryPilotScope = readAssistantMcpDiscoveryPilotScope(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const llmSelectedObjectScopeDetected =
|
||||
llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
||||
@@ -931,6 +938,7 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
previous_filters: previousFilters,
|
||||
previous_anchor_type: previousAnchorType ?? undefined,
|
||||
previous_anchor_value: previousAnchor,
|
||||
previous_discovery_pilot_scope: sourceDiscoveryPilotScope ?? undefined,
|
||||
resolved_counterparty_from_display: resolvedCounterpartyFromDisplay || undefined,
|
||||
root_context_only: rootScopedPivot || undefined,
|
||||
root_intent: shouldAttachInventoryRootFrame ? inventoryRootFrame?.intent ?? undefined : undefined,
|
||||
|
||||
@@ -117,6 +117,23 @@ function detectCounterpartyTurnoverFamily(text) {
|
||||
};
|
||||
}
|
||||
|
||||
function detectBroadBusinessEvaluation(text) {
|
||||
const normalized = String(text ?? "");
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
/(?:как\s+ты\s+оценишь\s+деятельност[ьи]\s+компан|оценк[аи]?\s+деятельност[ьи]\s+компан|что\s+у\s+нас\s+вообще\s+происход|где\s+главн(?:ые|ый)\s+риски|как\s+у\s+нас\s+дела\s+по\s+компан)/iu.test(
|
||||
normalized
|
||||
)
|
||||
) {
|
||||
return {
|
||||
family: "broad_business_evaluation"
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildEntityCandidates(counterpartyTurnover) {
|
||||
if (!counterpartyTurnover?.entity) {
|
||||
return [];
|
||||
@@ -139,10 +156,17 @@ export function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||
const joinedText = fallbackCompactWhitespace(`${rawText} ${effectiveText}`);
|
||||
const supportedIntent = detectSupportedIntent(joinedText, deps);
|
||||
const counterpartyTurnover = detectCounterpartyTurnoverFamily(joinedText);
|
||||
const broadBusinessEvaluation = detectBroadBusinessEvaluation(joinedText);
|
||||
const llmIntent = toNonEmptyString(input?.llmPreDecomposeMeta?.predecomposeContract?.intent, deps);
|
||||
const explicitIntentCandidate =
|
||||
supportedIntent?.intent ?? (llmIntent && llmIntent !== "unknown" ? llmIntent : null);
|
||||
const unsupportedFamily = !explicitIntentCandidate && counterpartyTurnover?.family ? counterpartyTurnover.family : null;
|
||||
broadBusinessEvaluation?.family
|
||||
? null
|
||||
: supportedIntent?.intent ?? (llmIntent && llmIntent !== "unknown" ? llmIntent : null);
|
||||
const unsupportedFamily = broadBusinessEvaluation?.family
|
||||
? broadBusinessEvaluation.family
|
||||
: !explicitIntentCandidate && counterpartyTurnover?.family
|
||||
? counterpartyTurnover.family
|
||||
: null;
|
||||
const reasonCodes = [];
|
||||
if (supportedIntent?.reason) {
|
||||
reasonCodes.push(supportedIntent.reason);
|
||||
@@ -150,6 +174,9 @@ export function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||
if (counterpartyTurnover?.family) {
|
||||
reasonCodes.push("counterparty_turnover_current_turn_signal");
|
||||
}
|
||||
if (broadBusinessEvaluation?.family) {
|
||||
reasonCodes.push("broad_business_evaluation_current_turn_signal");
|
||||
}
|
||||
if (rawText !== normalizeTurnText(rawMessage, { ...deps, repairAddressMojibake: (value) => String(value ?? "") })) {
|
||||
reasonCodes.push("mojibake_repair_applied");
|
||||
}
|
||||
@@ -168,6 +195,8 @@ export function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||
? "vat"
|
||||
: explicitIntentCandidate?.startsWith("inventory_")
|
||||
? "inventory"
|
||||
: broadBusinessEvaluation?.family
|
||||
? "business_summary"
|
||||
: explicitIntentCandidate?.includes("counterparty")
|
||||
? "counterparty"
|
||||
: counterpartyTurnover?.family
|
||||
@@ -178,6 +207,8 @@ export function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||
explicitIntentCandidate === "payables_confirmed_as_of_date" ||
|
||||
explicitIntentCandidate === "inventory_on_hand_as_of_date"
|
||||
? "confirmed_snapshot"
|
||||
: broadBusinessEvaluation?.family
|
||||
? "broad_evaluation"
|
||||
: explicitIntentCandidate === "vat_liability_confirmed_for_tax_period"
|
||||
? "confirmed_tax_period"
|
||||
: explicitIntentCandidate === "vat_payable_confirmed_as_of_date"
|
||||
@@ -189,7 +220,9 @@ export function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||
: counterpartyTurnover?.family
|
||||
? "counterparty_value_or_turnover"
|
||||
: null;
|
||||
const staleReplayForbidden = Boolean(unsupportedFamily || (counterpartyTurnover?.entity && !explicitIntentCandidate));
|
||||
const staleReplayForbidden = Boolean(
|
||||
unsupportedFamily || broadBusinessEvaluation?.family || (counterpartyTurnover?.entity && !explicitIntentCandidate)
|
||||
);
|
||||
return {
|
||||
schema_version: "assistant_turn_meaning_v1",
|
||||
raw_message: rawMessage,
|
||||
@@ -200,7 +233,9 @@ export function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||
asked_action_family: askedActionFamily,
|
||||
explicit_intent_candidate: explicitIntentCandidate,
|
||||
explicit_entity_candidates: buildEntityCandidates(counterpartyTurnover),
|
||||
meaning_confidence: supportedIntent?.confidence ?? (counterpartyTurnover?.family ? "medium" : "low"),
|
||||
meaning_confidence: broadBusinessEvaluation?.family
|
||||
? "medium"
|
||||
: supportedIntent?.confidence ?? (counterpartyTurnover?.family ? "medium" : "low"),
|
||||
intent_override_strength: explicitIntentCandidate
|
||||
? "explicit_current_turn_intent"
|
||||
: staleReplayForbidden
|
||||
|
||||
Reference in New Issue
Block a user