ARCH: включить оборот конкретного контрагента в revenue-контур

This commit is contained in:
2026-04-19 21:32:07 +03:00
parent b339a8f8ca
commit f75be32e41
12 changed files with 296 additions and 8 deletions
@@ -528,6 +528,25 @@ function extractLooseByAnchorValue(text) {
}
return normalizedToken;
}
function extractSpecificCounterpartyRevenueAnchor(text) {
const source = String(text ?? "");
const normalized = source.trim().toLowerCase();
if (!normalized) {
return undefined;
}
const hasRevenueCue = /(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\w*|\u0434\u043e\u0445\u043e\u0434\w*|revenue|turnover)/iu.test(normalized);
const hasExcludedCue = /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|supplier|vendor|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0434\u043e\u043a\u0438|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u0431\u0430\u043d\u043a|\u043f\u043b\u0430\u0442\u0435\u0436|docs?|documents?|contract|bank|payment)/iu.test(normalized);
if (!hasRevenueCue || hasExcludedCue) {
return undefined;
}
const match = source.match(/(?:^|[\s,.;:!?])(?:\u043f\u043e|by|for)\s+([\p{L}\d][\p{L}\d._-]{1,})\s*$/iu) ??
source.match(/(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\w*|\u0434\u043e\u0445\u043e\u0434\w*|revenue|turnover)\s+(?:(?:\u0431\u044b\u043b(?:\u0430|\u043e)?|was)\s+)?(?:(?:\u0443|\u043f\u043e|by|for)\s+)?([\p{L}\d][\p{L}\d._-]{1,})\s*$/iu);
const token = cleanupAnchorValue(String(match?.[1] ?? ""));
if (!token || !hasStrongCounterpartyTokenShape(token) || !isLikelyCounterpartyToken(token)) {
return undefined;
}
return token;
}
function extractContractTokenHeuristic(text) {
const source = String(text ?? "");
const explicit = source.match(/(?:№|#|n)\s*([a-zа-яё0-9][a-zа-яё0-9./_-]{1,})/iu);
@@ -1494,6 +1513,13 @@ function extractAddressFilters(userMessage, intent) {
if (counterpartyMatch && !filters.counterparty) {
filters.counterparty = cleanupAnchorValue(String(counterpartyMatch[1]));
}
if (!filters.counterparty && allowGenericCounterpartyAnchor && intent === "customer_revenue_and_payments") {
const revenueCounterparty = extractSpecificCounterpartyRevenueAnchor(text);
if (revenueCounterparty) {
filters.counterparty = cleanupAnchorValue(revenueCounterparty);
warnings.push("counterparty_anchor_derived_from_revenue_phrase");
}
}
if (!filters.counterparty &&
allowGenericCounterpartyAnchor &&
(intent === "list_documents_by_counterparty" ||
@@ -1467,6 +1467,39 @@ function hasCustomerRevenueRankingBridgeSignal(text) {
const hasRevenueAggregateCue = /(?:(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f)\s+(?:\u0443\s+\u043d\u0430\u0441\s+)?\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0434\u043e\u0445\u043e\u0434\u043d\w*\s+\u0433\u043e\u0434|(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|скок)\s+(?:\u0432\u043e\u043e\u0431\u0449\u0435\s+)?(?:\u0434\u0435\u043d\u0435\u0433\s+)?\u043c\u044b\s+\u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438|(?:\u0437\u0430|for)\s+\d{4}\s+\u043c\u044b\s+(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|скок)\s+\u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438|\u0432\u044b\u0440\u0443\u0447\u043a\w*\s+\u0437\u0430\s+\d{4})/iu.test(normalized);
return hasCustomerRankingCue || hasRevenueAggregateCue;
}
function hasSpecificCounterpartyRevenueBridgeSignal(text) {
const normalized = String(text ?? "").trim().toLowerCase();
if (!normalized) {
return false;
}
const hasSupplierCue = /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0432\u0435\u043d\u0434\u043e\u0440|supplier|vendor)/iu.test(normalized);
const hasNonRevenueEntityCue = /(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0434\u043e\u043a\u0438|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u0431\u0430\u043d\u043a|\u043f\u043b\u0430\u0442\u0435\u0436|docs?|documents?|contract|bank|payment)/iu.test(normalized);
if (hasSupplierCue || hasNonRevenueEntityCue) {
return false;
}
const hasRevenueCue = /(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\w*|\u0434\u043e\u0445\u043e\u0434\w*|revenue|turnover)/iu.test(normalized);
if (!hasRevenueCue) {
return false;
}
const explicitEntityMatch = normalized.match(/(?:^|[\s,.;:!?])(?:\u043f\u043e|by|for)\s+([\p{L}\d][\p{L}\d._-]{1,})\s*$/iu) ??
normalized.match(/(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\w*|\u0434\u043e\u0445\u043e\u0434\w*|revenue|turnover)\s+(?:(?:\u0431\u044b\u043b(?:\u0430|\u043e)?|was)\s+)?(?:(?:\u0443|\u043f\u043e|by|for)\s+)?([\p{L}\d][\p{L}\d._-]{1,})\s*$/iu);
const entity = explicitEntityMatch?.[1] ? String(explicitEntityMatch[1]).toLowerCase() : null;
if (!entity || /^\d+$/.test(entity)) {
return false;
}
const ignoredEntityTails = new Set([
"\u043d\u0430\u043c",
"\u043d\u0430\u0441",
"\u0432\u0441\u0435",
"\u0432\u0441\u0435\u043c",
"\u0433\u043e\u0434",
"\u0433\u043e\u0434\u0430",
"\u043c\u0435\u0441\u044f\u0446",
"year",
"month"
]);
return !ignoredEntityTails.has(entity);
}
function hasInventoryProvenanceBridgeSignal(text) {
const normalized = String(text ?? "").trim().toLowerCase();
if (!normalized) {
@@ -1565,11 +1598,18 @@ function resolveAddressIntent(userMessage) {
};
}
const hasDirectRevenueAggregateBridge = /(?:\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0434\u043e\u0445\u043e\u0434\u043d(?:\u044b\u0439|\u0430\u044f|\u043e\u0435|\u044b\u0435|\u043e\u0433\u043e|\u043e\u043c\u0443|\u044b\u043c|\u044b\u0445)?\s+\u043a\u043b\u0438\u0435\u043d\u0442|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f)\s+(?:\u0443\s+\u043d\u0430\u0441\s+)?\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0434\u043e\u0445\u043e\u0434\u043d(?:\u044b\u0439|\u0430\u044f|\u043e\u0435|\u044b\u0435|\u043e\u0433\u043e|\u043e\u043c\u0443|\u044b\u043c|\u044b\u0445)?\s+\u0433\u043e\u0434|(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a).*(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438|\u0432\u044b\u0440\u0443\u0447\u0438\u043b\u0438)|\u0432\u044b\u0440\u0443\u0447\u043a\u0430\s+\u0437\u0430\s+\d{4})/iu.test(bridgeText);
if (hasDirectRevenueAggregateBridge || hasCustomerRevenueRankingBridgeSignal(bridgeText)) {
const hasSpecificCounterpartyRevenueBridge = [text, repairedText, turnNoiseNormalizedBridgeText, currentTurnBridgeText].some((sample) => hasSpecificCounterpartyRevenueBridgeSignal(sample));
if (hasDirectRevenueAggregateBridge ||
hasCustomerRevenueRankingBridgeSignal(bridgeText) ||
hasSpecificCounterpartyRevenueBridge) {
return {
intent: "customer_revenue_and_payments",
confidence: "medium",
reasons: ["customer_revenue_ranking_bridge_signal_detected"]
reasons: [
hasSpecificCounterpartyRevenueBridge
? "specific_counterparty_revenue_bridge_signal_detected"
: "customer_revenue_ranking_bridge_signal_detected"
]
};
}
const hasHistoricalInventorySnapshotBridge = [text, repairedText, bridgeText].some((sample) => /(?:\u043e\u0441\u0442\u0430\u0442|inventory|stock|\u0441\u043a\u043b\u0430\u0434|остат|склад)/iu.test(sample) &&
@@ -400,6 +400,30 @@ function createAssistantTransitionPolicy(deps) {
? deps.hasFollowupMarker(String(alternateMessage ?? "")) ||
deps.hasReferentialPointer(String(alternateMessage ?? ""))
: false);
const hasConcreteFollowupReference = hasPrimaryIndexReferenceSignal ||
hasAlternateIndexReferenceSignal ||
hasOrganizationClarificationContinuation ||
inventoryShortFollowupPrimary ||
inventoryShortFollowupAlternate ||
hasInventoryRootTemporalFollowupPrimary ||
hasInventoryRootTemporalFollowupAlternate ||
hasInventoryRootRestatementPrimary ||
hasInventoryRootRestatementAlternate ||
inventoryPurchaseDateVatBridge ||
Boolean(debtRoleSwapIntent) ||
deps.hasFollowupMarker(userMessage) ||
deps.hasReferentialPointer(userMessage) ||
(deps.toNonEmptyString(alternateMessage)
? deps.hasFollowupMarker(String(alternateMessage ?? "")) ||
deps.hasReferentialPointer(String(alternateMessage ?? ""))
: false);
const hasCurrentTurnExplicitEntity = Array.isArray(assistantTurnMeaning?.explicit_entity_candidates) &&
assistantTurnMeaning.explicit_entity_candidates.length > 0;
if (assistantTurnMeaning?.intent_override_strength === "explicit_current_turn_intent" &&
hasCurrentTurnExplicitEntity &&
!hasConcreteFollowupReference) {
return null;
}
const hasStandaloneAddressTopic = deps.hasStandaloneAddressTopicSignal(userMessage) ||
(deps.toNonEmptyString(alternateMessage) ? deps.hasStandaloneAddressTopicSignal(alternateMessage) : false);
if (hasStandaloneAddressTopic &&
@@ -6,6 +6,7 @@ const SUPPORTED_ADDRESS_INTENTS = new Set([
"receivables_confirmed_as_of_date",
"payables_confirmed_as_of_date",
"list_documents_by_counterparty",
"customer_revenue_and_payments",
"inventory_on_hand_as_of_date"
]);
function fallbackCompactWhitespace(value) {