Укрепить семантическую арбитрацию follow-up после жирного прогона
This commit is contained in:
parent
ba23b056b8
commit
a15cbb3fcb
|
|
@ -1724,6 +1724,13 @@ function resolveUnicodeAddressIntentBridge(text) {
|
||||||
]).has(byAnchorToken);
|
]).has(byAnchorToken);
|
||||||
const hasMoneyCue = /(?:деньг|денег|выручк|доход|оборот|заработ|прин[её]с|чек|ликвидн|revenue|turnover|money)/iu.test(normalized);
|
const hasMoneyCue = /(?:деньг|денег|выручк|доход|оборот|заработ|прин[её]с|чек|ликвидн|revenue|turnover|money)/iu.test(normalized);
|
||||||
const hasRankingCue = /(?:топ|ранк|сам(?:ый|ая|ое|ые)|больше\s+всего|наибольш|крупн|жирн|max|top|rank)/iu.test(normalized);
|
const hasRankingCue = /(?:топ|ранк|сам(?:ый|ая|ое|ые)|больше\s+всего|наибольш|крупн|жирн|max|top|rank)/iu.test(normalized);
|
||||||
|
const hasSelectedObjectProfitabilityCue = /(?:\u043f\u043e\s+\u0432\u044b\u0431\u0440\u0430\u043d\u043d(?:\u043e\u043c\u0443|\u043e\u0439)\s+(?:\u043e\u0431\u044a\u0435\u043a\u0442\u0443|\u043f\u043e\u0437\u0438\u0446\u0438\u0438)|selected\s+object)/iu.test(normalized) &&
|
||||||
|
(/(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u0440\u0438\u0431\u044b\u043b|\u043c\u0430\u0440\u0436|profit|margin)/iu.test(normalized) ||
|
||||||
|
(/(?:\u043f\u0440\u043e\u0434\u0430\u0436|sale)/iu.test(normalized) &&
|
||||||
|
/(?:\u0437\u0430\u043a\u0443\u043f|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|purchase|document)/iu.test(normalized)));
|
||||||
|
if (hasSelectedObjectProfitabilityCue) {
|
||||||
|
return unicodeBridgeResolution("inventory_profitability_for_item", "high", "unicode_selected_object_profitability_bridge_signal_detected");
|
||||||
|
}
|
||||||
const hasInventoryPurchaseToSaleDocumentChainCue = /(?:закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|->\s*(?:склад|warehouse|stock)\s*->\s*(?:продаж|sale))/iu.test(normalized) && /(?:товар|позици|номенклатур|sku|item|product)/iu.test(normalized);
|
const hasInventoryPurchaseToSaleDocumentChainCue = /(?:закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|->\s*(?:склад|warehouse|stock)\s*->\s*(?:продаж|sale))/iu.test(normalized) && /(?:товар|позици|номенклатур|sku|item|product)/iu.test(normalized);
|
||||||
if (hasInventoryPurchaseToSaleDocumentChainCue) {
|
if (hasInventoryPurchaseToSaleDocumentChainCue) {
|
||||||
return unicodeBridgeResolution("inventory_purchase_to_sale_chain", "high", "unicode_inventory_purchase_to_sale_chain_bridge_signal_detected");
|
return unicodeBridgeResolution("inventory_purchase_to_sale_chain", "high", "unicode_inventory_purchase_to_sale_chain_bridge_signal_detected");
|
||||||
|
|
@ -1745,6 +1752,11 @@ function resolveUnicodeAddressIntentBridge(text) {
|
||||||
if (!hasContractCue && hasHighestValueCustomerCue) {
|
if (!hasContractCue && hasHighestValueCustomerCue) {
|
||||||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_bridge_signal_detected");
|
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_bridge_signal_detected");
|
||||||
}
|
}
|
||||||
|
const hasCustomerConcentrationCue = /(?:\u043a\u0440\u0443\u043f\u043d\w*|\u043e\u0441\u043d\u043e\u0432\u043d\w*|\u0433\u043b\u0430\u0432\u043d\w*|\u0442\u043e\u043f|top|largest|main|key|concentration|\u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\w*|\u0437\u0430\u0432\u0438\u0441\w*|\u043e\u0434\u043d(?:\u043e\u0433\u043e|\u043e\u043c\u0443)\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442))/iu.test(normalized) &&
|
||||||
|
/(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|customer|client|buyer|counterparty)/iu.test(normalized);
|
||||||
|
if (!hasContractCue && hasCustomerConcentrationCue) {
|
||||||
|
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_concentration_bridge_signal_detected");
|
||||||
|
}
|
||||||
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
||||||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -522,6 +522,9 @@ function shouldRestoreInventoryRootFrame(userMessage, intent, extractedFilters,
|
||||||
return hasTemporalPatch;
|
return hasTemporalPatch;
|
||||||
}
|
}
|
||||||
function hasSelectedObjectInventorySignal(text) {
|
function hasSelectedObjectInventorySignal(text) {
|
||||||
|
if (/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(String(text ?? ""))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(String(text ?? ""));
|
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(String(text ?? ""));
|
||||||
}
|
}
|
||||||
function hasSelectedObjectInlineSnapshotMetadata(text) {
|
function hasSelectedObjectInlineSnapshotMetadata(text) {
|
||||||
|
|
@ -590,6 +593,12 @@ function hasAddressFollowupContextSignal(text) {
|
||||||
if (/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(normalized)) {
|
if (/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(normalized)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(normalized)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (/(?:^|\s)(?:и|а\s+еще|а\s+ещё|еще|ещё|также|а\s+теперь|теперь|по\s+этому|по\s+тому|это\s+же|в\s+этом|тот\s+же|also|same|that|then|now)/iu.test(normalized)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (hasAllTimeHint(normalized)) {
|
if (hasAllTimeHint(normalized)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1201,7 +1210,17 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
|
||||||
followupContext.current_frame_kind === "inventory_drilldown";
|
followupContext.current_frame_kind === "inventory_drilldown";
|
||||||
const inventorySelectedObjectFollowup = inventoryLineageActive &&
|
const inventorySelectedObjectFollowup = inventoryLineageActive &&
|
||||||
(hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal));
|
(hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal));
|
||||||
|
const previousCounterpartyLaneActive = hasPreviousCounterparty &&
|
||||||
|
(followupContext.previous_anchor_type === "counterparty" ||
|
||||||
|
sourceIntent === "list_documents_by_counterparty" ||
|
||||||
|
sourceIntent === "list_contracts_by_counterparty" ||
|
||||||
|
sourceIntent === "bank_operations_by_counterparty" ||
|
||||||
|
sourceIntent === "customer_revenue_and_payments" ||
|
||||||
|
sourceIntent === "supplier_payouts_profile" ||
|
||||||
|
sourceIntent === "counterparty_activity_lifecycle" ||
|
||||||
|
sourceIntent === "open_items_by_counterparty_or_contract");
|
||||||
const hasExplicitInventoryItemReference = /(?:товар|номенклатур|позици|склад|остат|sku|item|product|товар|номенклатур|позици|склад|остат)/iu.test(normalizedMessage) || hasSelectedObjectInlineSnapshotMetadata(normalizedMessage);
|
const hasExplicitInventoryItemReference = /(?:товар|номенклатур|позици|склад|остат|sku|item|product|товар|номенклатур|позици|склад|остат)/iu.test(normalizedMessage) || hasSelectedObjectInlineSnapshotMetadata(normalizedMessage);
|
||||||
|
const staleInventoryLineageCanYieldToCounterparty = previousCounterpartyLaneActive && !hasExplicitInventoryItemReference;
|
||||||
const inventoryPurchaseDateVatBridge = inventorySelectedObjectFollowup && hasInventoryPurchaseDateVatBridgeCue(normalizedMessage);
|
const inventoryPurchaseDateVatBridge = inventorySelectedObjectFollowup && hasInventoryPurchaseDateVatBridgeCue(normalizedMessage);
|
||||||
if (inventoryPurchaseDateVatBridge &&
|
if (inventoryPurchaseDateVatBridge &&
|
||||||
(detectedIntent.intent === "unknown" ||
|
(detectedIntent.intent === "unknown" ||
|
||||||
|
|
@ -1236,14 +1255,20 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
|
||||||
reasons: [...detectedIntent.reasons, "intent_adjusted_to_vat_followup_context"]
|
reasons: [...detectedIntent.reasons, "intent_adjusted_to_vat_followup_context"]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!inventoryLineageActive &&
|
if ((!inventoryLineageActive || staleInventoryLineageCanYieldToCounterparty) &&
|
||||||
hasAnyPartyAnchor &&
|
hasAnyPartyAnchor &&
|
||||||
!hasExplicitInventoryItemReference &&
|
!hasExplicitInventoryItemReference &&
|
||||||
detectedIntent.intent === "inventory_purchase_documents_for_item") {
|
(detectedIntent.intent === "inventory_purchase_documents_for_item" ||
|
||||||
|
(staleInventoryLineageCanYieldToCounterparty && hasInventoryPurchaseDocumentsFollowupCue(normalizedMessage)))) {
|
||||||
return {
|
return {
|
||||||
intent: hasPreviousContract && !hasPreviousCounterparty ? "list_documents_by_contract" : "list_documents_by_counterparty",
|
intent: hasPreviousContract && !hasPreviousCounterparty ? "list_documents_by_contract" : "list_documents_by_counterparty",
|
||||||
confidence: "low",
|
confidence: "low",
|
||||||
reasons: [...detectedIntent.reasons, "intent_adjusted_from_non_inventory_followup_context"]
|
reasons: [
|
||||||
|
...detectedIntent.reasons,
|
||||||
|
staleInventoryLineageCanYieldToCounterparty
|
||||||
|
? "intent_adjusted_from_stale_inventory_context_to_counterparty_documents"
|
||||||
|
: "intent_adjusted_from_non_inventory_followup_context"
|
||||||
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const allowOpenItemsFollowupFallback = detectedIntent.intent === "unknown" && !isVatFollowup;
|
const allowOpenItemsFollowupFallback = detectedIntent.intent === "unknown" && !isVatFollowup;
|
||||||
|
|
|
||||||
|
|
@ -111,10 +111,11 @@ function isDetectedIntentAlignedWithTurnMeaning(detectedIntent, turnMeaning) {
|
||||||
askedAction === "age_or_activity_duration");
|
askedAction === "age_or_activity_duration");
|
||||||
}
|
}
|
||||||
if (normalizedIntent === "supplier_payouts_profile") {
|
if (normalizedIntent === "supplier_payouts_profile") {
|
||||||
return askedDomain === "counterparty_value" && askedAction === "payout";
|
return (askedDomain === "counterparty_value" || askedDomain === "counterparty") && askedAction === "payout";
|
||||||
}
|
}
|
||||||
if (normalizedIntent === "customer_revenue_and_payments") {
|
if (normalizedIntent === "customer_revenue_and_payments") {
|
||||||
return askedDomain === "counterparty_value" && (askedAction === "turnover" || askedAction === "counterparty_value_or_turnover");
|
return ((askedDomain === "counterparty_value" || askedDomain === "counterparty") &&
|
||||||
|
(askedAction === "turnover" || askedAction === "counterparty_value_or_turnover"));
|
||||||
}
|
}
|
||||||
if (normalizedIntent === "receivables_confirmed_as_of_date") {
|
if (normalizedIntent === "receivables_confirmed_as_of_date") {
|
||||||
return askedDomain === "receivables" || askedAction === "confirmed_snapshot";
|
return askedDomain === "receivables" || askedAction === "confirmed_snapshot";
|
||||||
|
|
@ -137,6 +138,13 @@ function isDetectedIntentAlignedWithTurnMeaning(detectedIntent, turnMeaning) {
|
||||||
if (normalizedIntent === "inventory_on_hand_as_of_date" || normalizedIntent === "inventory_aging_by_purchase_date") {
|
if (normalizedIntent === "inventory_on_hand_as_of_date" || normalizedIntent === "inventory_aging_by_purchase_date") {
|
||||||
return askedDomain === "inventory" && askedAction === "confirmed_snapshot";
|
return askedDomain === "inventory" && askedAction === "confirmed_snapshot";
|
||||||
}
|
}
|
||||||
|
if (normalizedIntent === "inventory_purchase_provenance_for_item" ||
|
||||||
|
normalizedIntent === "inventory_purchase_documents_for_item" ||
|
||||||
|
normalizedIntent === "inventory_sale_trace_for_item" ||
|
||||||
|
normalizedIntent === "inventory_profitability_for_item" ||
|
||||||
|
normalizedIntent === "inventory_purchase_to_sale_chain") {
|
||||||
|
return askedDomain === "inventory";
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function readDiscoveryTurnMeaning(entryPoint) {
|
function readDiscoveryTurnMeaning(entryPoint) {
|
||||||
|
|
@ -147,6 +155,34 @@ function readDiscoveryDataNeedGraph(entryPoint) {
|
||||||
const turnInput = toRecordObject(entryPoint?.turn_input);
|
const turnInput = toRecordObject(entryPoint?.turn_input);
|
||||||
return toRecordObject(turnInput?.data_need_graph);
|
return toRecordObject(turnInput?.data_need_graph);
|
||||||
}
|
}
|
||||||
|
function isMetadataDiscoveryTurn(entryPoint) {
|
||||||
|
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||||
|
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||||
|
const bridge = toRecordObject(entryPoint?.bridge);
|
||||||
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
const reasonCodes = Array.isArray(entryPoint?.reason_codes) ? entryPoint.reason_codes : [];
|
||||||
|
return Boolean(toNonEmptyString(turnMeaning?.asked_domain_family) === "metadata" ||
|
||||||
|
toNonEmptyString(turnMeaning?.unsupported_but_understood_family) === "1c_metadata_surface" ||
|
||||||
|
toNonEmptyString(graph?.business_fact_family) === "schema_surface" ||
|
||||||
|
toNonEmptyString(pilot?.pilot_scope) === "metadata_inspection_v1" ||
|
||||||
|
reasonCodes.some((reason) => toNonEmptyString(reason) === "mcp_discovery_metadata_signal_detected"));
|
||||||
|
}
|
||||||
|
function isInventoryExactAddressIntent(intent) {
|
||||||
|
return /^(?:inventory_purchase_provenance_for_item|inventory_purchase_documents_for_item|inventory_sale_trace_for_item|inventory_profitability_for_item|inventory_purchase_to_sale_chain|inventory_aging_by_purchase_date|inventory_on_hand_as_of_date)$/u.test(String(intent ?? ""));
|
||||||
|
}
|
||||||
|
function hasMetadataDiscoveryPriority(input, entryPoint) {
|
||||||
|
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isMetadataDiscoveryTurn(entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
|
return !isInventoryExactAddressIntent(detectedIntent);
|
||||||
|
}
|
||||||
function isOpenScopeValueFlowWithoutSubject(entryPoint) {
|
function isOpenScopeValueFlowWithoutSubject(entryPoint) {
|
||||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||||
const businessFactFamily = toNonEmptyString(graph?.business_fact_family);
|
const businessFactFamily = toNonEmptyString(graph?.business_fact_family);
|
||||||
|
|
@ -224,6 +260,26 @@ function hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
function hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint) {
|
||||||
|
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||||
|
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||||
|
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family);
|
||||||
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
|
if (!detectedIntent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const asksForMovements = askedDomain === "movements" || askedAction === "list_movements";
|
||||||
|
const asksForDocuments = askedDomain === "documents" || askedAction === "list_documents";
|
||||||
|
const detectedDocumentsLane = detectedIntent === "list_documents_by_counterparty" || detectedIntent === "list_documents_by_contract";
|
||||||
|
const detectedCashLane = detectedIntent === "bank_operations_by_counterparty" || detectedIntent === "bank_operations_by_contract";
|
||||||
|
return (asksForMovements && detectedDocumentsLane) || (asksForDocuments && detectedCashLane);
|
||||||
|
}
|
||||||
function hasExactMatchedFactualAddressReply(input, entryPoint) {
|
function hasExactMatchedFactualAddressReply(input, entryPoint) {
|
||||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -234,9 +290,15 @@ function hasExactMatchedFactualAddressReply(input, entryPoint) {
|
||||||
if (hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint)) {
|
if (hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
if (hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
|
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
|
||||||
const truthMode = toNonEmptyString(input.addressRuntimeMeta?.truth_mode);
|
const truthMode = toNonEmptyString(input.addressRuntimeMeta?.truth_mode);
|
||||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||||
|
|
@ -267,6 +329,12 @@ function hasRuntimeAdjustedExactReply(input, entryPoint) {
|
||||||
if (!hasEffectivelyFactualAddressReply(input)) {
|
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const truthGateStatus = toNonEmptyString(input.addressRuntimeMeta?.truth_gate_contract_status);
|
const truthGateStatus = toNonEmptyString(input.addressRuntimeMeta?.truth_gate_contract_status);
|
||||||
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
||||||
const truthGate = toRecordObject(truthAnswerPolicy?.truth_gate);
|
const truthGate = toRecordObject(truthAnswerPolicy?.truth_gate);
|
||||||
|
|
@ -293,6 +361,9 @@ function hasAlignedFactualAddressReply(input, entryPoint) {
|
||||||
if (!hasEffectivelyFactualAddressReply(input)) {
|
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -323,6 +394,9 @@ function hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint) {
|
||||||
if (needsOpenScopeValueFlowOrganizationClarification(entryPoint)) {
|
if (needsOpenScopeValueFlowOrganizationClarification(entryPoint)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (detectedIntent === "customer_revenue_and_payments" &&
|
if (detectedIntent === "customer_revenue_and_payments" &&
|
||||||
isOpenScopeValueFlowWithoutSubject(entryPoint)) {
|
isOpenScopeValueFlowWithoutSubject(entryPoint)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -336,6 +410,9 @@ function hasMatchedFactualAddressContinuationTarget(input, entryPoint) {
|
||||||
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
const dialogContinuationContract = toRecordObject(input.addressRuntimeMeta?.dialogContinuationContract) ??
|
const dialogContinuationContract = toRecordObject(input.addressRuntimeMeta?.dialogContinuationContract) ??
|
||||||
toRecordObject(input.addressRuntimeMeta?.dialog_continuation_contract_v2);
|
toRecordObject(input.addressRuntimeMeta?.dialog_continuation_contract_v2);
|
||||||
|
|
@ -373,6 +450,9 @@ function hasFullConfirmedFactualAddressReply(input, entryPoint) {
|
||||||
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const truthGateStatus = toNonEmptyString(input.addressRuntimeMeta?.truth_gate_contract_status);
|
const truthGateStatus = toNonEmptyString(input.addressRuntimeMeta?.truth_gate_contract_status);
|
||||||
if (truthGateStatus === "full_confirmed") {
|
if (truthGateStatus === "full_confirmed") {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -403,7 +483,9 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
||||||
const exactMatchedFactualAddressReply = hasExactMatchedFactualAddressReply(input, entryPoint);
|
const exactMatchedFactualAddressReply = hasExactMatchedFactualAddressReply(input, entryPoint);
|
||||||
const runtimeAdjustedExactReply = hasRuntimeAdjustedExactReply(input, entryPoint);
|
const runtimeAdjustedExactReply = hasRuntimeAdjustedExactReply(input, entryPoint);
|
||||||
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
||||||
|
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
||||||
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
||||||
|
const evidenceLaneConflictWithDiscoveryTurnMeaning = hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
||||||
if (!entryPoint) {
|
if (!entryPoint) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_no_entry_point");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_no_entry_point");
|
||||||
}
|
}
|
||||||
|
|
@ -428,9 +510,15 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
||||||
if (valueFlowActionConflictWithDiscoveryTurnMeaning) {
|
if (valueFlowActionConflictWithDiscoveryTurnMeaning) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_value_flow_action_conflict_allows_candidate_override");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_value_flow_action_conflict_allows_candidate_override");
|
||||||
}
|
}
|
||||||
|
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
||||||
|
}
|
||||||
if (openScopeValueFlowDiscoveryPriority) {
|
if (openScopeValueFlowDiscoveryPriority) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
||||||
}
|
}
|
||||||
|
if (metadataDiscoveryPriority) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_response_policy_metadata_candidate_priority");
|
||||||
|
}
|
||||||
if (matchedFactualAddressContinuationTarget) {
|
if (matchedFactualAddressContinuationTarget) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_factual_address_continuation_target");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_factual_address_continuation_target");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,23 @@ function isGarbageSemanticAnchorCandidate(value) {
|
||||||
}
|
}
|
||||||
const compact = (0, addressTextRepair_1.normalizeRussianComparableText)(text);
|
const compact = (0, addressTextRepair_1.normalizeRussianComparableText)(text);
|
||||||
if (new Set([
|
if (new Set([
|
||||||
|
"для",
|
||||||
|
"по",
|
||||||
|
"ему",
|
||||||
|
"нему",
|
||||||
|
"ней",
|
||||||
|
"этой",
|
||||||
|
"этому",
|
||||||
|
"этим",
|
||||||
|
"этими",
|
||||||
|
"данным",
|
||||||
|
"данными",
|
||||||
|
"документам",
|
||||||
|
"документами",
|
||||||
|
"движение",
|
||||||
|
"движения",
|
||||||
|
"движениям",
|
||||||
|
"операциям",
|
||||||
"данным",
|
"данным",
|
||||||
"этим",
|
"этим",
|
||||||
"этими",
|
"этими",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,45 @@ function hasImplicitHistoricalCapabilityMetaSignal(samples) {
|
||||||
return samples.some((sample) => /(?:историческ|история|архив|раньше|ретро|старые\s+данные)/iu.test(sample) &&
|
return samples.some((sample) => /(?:историческ|история|архив|раньше|ретро|старые\s+данные)/iu.test(sample) &&
|
||||||
/(?:мож(?:ешь|ем|но)|уме(?:ешь|ете))/iu.test(sample));
|
/(?:мож(?:ешь|ем|но)|уме(?:ешь|ете))/iu.test(sample));
|
||||||
}
|
}
|
||||||
|
function normalizedSampleText(value) {
|
||||||
|
return String(value ?? "")
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.replace(/\u0451/gu, "\u0435")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
function hasSchemaDataScopeMetaSignal(samples) {
|
||||||
|
return samples.some((sample) => {
|
||||||
|
const text = normalizedSampleText(sample);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasOneCOrSchemaCue = /(?:\b1\s*[cс]\b|1с|metadata|schema|схем|структур|метаданн)/iu.test(text);
|
||||||
|
const hasSchemaObjectCue = /(?:справочник|справочники|регистр|регистры|объект(?:ы|ов)?|пол[ея]|связ[ьи]|реквизит|таблиц|раздел(?:ы|ов)?|catalog|register|object|field|relation|link)/iu.test(text);
|
||||||
|
const hasDirectSchemaQuestion = /(?:какие\s+(?:справочник|справочники|регистры|объекты|поля|связи|реквизиты)|где\s+(?:лежат|хранятся).*(?:данные|поля)|что\s+есть\s+в\s+1с\s+по)/iu.test(text);
|
||||||
|
const hasDocumentSchemaQuestion = /(?:какие\s+(?:поля|связи|реквизиты).{0,80}(?:документ|реализац|поступлен)|(?:документ|реализац|поступлен).{0,80}(?:поля|связи|реквизиты))/iu.test(text);
|
||||||
|
return (hasOneCOrSchemaCue && hasSchemaObjectCue) || hasDirectSchemaQuestion || hasDocumentSchemaQuestion;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function hasBusinessBoundaryQuestionSignal(samples) {
|
||||||
|
return samples.some((sample) => {
|
||||||
|
const text = normalizedSampleText(sample);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasClassificationCue = /(?:можно\s+ли\s+считать|это\s+можно\s+считать|как\s+считать|классифиц|объясни\s+аккуратно|это\s+уже|это\s+пока|can\s+this\s+be\s+treated)/iu.test(text);
|
||||||
|
const hasBusinessObjectCue = /(?:просроч|открыт\w*\s+задолж|долг|дебитор|кредитор|ликвид|резерв|неликвид|склад|прибыл|марж|оборот|контрагент|покупател|поставщик|overdue|debt|receivable|payable|stock|profit|margin)/iu.test(text);
|
||||||
|
return hasClassificationCue && hasBusinessObjectCue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function hasContextIntegrityInspectionSignal(samples) {
|
||||||
|
return samples.some((sample) => {
|
||||||
|
const text = normalizedSampleText(sample);
|
||||||
|
return Boolean(text &&
|
||||||
|
/(?:проверь\s+себя|не\s+смешал|не\s+перепутал|правильно\s+ли\s+контур|объясни\s+контур|self[-\s]?check)/iu.test(text) &&
|
||||||
|
/(?:контрагент|организац|компан|контур|scope|counterparty|organization|company)/iu.test(text));
|
||||||
|
});
|
||||||
|
}
|
||||||
function createAssistantMetaFollowupPolicy(deps) {
|
function createAssistantMetaFollowupPolicy(deps) {
|
||||||
function resolveMetaSignalSet(input) {
|
function resolveMetaSignalSet(input) {
|
||||||
const samples = collectMessageSamples(input);
|
const samples = collectMessageSamples(input);
|
||||||
|
|
@ -31,12 +70,17 @@ function createAssistantMetaFollowupPolicy(deps) {
|
||||||
answerInspectionFollowupSignal: false
|
answerInspectionFollowupSignal: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const dataScopeMetaQuery = hasSignalAcrossSamples(samples, deps.hasAssistantDataScopeMetaQuestionSignal) ||
|
||||||
|
hasSchemaDataScopeMetaSignal(samples);
|
||||||
|
const businessBoundaryQuestion = hasBusinessBoundaryQuestionSignal(samples);
|
||||||
return {
|
return {
|
||||||
dataScopeMetaQuery: hasSignalAcrossSamples(samples, deps.hasAssistantDataScopeMetaQuestionSignal),
|
dataScopeMetaQuery,
|
||||||
capabilityMetaQuery: hasSignalAcrossSamples(samples, deps.shouldHandleAsAssistantCapabilityMetaQuery) ||
|
capabilityMetaQuery: !businessBoundaryQuestion &&
|
||||||
hasImplicitHistoricalCapabilityMetaSignal(samples),
|
(hasSignalAcrossSamples(samples, deps.shouldHandleAsAssistantCapabilityMetaQuery) ||
|
||||||
|
hasImplicitHistoricalCapabilityMetaSignal(samples)),
|
||||||
metaAnswerFollowupSignal: hasSignalAcrossSamples(samples, deps.hasMetaAnswerFollowupSignal),
|
metaAnswerFollowupSignal: hasSignalAcrossSamples(samples, deps.hasMetaAnswerFollowupSignal),
|
||||||
answerInspectionFollowupSignal: hasSignalAcrossSamples(samples, deps.hasAnswerInspectionFollowupSignal)
|
answerInspectionFollowupSignal: hasSignalAcrossSamples(samples, deps.hasAnswerInspectionFollowupSignal) ||
|
||||||
|
hasContextIntegrityInspectionSignal(samples)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function resolveHardMetaMode(input) {
|
function resolveHardMetaMode(input) {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,32 @@
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.createAssistantTurnMeaningPolicy = createAssistantTurnMeaningPolicy;
|
exports.createAssistantTurnMeaningPolicy = createAssistantTurnMeaningPolicy;
|
||||||
const SUPPORTED_ADDRESS_INTENTS = new Set([
|
const SUPPORTED_ADDRESS_INTENTS = new Set([
|
||||||
|
"period_coverage_profile",
|
||||||
|
"document_type_and_account_section_profile",
|
||||||
|
"counterparty_population_and_roles",
|
||||||
|
"counterparty_activity_lifecycle",
|
||||||
"receivables_confirmed_as_of_date",
|
"receivables_confirmed_as_of_date",
|
||||||
"payables_confirmed_as_of_date",
|
"payables_confirmed_as_of_date",
|
||||||
"list_documents_by_counterparty",
|
"list_documents_by_counterparty",
|
||||||
|
"bank_operations_by_counterparty",
|
||||||
|
"list_contracts_by_counterparty",
|
||||||
"customer_revenue_and_payments",
|
"customer_revenue_and_payments",
|
||||||
|
"supplier_payouts_profile",
|
||||||
|
"open_contracts_confirmed_as_of_date",
|
||||||
|
"list_open_contracts",
|
||||||
|
"open_items_by_counterparty_or_contract",
|
||||||
|
"list_payables_counterparties",
|
||||||
|
"list_receivables_counterparties",
|
||||||
"inventory_on_hand_as_of_date",
|
"inventory_on_hand_as_of_date",
|
||||||
|
"inventory_purchase_provenance_for_item",
|
||||||
|
"inventory_purchase_documents_for_item",
|
||||||
|
"inventory_supplier_stock_overlap_as_of_date",
|
||||||
|
"inventory_sale_trace_for_item",
|
||||||
|
"inventory_profitability_for_item",
|
||||||
|
"inventory_purchase_to_sale_chain",
|
||||||
|
"inventory_aging_by_purchase_date",
|
||||||
|
"contract_usage_overview",
|
||||||
|
"contract_usage_and_value",
|
||||||
"vat_liability_confirmed_for_tax_period",
|
"vat_liability_confirmed_for_tax_period",
|
||||||
"vat_payable_confirmed_as_of_date",
|
"vat_payable_confirmed_as_of_date",
|
||||||
"vat_payable_forecast"
|
"vat_payable_forecast"
|
||||||
|
|
@ -115,6 +136,17 @@ function detectCounterpartyTurnoverFamily(text) {
|
||||||
function hasExplicitCounterpartyValueObject(text) {
|
function hasExplicitCounterpartyValueObject(text) {
|
||||||
return /(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|\u0441\u0434\u0435\u043b\u043a|customer|client|counterparty|supplier|vendor|contract|item|product|deal)/iu.test(text);
|
return /(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|\u0441\u0434\u0435\u043b\u043a|customer|client|counterparty|supplier|vendor|contract|item|product|deal)/iu.test(text);
|
||||||
}
|
}
|
||||||
|
function hasSelectedObjectInventoryExactSignal(text) {
|
||||||
|
const normalized = String(text ?? "");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasSelectedObjectCue = /(?:\u043f\u043e\s+\u0432\u044b\u0431\u0440\u0430\u043d\u043d(?:\u043e\u043c\u0443|\u043e\u0439)\s+(?:\u043e\u0431\u044a\u0435\u043a\u0442\u0443|\u043f\u043e\u0437\u0438\u0446\u0438\u0438)|selected\s+object|\u043f\u043e\s+\u044d\u0442(?:\u043e\u0439|\u043e\u043c\u0443)\s+(?:\u043f\u043e\u0437\u0438\u0446\u0438\u0438|\u0442\u043e\u0432\u0430\u0440\u0443))/iu.test(normalized);
|
||||||
|
if (!hasSelectedObjectCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u0440\u0438\u0431\u044b\u043b|\u043c\u0430\u0440\u0436|\u043f\u0440\u043e\u0434\u0430\u0436|\u043f\u0440\u043e\u0434\u0430\u043b|\u0437\u0430\u043a\u0443\u043f|\u043f\u043e\u043a\u0443\u043f|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0446\u0435\u043f\u043e\u0447|profit|margin|sale|purchase|document)/iu.test(normalized);
|
||||||
|
}
|
||||||
function hasOrganizationLevelEarningsOverviewSignal(text) {
|
function hasOrganizationLevelEarningsOverviewSignal(text) {
|
||||||
const normalized = String(text ?? "");
|
const normalized = String(text ?? "");
|
||||||
if (!normalized || hasExplicitCounterpartyValueObject(normalized)) {
|
if (!normalized || hasExplicitCounterpartyValueObject(normalized)) {
|
||||||
|
|
@ -138,6 +170,25 @@ function hasOrganizationLevelDebtDueDateOverviewSignal(text) {
|
||||||
const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(normalized);
|
const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(normalized);
|
||||||
return hasDueDateDebtCue && hasCompanyScopeCue;
|
return hasDueDateDebtCue && hasCompanyScopeCue;
|
||||||
}
|
}
|
||||||
|
function hasOrganizationLevelDebtPositionOverviewSignal(text) {
|
||||||
|
const normalized = String(text ?? "");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasReceivablesCue = /(?:\u0434\u0435\u0431\u0438\u0442\u043e\u0440\w*|\u043a\u0442\u043e\s+\u043d\u0430\u043c\s+\u0434\u043e\u043b\u0436|\u043d\u0430\u043c\s+\u0434\u043e\u043b\u0436\w*|receivables?|accounts\s+receivable)/iu.test(normalized);
|
||||||
|
const hasPayablesCue = /(?:\u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440\w*|\u043a\u043e\u043c\u0443\s+\u043c\u044b\s+\u0434\u043e\u043b\u0436|\u043c\u044b\s+\u0434\u043e\u043b\u0436\w*|payables?|accounts\s+payable)/iu.test(normalized);
|
||||||
|
const hasOverviewCue = /(?:\u0441\u0440\u0435\u0437|\u043f\u043e\u0437\u0438\u0446\w*|\u0431\u0430\u043b\u0430\u043d\u0441|\u0441\u0430\u043b\u044c\u0434\u043e|\u043d\u0435\u0442\u0442\u043e|\u043d\u0430\s+\u0441\u0435\u0433\u043e\u0434\u043d|\u043d\u0430\s+\u0434\u0430\u0442\u0443|\u0443\s+\u043d\u0430\u0441|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0431\u0437\u043e\u0440|\u0430\u043d\u0430\u043b\u0438\u0437|as\s+of|overview|balance|net|company|business)/iu.test(normalized);
|
||||||
|
return hasReceivablesCue && hasPayablesCue && hasOverviewCue;
|
||||||
|
}
|
||||||
|
function hasDebtClassificationFollowupSignal(text) {
|
||||||
|
const normalized = String(text ?? "");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasClassificationCue = /(?:\u043c\u043e\u0436\u043d\u043e\s+\u043b\u0438\s+\u0441\u0447\u0438\u0442\u0430\u0442\u044c|\u044d\u0442\u043e\s+\u043c\u043e\u0436\u043d\u043e\s+\u0441\u0447\u0438\u0442\u0430\u0442\u044c|\u044d\u0442\u043e\s+\u0441\u0447\u0438\u0442\u0430\u0442\u044c|\u043a\u043b\u0430\u0441\u0441\u0438\u0444\u0438\u0446|\u0447\u0442\u043e\s+\u044d\u0442\u043e|can\s+this\s+be\s+treated)/iu.test(normalized);
|
||||||
|
const hasDebtCue = /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447|\u043e\u0442\u043a\u0440\u044b\u0442\w*\s+\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0434\u0435\u0431\u0438\u0442\u043e\u0440|\u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440|overdue|open\s+debt|receivable|payable)/iu.test(normalized);
|
||||||
|
return hasClassificationCue && hasDebtCue;
|
||||||
|
}
|
||||||
function hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(text) {
|
function hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(text) {
|
||||||
const normalized = String(text ?? "");
|
const normalized = String(text ?? "");
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -201,6 +252,16 @@ function detectBroadBusinessEvaluation(text) {
|
||||||
family: "broad_business_evaluation"
|
family: "broad_business_evaluation"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (hasOrganizationLevelDebtPositionOverviewSignal(normalized)) {
|
||||||
|
return {
|
||||||
|
family: "broad_business_evaluation"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasDebtClassificationFollowupSignal(normalized)) {
|
||||||
|
return {
|
||||||
|
family: "broad_business_evaluation"
|
||||||
|
};
|
||||||
|
}
|
||||||
if (hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(normalized)) {
|
if (hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(normalized)) {
|
||||||
return {
|
return {
|
||||||
family: "broad_business_evaluation"
|
family: "broad_business_evaluation"
|
||||||
|
|
@ -239,7 +300,8 @@ function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||||
const joinedText = fallbackCompactWhitespace(`${rawText} ${effectiveText}`);
|
const joinedText = fallbackCompactWhitespace(`${rawText} ${effectiveText}`);
|
||||||
const supportedIntent = detectSupportedIntent(joinedText, deps);
|
const supportedIntent = detectSupportedIntent(joinedText, deps);
|
||||||
const counterpartyTurnover = detectCounterpartyTurnoverFamily(joinedText);
|
const counterpartyTurnover = detectCounterpartyTurnoverFamily(joinedText);
|
||||||
const broadBusinessEvaluation = detectBroadBusinessEvaluation(joinedText);
|
const selectedObjectInventoryExact = hasSelectedObjectInventoryExactSignal(joinedText);
|
||||||
|
const broadBusinessEvaluation = selectedObjectInventoryExact ? null : detectBroadBusinessEvaluation(joinedText);
|
||||||
const llmIntent = toNonEmptyString(input?.llmPreDecomposeMeta?.predecomposeContract?.intent, deps);
|
const llmIntent = toNonEmptyString(input?.llmPreDecomposeMeta?.predecomposeContract?.intent, deps);
|
||||||
const explicitIntentCandidate = broadBusinessEvaluation?.family
|
const explicitIntentCandidate = broadBusinessEvaluation?.family
|
||||||
? null
|
? null
|
||||||
|
|
@ -287,6 +349,9 @@ function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||||
? "confirmed_snapshot"
|
? "confirmed_snapshot"
|
||||||
: broadBusinessEvaluation?.family
|
: broadBusinessEvaluation?.family
|
||||||
? "broad_evaluation"
|
? "broad_evaluation"
|
||||||
|
: explicitIntentCandidate === "customer_revenue_and_payments" ||
|
||||||
|
explicitIntentCandidate === "supplier_payouts_profile"
|
||||||
|
? "counterparty_value_or_turnover"
|
||||||
: explicitIntentCandidate === "vat_liability_confirmed_for_tax_period"
|
: explicitIntentCandidate === "vat_liability_confirmed_for_tax_period"
|
||||||
? "confirmed_tax_period"
|
? "confirmed_tax_period"
|
||||||
: explicitIntentCandidate === "vat_payable_confirmed_as_of_date"
|
: explicitIntentCandidate === "vat_payable_confirmed_as_of_date"
|
||||||
|
|
|
||||||
|
|
@ -2232,6 +2232,21 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio
|
||||||
const hasRankingCue = /(?:топ|ранк|сам(?:ый|ая|ое|ые)|больше\s+всего|наибольш|крупн|жирн|max|top|rank)/iu.test(
|
const hasRankingCue = /(?:топ|ранк|сам(?:ый|ая|ое|ые)|больше\s+всего|наибольш|крупн|жирн|max|top|rank)/iu.test(
|
||||||
normalized
|
normalized
|
||||||
);
|
);
|
||||||
|
const hasSelectedObjectProfitabilityCue =
|
||||||
|
/(?:\u043f\u043e\s+\u0432\u044b\u0431\u0440\u0430\u043d\u043d(?:\u043e\u043c\u0443|\u043e\u0439)\s+(?:\u043e\u0431\u044a\u0435\u043a\u0442\u0443|\u043f\u043e\u0437\u0438\u0446\u0438\u0438)|selected\s+object)/iu.test(
|
||||||
|
normalized
|
||||||
|
) &&
|
||||||
|
(/(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u0440\u0438\u0431\u044b\u043b|\u043c\u0430\u0440\u0436|profit|margin)/iu.test(normalized) ||
|
||||||
|
(/(?:\u043f\u0440\u043e\u0434\u0430\u0436|sale)/iu.test(normalized) &&
|
||||||
|
/(?:\u0437\u0430\u043a\u0443\u043f|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|purchase|document)/iu.test(normalized)));
|
||||||
|
if (hasSelectedObjectProfitabilityCue) {
|
||||||
|
return unicodeBridgeResolution(
|
||||||
|
"inventory_profitability_for_item",
|
||||||
|
"high",
|
||||||
|
"unicode_selected_object_profitability_bridge_signal_detected"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const hasInventoryPurchaseToSaleDocumentChainCue =
|
const hasInventoryPurchaseToSaleDocumentChainCue =
|
||||||
/(?:закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|->\s*(?:склад|warehouse|stock)\s*->\s*(?:продаж|sale))/iu.test(
|
/(?:закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|->\s*(?:склад|warehouse|stock)\s*->\s*(?:продаж|sale))/iu.test(
|
||||||
normalized
|
normalized
|
||||||
|
|
@ -2281,6 +2296,21 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasCustomerConcentrationCue =
|
||||||
|
/(?:\u043a\u0440\u0443\u043f\u043d\w*|\u043e\u0441\u043d\u043e\u0432\u043d\w*|\u0433\u043b\u0430\u0432\u043d\w*|\u0442\u043e\u043f|top|largest|main|key|concentration|\u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\w*|\u0437\u0430\u0432\u0438\u0441\w*|\u043e\u0434\u043d(?:\u043e\u0433\u043e|\u043e\u043c\u0443)\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442))/iu.test(
|
||||||
|
normalized
|
||||||
|
) &&
|
||||||
|
/(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|customer|client|buyer|counterparty)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
if (!hasContractCue && hasCustomerConcentrationCue) {
|
||||||
|
return unicodeBridgeResolution(
|
||||||
|
"customer_revenue_and_payments",
|
||||||
|
"high",
|
||||||
|
"unicode_customer_concentration_bridge_signal_detected"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
||||||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -660,6 +660,13 @@ function shouldRestoreInventoryRootFrame(
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasSelectedObjectInventorySignal(text: string): boolean {
|
function hasSelectedObjectInventorySignal(text: string): boolean {
|
||||||
|
if (
|
||||||
|
/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(
|
||||||
|
String(text ?? "")
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(
|
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(
|
||||||
String(text ?? "")
|
String(text ?? "")
|
||||||
);
|
);
|
||||||
|
|
@ -763,6 +770,20 @@ export function hasAddressFollowupContextSignal(text: string): boolean {
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(
|
||||||
|
normalized
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
/(?:^|\s)(?:и|а\s+еще|а\s+ещё|еще|ещё|также|а\s+теперь|теперь|по\s+этому|по\s+тому|это\s+же|в\s+этом|тот\s+же|also|same|that|then|now)/iu.test(
|
||||||
|
normalized
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (hasAllTimeHint(normalized)) {
|
if (hasAllTimeHint(normalized)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1499,10 +1520,22 @@ function deriveIntentWithFollowupContext(
|
||||||
const inventorySelectedObjectFollowup =
|
const inventorySelectedObjectFollowup =
|
||||||
inventoryLineageActive &&
|
inventoryLineageActive &&
|
||||||
(hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal));
|
(hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal));
|
||||||
|
const previousCounterpartyLaneActive =
|
||||||
|
hasPreviousCounterparty &&
|
||||||
|
(followupContext.previous_anchor_type === "counterparty" ||
|
||||||
|
sourceIntent === "list_documents_by_counterparty" ||
|
||||||
|
sourceIntent === "list_contracts_by_counterparty" ||
|
||||||
|
sourceIntent === "bank_operations_by_counterparty" ||
|
||||||
|
sourceIntent === "customer_revenue_and_payments" ||
|
||||||
|
sourceIntent === "supplier_payouts_profile" ||
|
||||||
|
sourceIntent === "counterparty_activity_lifecycle" ||
|
||||||
|
sourceIntent === "open_items_by_counterparty_or_contract");
|
||||||
const hasExplicitInventoryItemReference =
|
const hasExplicitInventoryItemReference =
|
||||||
/(?:товар|номенклатур|позици|склад|остат|sku|item|product|товар|номенклатур|позици|склад|остат)/iu.test(
|
/(?:товар|номенклатур|позици|склад|остат|sku|item|product|товар|номенклатур|позици|склад|остат)/iu.test(
|
||||||
normalizedMessage
|
normalizedMessage
|
||||||
) || hasSelectedObjectInlineSnapshotMetadata(normalizedMessage);
|
) || hasSelectedObjectInlineSnapshotMetadata(normalizedMessage);
|
||||||
|
const staleInventoryLineageCanYieldToCounterparty =
|
||||||
|
previousCounterpartyLaneActive && !hasExplicitInventoryItemReference;
|
||||||
const inventoryPurchaseDateVatBridge =
|
const inventoryPurchaseDateVatBridge =
|
||||||
inventorySelectedObjectFollowup && hasInventoryPurchaseDateVatBridgeCue(normalizedMessage);
|
inventorySelectedObjectFollowup && hasInventoryPurchaseDateVatBridgeCue(normalizedMessage);
|
||||||
|
|
||||||
|
|
@ -1547,15 +1580,21 @@ function deriveIntentWithFollowupContext(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!inventoryLineageActive &&
|
(!inventoryLineageActive || staleInventoryLineageCanYieldToCounterparty) &&
|
||||||
hasAnyPartyAnchor &&
|
hasAnyPartyAnchor &&
|
||||||
!hasExplicitInventoryItemReference &&
|
!hasExplicitInventoryItemReference &&
|
||||||
detectedIntent.intent === "inventory_purchase_documents_for_item"
|
(detectedIntent.intent === "inventory_purchase_documents_for_item" ||
|
||||||
|
(staleInventoryLineageCanYieldToCounterparty && hasInventoryPurchaseDocumentsFollowupCue(normalizedMessage)))
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
intent: hasPreviousContract && !hasPreviousCounterparty ? "list_documents_by_contract" : "list_documents_by_counterparty",
|
intent: hasPreviousContract && !hasPreviousCounterparty ? "list_documents_by_contract" : "list_documents_by_counterparty",
|
||||||
confidence: "low",
|
confidence: "low",
|
||||||
reasons: [...detectedIntent.reasons, "intent_adjusted_from_non_inventory_followup_context"]
|
reasons: [
|
||||||
|
...detectedIntent.reasons,
|
||||||
|
staleInventoryLineageCanYieldToCounterparty
|
||||||
|
? "intent_adjusted_from_stale_inventory_context_to_counterparty_documents"
|
||||||
|
: "intent_adjusted_from_non_inventory_followup_context"
|
||||||
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,10 +183,13 @@ function isDetectedIntentAlignedWithTurnMeaning(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (normalizedIntent === "supplier_payouts_profile") {
|
if (normalizedIntent === "supplier_payouts_profile") {
|
||||||
return askedDomain === "counterparty_value" && askedAction === "payout";
|
return (askedDomain === "counterparty_value" || askedDomain === "counterparty") && askedAction === "payout";
|
||||||
}
|
}
|
||||||
if (normalizedIntent === "customer_revenue_and_payments") {
|
if (normalizedIntent === "customer_revenue_and_payments") {
|
||||||
return askedDomain === "counterparty_value" && (askedAction === "turnover" || askedAction === "counterparty_value_or_turnover");
|
return (
|
||||||
|
(askedDomain === "counterparty_value" || askedDomain === "counterparty") &&
|
||||||
|
(askedAction === "turnover" || askedAction === "counterparty_value_or_turnover")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (normalizedIntent === "receivables_confirmed_as_of_date") {
|
if (normalizedIntent === "receivables_confirmed_as_of_date") {
|
||||||
return askedDomain === "receivables" || askedAction === "confirmed_snapshot";
|
return askedDomain === "receivables" || askedAction === "confirmed_snapshot";
|
||||||
|
|
@ -209,6 +212,15 @@ function isDetectedIntentAlignedWithTurnMeaning(
|
||||||
if (normalizedIntent === "inventory_on_hand_as_of_date" || normalizedIntent === "inventory_aging_by_purchase_date") {
|
if (normalizedIntent === "inventory_on_hand_as_of_date" || normalizedIntent === "inventory_aging_by_purchase_date") {
|
||||||
return askedDomain === "inventory" && askedAction === "confirmed_snapshot";
|
return askedDomain === "inventory" && askedAction === "confirmed_snapshot";
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
normalizedIntent === "inventory_purchase_provenance_for_item" ||
|
||||||
|
normalizedIntent === "inventory_purchase_documents_for_item" ||
|
||||||
|
normalizedIntent === "inventory_sale_trace_for_item" ||
|
||||||
|
normalizedIntent === "inventory_profitability_for_item" ||
|
||||||
|
normalizedIntent === "inventory_purchase_to_sale_chain"
|
||||||
|
) {
|
||||||
|
return askedDomain === "inventory";
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,6 +238,46 @@ function readDiscoveryDataNeedGraph(
|
||||||
return toRecordObject(turnInput?.data_need_graph);
|
return toRecordObject(turnInput?.data_need_graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMetadataDiscoveryTurn(
|
||||||
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||||
|
): boolean {
|
||||||
|
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||||
|
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||||
|
const bridge = toRecordObject(entryPoint?.bridge);
|
||||||
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
const reasonCodes = Array.isArray(entryPoint?.reason_codes) ? entryPoint.reason_codes : [];
|
||||||
|
return Boolean(
|
||||||
|
toNonEmptyString(turnMeaning?.asked_domain_family) === "metadata" ||
|
||||||
|
toNonEmptyString(turnMeaning?.unsupported_but_understood_family) === "1c_metadata_surface" ||
|
||||||
|
toNonEmptyString(graph?.business_fact_family) === "schema_surface" ||
|
||||||
|
toNonEmptyString(pilot?.pilot_scope) === "metadata_inspection_v1" ||
|
||||||
|
reasonCodes.some((reason) => toNonEmptyString(reason) === "mcp_discovery_metadata_signal_detected")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInventoryExactAddressIntent(intent: string | null): boolean {
|
||||||
|
return /^(?:inventory_purchase_provenance_for_item|inventory_purchase_documents_for_item|inventory_sale_trace_for_item|inventory_profitability_for_item|inventory_purchase_to_sale_chain|inventory_aging_by_purchase_date|inventory_on_hand_as_of_date)$/u.test(
|
||||||
|
String(intent ?? "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMetadataDiscoveryPriority(
|
||||||
|
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||||
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||||
|
): boolean {
|
||||||
|
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isMetadataDiscoveryTurn(entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
|
return !isInventoryExactAddressIntent(detectedIntent);
|
||||||
|
}
|
||||||
|
|
||||||
function isOpenScopeValueFlowWithoutSubject(
|
function isOpenScopeValueFlowWithoutSubject(
|
||||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||||
): boolean {
|
): boolean {
|
||||||
|
|
@ -324,6 +376,32 @@ function hasValueFlowActionConflictWithDiscoveryTurnMeaning(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasEvidenceLaneConflictWithDiscoveryTurnMeaning(
|
||||||
|
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||||
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||||
|
): boolean {
|
||||||
|
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||||
|
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||||
|
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family);
|
||||||
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
|
if (!detectedIntent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const asksForMovements = askedDomain === "movements" || askedAction === "list_movements";
|
||||||
|
const asksForDocuments = askedDomain === "documents" || askedAction === "list_documents";
|
||||||
|
const detectedDocumentsLane =
|
||||||
|
detectedIntent === "list_documents_by_counterparty" || detectedIntent === "list_documents_by_contract";
|
||||||
|
const detectedCashLane =
|
||||||
|
detectedIntent === "bank_operations_by_counterparty" || detectedIntent === "bank_operations_by_contract";
|
||||||
|
return (asksForMovements && detectedDocumentsLane) || (asksForDocuments && detectedCashLane);
|
||||||
|
}
|
||||||
|
|
||||||
function hasExactMatchedFactualAddressReply(
|
function hasExactMatchedFactualAddressReply(
|
||||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||||
|
|
@ -337,9 +415,15 @@ function hasExactMatchedFactualAddressReply(
|
||||||
if (hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint)) {
|
if (hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
if (hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
|
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
|
||||||
const truthMode = toNonEmptyString(input.addressRuntimeMeta?.truth_mode);
|
const truthMode = toNonEmptyString(input.addressRuntimeMeta?.truth_mode);
|
||||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||||
|
|
@ -382,6 +466,12 @@ function hasRuntimeAdjustedExactReply(
|
||||||
if (!hasEffectivelyFactualAddressReply(input)) {
|
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const truthGateStatus = toNonEmptyString(input.addressRuntimeMeta?.truth_gate_contract_status);
|
const truthGateStatus = toNonEmptyString(input.addressRuntimeMeta?.truth_gate_contract_status);
|
||||||
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
||||||
const truthGate = toRecordObject(truthAnswerPolicy?.truth_gate);
|
const truthGate = toRecordObject(truthAnswerPolicy?.truth_gate);
|
||||||
|
|
@ -415,6 +505,9 @@ function hasAlignedFactualAddressReply(
|
||||||
if (!hasEffectivelyFactualAddressReply(input)) {
|
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -449,6 +542,9 @@ function hasSemanticConflictWithDiscoveryTurnMeaning(
|
||||||
if (needsOpenScopeValueFlowOrganizationClarification(entryPoint)) {
|
if (needsOpenScopeValueFlowOrganizationClarification(entryPoint)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
detectedIntent === "customer_revenue_and_payments" &&
|
detectedIntent === "customer_revenue_and_payments" &&
|
||||||
isOpenScopeValueFlowWithoutSubject(entryPoint)
|
isOpenScopeValueFlowWithoutSubject(entryPoint)
|
||||||
|
|
@ -468,6 +564,9 @@ function hasMatchedFactualAddressContinuationTarget(
|
||||||
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
const dialogContinuationContract =
|
const dialogContinuationContract =
|
||||||
toRecordObject(input.addressRuntimeMeta?.dialogContinuationContract) ??
|
toRecordObject(input.addressRuntimeMeta?.dialogContinuationContract) ??
|
||||||
|
|
@ -517,6 +616,9 @@ function hasFullConfirmedFactualAddressReply(
|
||||||
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
if (hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const truthGateStatus = toNonEmptyString(input.addressRuntimeMeta?.truth_gate_contract_status);
|
const truthGateStatus = toNonEmptyString(input.addressRuntimeMeta?.truth_gate_contract_status);
|
||||||
if (truthGateStatus === "full_confirmed") {
|
if (truthGateStatus === "full_confirmed") {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -551,10 +653,15 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||||
const exactMatchedFactualAddressReply = hasExactMatchedFactualAddressReply(input, entryPoint);
|
const exactMatchedFactualAddressReply = hasExactMatchedFactualAddressReply(input, entryPoint);
|
||||||
const runtimeAdjustedExactReply = hasRuntimeAdjustedExactReply(input, entryPoint);
|
const runtimeAdjustedExactReply = hasRuntimeAdjustedExactReply(input, entryPoint);
|
||||||
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
||||||
|
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
||||||
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(
|
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(
|
||||||
input,
|
input,
|
||||||
entryPoint
|
entryPoint
|
||||||
);
|
);
|
||||||
|
const evidenceLaneConflictWithDiscoveryTurnMeaning = hasEvidenceLaneConflictWithDiscoveryTurnMeaning(
|
||||||
|
input,
|
||||||
|
entryPoint
|
||||||
|
);
|
||||||
|
|
||||||
if (!entryPoint) {
|
if (!entryPoint) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_no_entry_point");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_no_entry_point");
|
||||||
|
|
@ -580,9 +687,15 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||||
if (valueFlowActionConflictWithDiscoveryTurnMeaning) {
|
if (valueFlowActionConflictWithDiscoveryTurnMeaning) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_value_flow_action_conflict_allows_candidate_override");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_value_flow_action_conflict_allows_candidate_override");
|
||||||
}
|
}
|
||||||
|
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
||||||
|
}
|
||||||
if (openScopeValueFlowDiscoveryPriority) {
|
if (openScopeValueFlowDiscoveryPriority) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
||||||
}
|
}
|
||||||
|
if (metadataDiscoveryPriority) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_response_policy_metadata_candidate_priority");
|
||||||
|
}
|
||||||
if (matchedFactualAddressContinuationTarget) {
|
if (matchedFactualAddressContinuationTarget) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_factual_address_continuation_target");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_factual_address_continuation_target");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,23 @@ function isGarbageSemanticAnchorCandidate(value: string | null): boolean {
|
||||||
const compact = normalizeRussianComparableText(text);
|
const compact = normalizeRussianComparableText(text);
|
||||||
if (
|
if (
|
||||||
new Set([
|
new Set([
|
||||||
|
"для",
|
||||||
|
"по",
|
||||||
|
"ему",
|
||||||
|
"нему",
|
||||||
|
"ней",
|
||||||
|
"этой",
|
||||||
|
"этому",
|
||||||
|
"этим",
|
||||||
|
"этими",
|
||||||
|
"данным",
|
||||||
|
"данными",
|
||||||
|
"документам",
|
||||||
|
"документами",
|
||||||
|
"движение",
|
||||||
|
"движения",
|
||||||
|
"движениям",
|
||||||
|
"операциям",
|
||||||
"данным",
|
"данным",
|
||||||
"этим",
|
"этим",
|
||||||
"этими",
|
"этими",
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,69 @@ function hasImplicitHistoricalCapabilityMetaSignal(samples: string[]): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizedSampleText(value: string): string {
|
||||||
|
return String(value ?? "")
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.replace(/\u0451/gu, "\u0435")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasSchemaDataScopeMetaSignal(samples: string[]): boolean {
|
||||||
|
return samples.some((sample) => {
|
||||||
|
const text = normalizedSampleText(sample);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasOneCOrSchemaCue =
|
||||||
|
/(?:\b1\s*[cс]\b|1с|metadata|schema|схем|структур|метаданн)/iu.test(text);
|
||||||
|
const hasSchemaObjectCue =
|
||||||
|
/(?:справочник|справочники|регистр|регистры|объект(?:ы|ов)?|пол[ея]|связ[ьи]|реквизит|таблиц|раздел(?:ы|ов)?|catalog|register|object|field|relation|link)/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
const hasDirectSchemaQuestion =
|
||||||
|
/(?:какие\s+(?:справочник|справочники|регистры|объекты|поля|связи|реквизиты)|где\s+(?:лежат|хранятся).*(?:данные|поля)|что\s+есть\s+в\s+1с\s+по)/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
const hasDocumentSchemaQuestion =
|
||||||
|
/(?:какие\s+(?:поля|связи|реквизиты).{0,80}(?:документ|реализац|поступлен)|(?:документ|реализац|поступлен).{0,80}(?:поля|связи|реквизиты))/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
return (hasOneCOrSchemaCue && hasSchemaObjectCue) || hasDirectSchemaQuestion || hasDocumentSchemaQuestion;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasBusinessBoundaryQuestionSignal(samples: string[]): boolean {
|
||||||
|
return samples.some((sample) => {
|
||||||
|
const text = normalizedSampleText(sample);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasClassificationCue =
|
||||||
|
/(?:можно\s+ли\s+считать|это\s+можно\s+считать|как\s+считать|классифиц|объясни\s+аккуратно|это\s+уже|это\s+пока|can\s+this\s+be\s+treated)/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
const hasBusinessObjectCue =
|
||||||
|
/(?:просроч|открыт\w*\s+задолж|долг|дебитор|кредитор|ликвид|резерв|неликвид|склад|прибыл|марж|оборот|контрагент|покупател|поставщик|overdue|debt|receivable|payable|stock|profit|margin)/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
return hasClassificationCue && hasBusinessObjectCue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasContextIntegrityInspectionSignal(samples: string[]): boolean {
|
||||||
|
return samples.some((sample) => {
|
||||||
|
const text = normalizedSampleText(sample);
|
||||||
|
return Boolean(
|
||||||
|
text &&
|
||||||
|
/(?:проверь\s+себя|не\s+смешал|не\s+перепутал|правильно\s+ли\s+контур|объясни\s+контур|self[-\s]?check)/iu.test(
|
||||||
|
text
|
||||||
|
) &&
|
||||||
|
/(?:контрагент|организац|компан|контур|scope|counterparty|organization|company)/iu.test(text)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function createAssistantMetaFollowupPolicy(
|
export function createAssistantMetaFollowupPolicy(
|
||||||
deps: AssistantMetaFollowupPolicyDeps
|
deps: AssistantMetaFollowupPolicyDeps
|
||||||
) {
|
) {
|
||||||
|
|
@ -90,22 +153,23 @@ export function createAssistantMetaFollowupPolicy(
|
||||||
answerInspectionFollowupSignal: false
|
answerInspectionFollowupSignal: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const dataScopeMetaQuery =
|
||||||
|
hasSignalAcrossSamples(samples, deps.hasAssistantDataScopeMetaQuestionSignal) ||
|
||||||
|
hasSchemaDataScopeMetaSignal(samples);
|
||||||
|
const businessBoundaryQuestion = hasBusinessBoundaryQuestionSignal(samples);
|
||||||
return {
|
return {
|
||||||
dataScopeMetaQuery: hasSignalAcrossSamples(
|
dataScopeMetaQuery,
|
||||||
samples,
|
|
||||||
deps.hasAssistantDataScopeMetaQuestionSignal
|
|
||||||
),
|
|
||||||
capabilityMetaQuery:
|
capabilityMetaQuery:
|
||||||
hasSignalAcrossSamples(samples, deps.shouldHandleAsAssistantCapabilityMetaQuery) ||
|
!businessBoundaryQuestion &&
|
||||||
hasImplicitHistoricalCapabilityMetaSignal(samples),
|
(hasSignalAcrossSamples(samples, deps.shouldHandleAsAssistantCapabilityMetaQuery) ||
|
||||||
|
hasImplicitHistoricalCapabilityMetaSignal(samples)),
|
||||||
metaAnswerFollowupSignal: hasSignalAcrossSamples(
|
metaAnswerFollowupSignal: hasSignalAcrossSamples(
|
||||||
samples,
|
samples,
|
||||||
deps.hasMetaAnswerFollowupSignal
|
deps.hasMetaAnswerFollowupSignal
|
||||||
),
|
),
|
||||||
answerInspectionFollowupSignal: hasSignalAcrossSamples(
|
answerInspectionFollowupSignal:
|
||||||
samples,
|
hasSignalAcrossSamples(samples, deps.hasAnswerInspectionFollowupSignal) ||
|
||||||
deps.hasAnswerInspectionFollowupSignal
|
hasContextIntegrityInspectionSignal(samples)
|
||||||
)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,32 @@
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
const SUPPORTED_ADDRESS_INTENTS = new Set([
|
const SUPPORTED_ADDRESS_INTENTS = new Set([
|
||||||
|
"period_coverage_profile",
|
||||||
|
"document_type_and_account_section_profile",
|
||||||
|
"counterparty_population_and_roles",
|
||||||
|
"counterparty_activity_lifecycle",
|
||||||
"receivables_confirmed_as_of_date",
|
"receivables_confirmed_as_of_date",
|
||||||
"payables_confirmed_as_of_date",
|
"payables_confirmed_as_of_date",
|
||||||
"list_documents_by_counterparty",
|
"list_documents_by_counterparty",
|
||||||
|
"bank_operations_by_counterparty",
|
||||||
|
"list_contracts_by_counterparty",
|
||||||
"customer_revenue_and_payments",
|
"customer_revenue_and_payments",
|
||||||
|
"supplier_payouts_profile",
|
||||||
|
"open_contracts_confirmed_as_of_date",
|
||||||
|
"list_open_contracts",
|
||||||
|
"open_items_by_counterparty_or_contract",
|
||||||
|
"list_payables_counterparties",
|
||||||
|
"list_receivables_counterparties",
|
||||||
"inventory_on_hand_as_of_date",
|
"inventory_on_hand_as_of_date",
|
||||||
|
"inventory_purchase_provenance_for_item",
|
||||||
|
"inventory_purchase_documents_for_item",
|
||||||
|
"inventory_supplier_stock_overlap_as_of_date",
|
||||||
|
"inventory_sale_trace_for_item",
|
||||||
|
"inventory_profitability_for_item",
|
||||||
|
"inventory_purchase_to_sale_chain",
|
||||||
|
"inventory_aging_by_purchase_date",
|
||||||
|
"contract_usage_overview",
|
||||||
|
"contract_usage_and_value",
|
||||||
"vat_liability_confirmed_for_tax_period",
|
"vat_liability_confirmed_for_tax_period",
|
||||||
"vat_payable_confirmed_as_of_date",
|
"vat_payable_confirmed_as_of_date",
|
||||||
"vat_payable_forecast"
|
"vat_payable_forecast"
|
||||||
|
|
@ -123,6 +144,23 @@ function hasExplicitCounterpartyValueObject(text) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasSelectedObjectInventoryExactSignal(text) {
|
||||||
|
const normalized = String(text ?? "");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasSelectedObjectCue =
|
||||||
|
/(?:\u043f\u043e\s+\u0432\u044b\u0431\u0440\u0430\u043d\u043d(?:\u043e\u043c\u0443|\u043e\u0439)\s+(?:\u043e\u0431\u044a\u0435\u043a\u0442\u0443|\u043f\u043e\u0437\u0438\u0446\u0438\u0438)|selected\s+object|\u043f\u043e\s+\u044d\u0442(?:\u043e\u0439|\u043e\u043c\u0443)\s+(?:\u043f\u043e\u0437\u0438\u0446\u0438\u0438|\u0442\u043e\u0432\u0430\u0440\u0443))/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
if (!hasSelectedObjectCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u0440\u0438\u0431\u044b\u043b|\u043c\u0430\u0440\u0436|\u043f\u0440\u043e\u0434\u0430\u0436|\u043f\u0440\u043e\u0434\u0430\u043b|\u0437\u0430\u043a\u0443\u043f|\u043f\u043e\u043a\u0443\u043f|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0446\u0435\u043f\u043e\u0447|profit|margin|sale|purchase|document)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function hasOrganizationLevelEarningsOverviewSignal(text) {
|
function hasOrganizationLevelEarningsOverviewSignal(text) {
|
||||||
const normalized = String(text ?? "");
|
const normalized = String(text ?? "");
|
||||||
if (!normalized || hasExplicitCounterpartyValueObject(normalized)) {
|
if (!normalized || hasExplicitCounterpartyValueObject(normalized)) {
|
||||||
|
|
@ -169,6 +207,42 @@ function hasOrganizationLevelDebtDueDateOverviewSignal(text) {
|
||||||
return hasDueDateDebtCue && hasCompanyScopeCue;
|
return hasDueDateDebtCue && hasCompanyScopeCue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasOrganizationLevelDebtPositionOverviewSignal(text) {
|
||||||
|
const normalized = String(text ?? "");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasReceivablesCue =
|
||||||
|
/(?:\u0434\u0435\u0431\u0438\u0442\u043e\u0440\w*|\u043a\u0442\u043e\s+\u043d\u0430\u043c\s+\u0434\u043e\u043b\u0436|\u043d\u0430\u043c\s+\u0434\u043e\u043b\u0436\w*|receivables?|accounts\s+receivable)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
const hasPayablesCue =
|
||||||
|
/(?:\u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440\w*|\u043a\u043e\u043c\u0443\s+\u043c\u044b\s+\u0434\u043e\u043b\u0436|\u043c\u044b\s+\u0434\u043e\u043b\u0436\w*|payables?|accounts\s+payable)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
const hasOverviewCue =
|
||||||
|
/(?:\u0441\u0440\u0435\u0437|\u043f\u043e\u0437\u0438\u0446\w*|\u0431\u0430\u043b\u0430\u043d\u0441|\u0441\u0430\u043b\u044c\u0434\u043e|\u043d\u0435\u0442\u0442\u043e|\u043d\u0430\s+\u0441\u0435\u0433\u043e\u0434\u043d|\u043d\u0430\s+\u0434\u0430\u0442\u0443|\u0443\s+\u043d\u0430\u0441|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0431\u0437\u043e\u0440|\u0430\u043d\u0430\u043b\u0438\u0437|as\s+of|overview|balance|net|company|business)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
return hasReceivablesCue && hasPayablesCue && hasOverviewCue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasDebtClassificationFollowupSignal(text) {
|
||||||
|
const normalized = String(text ?? "");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasClassificationCue =
|
||||||
|
/(?:\u043c\u043e\u0436\u043d\u043e\s+\u043b\u0438\s+\u0441\u0447\u0438\u0442\u0430\u0442\u044c|\u044d\u0442\u043e\s+\u043c\u043e\u0436\u043d\u043e\s+\u0441\u0447\u0438\u0442\u0430\u0442\u044c|\u044d\u0442\u043e\s+\u0441\u0447\u0438\u0442\u0430\u0442\u044c|\u043a\u043b\u0430\u0441\u0441\u0438\u0444\u0438\u0446|\u0447\u0442\u043e\s+\u044d\u0442\u043e|can\s+this\s+be\s+treated)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
const hasDebtCue =
|
||||||
|
/(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447|\u043e\u0442\u043a\u0440\u044b\u0442\w*\s+\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0434\u0435\u0431\u0438\u0442\u043e\u0440|\u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440|overdue|open\s+debt|receivable|payable)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
return hasClassificationCue && hasDebtCue;
|
||||||
|
}
|
||||||
|
|
||||||
function hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(text) {
|
function hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(text) {
|
||||||
const normalized = String(text ?? "");
|
const normalized = String(text ?? "");
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -277,6 +351,16 @@ function detectBroadBusinessEvaluation(text) {
|
||||||
family: "broad_business_evaluation"
|
family: "broad_business_evaluation"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (hasOrganizationLevelDebtPositionOverviewSignal(normalized)) {
|
||||||
|
return {
|
||||||
|
family: "broad_business_evaluation"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasDebtClassificationFollowupSignal(normalized)) {
|
||||||
|
return {
|
||||||
|
family: "broad_business_evaluation"
|
||||||
|
};
|
||||||
|
}
|
||||||
if (hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(normalized)) {
|
if (hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(normalized)) {
|
||||||
return {
|
return {
|
||||||
family: "broad_business_evaluation"
|
family: "broad_business_evaluation"
|
||||||
|
|
@ -317,7 +401,8 @@ export function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||||
const joinedText = fallbackCompactWhitespace(`${rawText} ${effectiveText}`);
|
const joinedText = fallbackCompactWhitespace(`${rawText} ${effectiveText}`);
|
||||||
const supportedIntent = detectSupportedIntent(joinedText, deps);
|
const supportedIntent = detectSupportedIntent(joinedText, deps);
|
||||||
const counterpartyTurnover = detectCounterpartyTurnoverFamily(joinedText);
|
const counterpartyTurnover = detectCounterpartyTurnoverFamily(joinedText);
|
||||||
const broadBusinessEvaluation = detectBroadBusinessEvaluation(joinedText);
|
const selectedObjectInventoryExact = hasSelectedObjectInventoryExactSignal(joinedText);
|
||||||
|
const broadBusinessEvaluation = selectedObjectInventoryExact ? null : detectBroadBusinessEvaluation(joinedText);
|
||||||
const llmIntent = toNonEmptyString(input?.llmPreDecomposeMeta?.predecomposeContract?.intent, deps);
|
const llmIntent = toNonEmptyString(input?.llmPreDecomposeMeta?.predecomposeContract?.intent, deps);
|
||||||
const explicitIntentCandidate =
|
const explicitIntentCandidate =
|
||||||
broadBusinessEvaluation?.family
|
broadBusinessEvaluation?.family
|
||||||
|
|
@ -370,6 +455,9 @@ export function createAssistantTurnMeaningPolicy(deps = {}) {
|
||||||
? "confirmed_snapshot"
|
? "confirmed_snapshot"
|
||||||
: broadBusinessEvaluation?.family
|
: broadBusinessEvaluation?.family
|
||||||
? "broad_evaluation"
|
? "broad_evaluation"
|
||||||
|
: explicitIntentCandidate === "customer_revenue_and_payments" ||
|
||||||
|
explicitIntentCandidate === "supplier_payouts_profile"
|
||||||
|
? "counterparty_value_or_turnover"
|
||||||
: explicitIntentCandidate === "vat_liability_confirmed_for_tax_period"
|
: explicitIntentCandidate === "vat_liability_confirmed_for_tax_period"
|
||||||
? "confirmed_tax_period"
|
? "confirmed_tax_period"
|
||||||
: explicitIntentCandidate === "vat_payable_confirmed_as_of_date"
|
: explicitIntentCandidate === "vat_payable_confirmed_as_of_date"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { runAddressDecomposeStage } from "../src/services/address_runtime/decomposeStage";
|
||||||
|
|
||||||
|
describe("counterparty follow-up over stale inventory frame", () => {
|
||||||
|
it("keeps bare pronoun document follow-up on the active counterparty lane", () => {
|
||||||
|
const result = runAddressDecomposeStage("а по нему документы?", {
|
||||||
|
previous_intent: "list_contracts_by_counterparty",
|
||||||
|
target_intent: "list_documents_by_counterparty",
|
||||||
|
previous_filters: {
|
||||||
|
counterparty: 'ТСЖ "Жуковка 51"',
|
||||||
|
organization: "ООО Альтернатива Плюс",
|
||||||
|
as_of_date: "2026-04-16"
|
||||||
|
},
|
||||||
|
previous_anchor_type: "counterparty",
|
||||||
|
previous_anchor_value: 'ТСЖ "Жуковка 51"',
|
||||||
|
root_intent: "inventory_on_hand_as_of_date",
|
||||||
|
root_filters: {
|
||||||
|
organization: "ООО Альтернатива Плюс",
|
||||||
|
as_of_date: "2026-04-16"
|
||||||
|
},
|
||||||
|
root_anchor_type: "organization",
|
||||||
|
root_anchor_value: "ООО Альтернатива Плюс",
|
||||||
|
current_frame_kind: "inventory_root"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
expect(result?.intent.intent).toBe("list_documents_by_counterparty");
|
||||||
|
expect(result?.filters.extracted_filters.counterparty).toBe('ТСЖ "Жуковка 51"');
|
||||||
|
expect(result?.baseReasons).not.toContain("intent_adjusted_to_inventory_followup_context");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -29,6 +29,15 @@ describe("addressIntentResolver regression bridges", () => {
|
||||||
expect(result.intent).toBe("customer_revenue_and_payments");
|
expect(result.intent).toBe("customer_revenue_and_payments");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("detects customer concentration wording as customer revenue ranking", () => {
|
||||||
|
const result = resolveAddressIntent(
|
||||||
|
"\u041a\u0442\u043e \u043a\u0440\u0443\u043f\u043d\u0435\u0439\u0448\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u044b \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b \u041f\u043b\u044e\u0441 \u0438 \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u0438\u0437\u043d\u0435\u0441 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b\u044f?"
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.intent).toBe("customer_revenue_and_payments");
|
||||||
|
expect(result.reasons).toContain("unicode_customer_concentration_bridge_signal_detected");
|
||||||
|
});
|
||||||
|
|
||||||
it("defers top-year company revenue wording to business overview discovery", () => {
|
it("defers top-year company revenue wording to business overview discovery", () => {
|
||||||
const result = resolveAddressIntent("какой у нас самый доходный год");
|
const result = resolveAddressIntent("какой у нас самый доходный год");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,45 @@ describe("assistant MCP discovery response policy", () => {
|
||||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("overrides exact document replies when the discovery turn asks for movements", () => {
|
||||||
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
|
currentReply: "Documents for SVK: invoice 1, act 2.",
|
||||||
|
currentReplySource: "address_query_runtime_v1",
|
||||||
|
currentReplyType: "factual",
|
||||||
|
addressRuntimeMeta: {
|
||||||
|
detected_intent: "list_documents_by_counterparty",
|
||||||
|
selected_recipe: "address_list_documents_by_counterparty_v1",
|
||||||
|
mcp_call_status: "matched_non_empty",
|
||||||
|
truth_mode: "confirmed",
|
||||||
|
capability_binding_status: "bound",
|
||||||
|
capability_binding_violations: [],
|
||||||
|
answer_shape_contract: {
|
||||||
|
reply_type: "factual",
|
||||||
|
capability_contract_id: "address_list_documents_by_counterparty"
|
||||||
|
},
|
||||||
|
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
should_run_discovery: true,
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "movements",
|
||||||
|
asked_action_family: "list_movements",
|
||||||
|
explicit_entity_candidates: ["SVK"],
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.applied).toBe(true);
|
||||||
|
expect(result.decision).toBe("apply_candidate");
|
||||||
|
expect(result.reply_text).toContain("Confirmed fact");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps exact matched inventory address replies over stale metadata discovery candidates", () => {
|
it("keeps exact matched inventory address replies over stale metadata discovery candidates", () => {
|
||||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
currentReply: "По товару Шкаф картотечный 1000*400*2100 цепочка поставки и продажи подтверждена.",
|
currentReply: "По товару Шкаф картотечный 1000*400*2100 цепочка поставки и продажи подтверждена.",
|
||||||
|
|
@ -209,6 +248,62 @@ describe("assistant MCP discovery response policy", () => {
|
||||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
|
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("lets metadata discovery override a stale exact address answer outside protected inventory chains", () => {
|
||||||
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
|
currentReply: "РРџ Калинин Рќ.Рњ. | СЃСѓРјРјР°: 216600",
|
||||||
|
currentReplySource: "address_query_runtime_v1",
|
||||||
|
currentReplyType: "factual",
|
||||||
|
addressRuntimeMeta: {
|
||||||
|
detected_intent: "customer_revenue_and_payments",
|
||||||
|
selected_recipe: "address_customer_revenue_and_payments_v1",
|
||||||
|
mcp_call_status: "matched_non_empty",
|
||||||
|
truth_mode: "confirmed",
|
||||||
|
capability_binding_status: "bound",
|
||||||
|
capability_binding_violations: [],
|
||||||
|
answer_shape_contract: {
|
||||||
|
reply_type: "factual",
|
||||||
|
capability_contract_id: "address_customer_revenue_and_payments"
|
||||||
|
},
|
||||||
|
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
should_run_discovery: true,
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "metadata",
|
||||||
|
asked_action_family: "inspect_surface",
|
||||||
|
unsupported_but_understood_family: "1c_metadata_surface"
|
||||||
|
},
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "schema_surface",
|
||||||
|
decomposition_candidates: ["inspect_metadata_surface"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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: "Metadata surface confirmed.",
|
||||||
|
confirmed_lines: ["Available metadata object sets: Catalog.Counterparties"],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: [],
|
||||||
|
limitation_lines: [],
|
||||||
|
next_step_line: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.applied).toBe(true);
|
||||||
|
expect(result.decision).toBe("apply_candidate");
|
||||||
|
expect(result.reply_text).toContain("Catalog.Counterparties");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_response_policy_metadata_candidate_priority");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps aligned factual address lane answers when the exact lane already matched the same semantic intent", () => {
|
it("keeps aligned factual address lane answers when the exact lane already matched the same semantic intent", () => {
|
||||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
currentReply: "ИП Калинин Н.М. | сумма: 216600 | операций: 2",
|
currentReply: "ИП Калинин Н.М. | сумма: 216600 | операций: 2",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { buildAssistantMcpDiscoveryTurnInput } from "../src/services/assistantMcpDiscoveryTurnInputAdapter";
|
||||||
|
|
||||||
|
describe("assistant MCP discovery turn input adapter garbage entity regressions", () => {
|
||||||
|
it("does not treat document and movement lane words as counterparty candidates", () => {
|
||||||
|
const followupContext = {
|
||||||
|
previous_intent: "customer_revenue_and_payments" as const,
|
||||||
|
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
|
||||||
|
previous_filters: {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
organization: "ООО Альтернатива Плюс",
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
},
|
||||||
|
previous_anchor_type: "counterparty" as const,
|
||||||
|
previous_anchor_value: "Группа СВК"
|
||||||
|
};
|
||||||
|
|
||||||
|
const documents = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "а по документам?",
|
||||||
|
followupContext
|
||||||
|
});
|
||||||
|
const movements = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "а по движениям?",
|
||||||
|
followupContext
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(documents.turn_meaning_ref?.explicit_entity_candidates).toEqual(["Группа СВК"]);
|
||||||
|
expect(movements.turn_meaning_ref?.explicit_entity_candidates).toEqual(["Группа СВК"]);
|
||||||
|
expect(documents.turn_meaning_ref?.explicit_entity_candidates).not.toContain("документам");
|
||||||
|
expect(movements.turn_meaning_ref?.explicit_entity_candidates).not.toContain("движениям");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -56,6 +56,51 @@ describe("assistantMetaFollowupPolicy", () => {
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("detects 1C schema/catalog questions as data-scope meta even when base detector misses them", () => {
|
||||||
|
const signals = policy.resolveMetaSignalSet({
|
||||||
|
rawUserMessage:
|
||||||
|
"\u041a\u0430\u043a\u0438\u0435 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438 1\u0421 \u0435\u0441\u0442\u044c \u043f\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430\u043c?",
|
||||||
|
repairedRawUserMessage: "",
|
||||||
|
effectiveAddressUserMessage: "",
|
||||||
|
repairedEffectiveAddressUserMessage: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(signals.dataScopeMetaQuery).toBe(true);
|
||||||
|
expect(signals.capabilityMetaQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects document field/link questions as schema meta rather than fake counterparty documents", () => {
|
||||||
|
const signals = policy.resolveMetaSignalSet({
|
||||||
|
rawUserMessage:
|
||||||
|
"\u041a\u0430\u043a\u0438\u0435 \u043f\u043e\u043b\u044f \u0438 \u0441\u0432\u044f\u0437\u0438 \u0441\u0442\u043e\u0438\u0442 \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0443 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u044f?",
|
||||||
|
repairedRawUserMessage: "",
|
||||||
|
effectiveAddressUserMessage: "",
|
||||||
|
repairedEffectiveAddressUserMessage: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(signals.dataScopeMetaQuery).toBe(true);
|
||||||
|
expect(signals.capabilityMetaQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not turn debt classification wording into capability meta help", () => {
|
||||||
|
const noisyPolicy = createAssistantMetaFollowupPolicy({
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal: () => false,
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery: () => true,
|
||||||
|
hasMetaAnswerFollowupSignal: () => false,
|
||||||
|
hasAnswerInspectionFollowupSignal: () => false
|
||||||
|
});
|
||||||
|
|
||||||
|
const signals = noisyPolicy.resolveMetaSignalSet({
|
||||||
|
rawUserMessage:
|
||||||
|
"\u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a\u043e\u0439 \u0438\u043b\u0438 \u043f\u043e\u043a\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u0437\u0430\u0434\u043e\u043b\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u044e?",
|
||||||
|
repairedRawUserMessage: "",
|
||||||
|
effectiveAddressUserMessage: "",
|
||||||
|
repairedEffectiveAddressUserMessage: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(signals.capabilityMetaQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("detects evaluative meta follow-up over grounded answer", () => {
|
it("detects evaluative meta follow-up over grounded answer", () => {
|
||||||
const detected = policy.isMetaFollowupOverGroundedAnswer({
|
const detected = policy.isMetaFollowupOverGroundedAnswer({
|
||||||
followupContext: { previous_intent: "vat_payable_forecast" },
|
followupContext: { previous_intent: "vat_payable_forecast" },
|
||||||
|
|
|
||||||
|
|
@ -227,4 +227,50 @@ describe("assistantTurnMeaningPolicy", () => {
|
||||||
expect(supplierQuality.unsupported_but_understood_family).toBe("broad_business_evaluation");
|
expect(supplierQuality.unsupported_but_understood_family).toBe("broad_business_evaluation");
|
||||||
expect(supplierQuality.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
expect(supplierQuality.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps selected-object profitability as an inventory exact intent instead of company overview", () => {
|
||||||
|
const policy = buildPolicy();
|
||||||
|
|
||||||
|
const meaning = policy.resolveAssistantTurnMeaning({
|
||||||
|
rawUserMessage:
|
||||||
|
'\u041f\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u043c\u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0443 "\u0427\u0435\u0442\u043a\u0438 \u041f\u043e\u0441\u0442 (84*117)": \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u043d\u0430 \u043f\u0440\u043e\u0434\u0430\u0436\u0435, \u043a\u0430\u043a\u0438\u0435 \u0437\u0430\u043a\u0443\u043f\u043e\u0447\u043d\u044b\u0435 \u0438 \u043f\u0440\u043e\u0434\u0430\u0436\u043d\u044b\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u044d\u0442\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044e\u0442?'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(meaning.explicit_intent_candidate).toBe("inventory_profitability_for_item");
|
||||||
|
expect(meaning.asked_domain_family).toBe("inventory");
|
||||||
|
expect(meaning.unsupported_but_understood_family).toBeNull();
|
||||||
|
expect(meaning.stale_replay_forbidden).toBe(false);
|
||||||
|
expect(meaning.reason_codes).not.toContain("broad_business_evaluation_current_turn_signal");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats paired receivables/payables position as business overview instead of one debt side", () => {
|
||||||
|
const policy = buildPolicy();
|
||||||
|
|
||||||
|
const meaning = policy.resolveAssistantTurnMeaning({
|
||||||
|
rawUserMessage:
|
||||||
|
"\u041a\u0430\u043a\u0430\u044f \u0434\u0435\u0431\u0438\u0442\u043e\u0440\u043a\u0430 \u0438 \u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440\u043a\u0430 \u043d\u0430 30.09.2020 \u043f\u043e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438?"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(meaning.explicit_intent_candidate).toBeNull();
|
||||||
|
expect(meaning.asked_domain_family).toBe("business_summary");
|
||||||
|
expect(meaning.asked_action_family).toBe("broad_evaluation");
|
||||||
|
expect(meaning.unsupported_but_understood_family).toBe("broad_business_evaluation");
|
||||||
|
expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats debt overdue classification follow-up as business interpretation, not capability help", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
resolveAddressIntent: () => ({ intent: "unknown", confidence: "low" })
|
||||||
|
});
|
||||||
|
|
||||||
|
const meaning = policy.resolveAssistantTurnMeaning({
|
||||||
|
rawUserMessage:
|
||||||
|
"\u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a\u043e\u0439 \u0438\u043b\u0438 \u043f\u043e\u043a\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u0437\u0430\u0434\u043e\u043b\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u044e?"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(meaning.explicit_intent_candidate).toBeNull();
|
||||||
|
expect(meaning.asked_domain_family).toBe("business_summary");
|
||||||
|
expect(meaning.asked_action_family).toBe("broad_evaluation");
|
||||||
|
expect(meaning.unsupported_but_understood_family).toBe("broad_business_evaluation");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue