Укрепить exact value-flow аналитику адресного контура
This commit is contained in:
@@ -5,6 +5,7 @@ const ACCOUNT_PATTERN = /(?:сч[её]т|счет|account)[^0-9]{0,12}(\d{2}(?:[
|
||||
const ACCOUNT_REVERSE_PATTERN =
|
||||
/(?:^|[\s,.;:!?()\-])(\d{2}(?:[.,]\d{1,2})?)(?=\s*(?:сч[её]т|счет|account|acct))/iu;
|
||||
const LIMIT_PATTERN = /(?:\btop\b|\blimit\b|первые|топ)[\s\-–—_:№#]*?(\d{1,3})/iu;
|
||||
const VALUE_ANALYTICS_SAMPLE_LIMIT = 1000;
|
||||
const COUNTERPARTY_PATTERN =
|
||||
/(?:по\s+контрагенту|контрагент(?:у|а)?|по\s+контре|контра|по\s+компан(?:ии|ию|ия)|компан(?:ия|ии|ию)|по\s+организац(?:ии|ию|ия)|организац(?:ия|ии|ию)|по\s+поставщик(?:у|а)?|поставщик(?:у|а)?|по\s+клиент(?:у|а)?|клиент(?:у|а)?|по\s+покупател(?:ю|я)|покупател(?:ю|я)|по\s+партнер(?:у|а)?|партнер(?:у|а)?|by\s+counterparty|counterparty|by\s+company|company|by\s+supplier|supplier|by\s+vendor|vendor|by\s+customer|customer|by\s+client|client|by\s+partner|partner)\s+([^\r\n,.;:]+)/iu;
|
||||
const CONTRACT_PATTERN =
|
||||
@@ -1705,6 +1706,14 @@ function buildSemanticFrame(
|
||||
};
|
||||
}
|
||||
|
||||
function shouldExpandSampleForValueAnalytics(intent: AddressIntent): boolean {
|
||||
return (
|
||||
intent === "customer_revenue_and_payments" ||
|
||||
intent === "supplier_payouts_profile" ||
|
||||
intent === "contract_usage_and_value"
|
||||
);
|
||||
}
|
||||
|
||||
export function extractAddressFilters(userMessage: string, intent: AddressIntent): AddressFilterExtraction {
|
||||
const rawText = String(userMessage ?? "").trim();
|
||||
const text = normalizeMojibakeString(rawText);
|
||||
@@ -1753,6 +1762,12 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
|
||||
filters.limit = Math.min(200, Math.trunc(parsed));
|
||||
}
|
||||
}
|
||||
if (shouldExpandSampleForValueAnalytics(intent)) {
|
||||
const currentLimit =
|
||||
typeof filters.limit === "number" && Number.isFinite(filters.limit) ? Math.max(1, Math.trunc(filters.limit)) : 0;
|
||||
filters.limit = Math.max(currentLimit, VALUE_ANALYTICS_SAMPLE_LIMIT);
|
||||
warnings.push("value_analytics_sample_limit_expanded");
|
||||
}
|
||||
|
||||
if (isInventoryItemAnchoredIntent(intent)) {
|
||||
const itemAnchor = extractInventoryItemAnchor(text);
|
||||
|
||||
@@ -2768,7 +2768,23 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
|
||||
|
||||
const unicodeAddressIntent = resolveUnicodeAddressIntentBridge(currentTurnBridgeText);
|
||||
if (unicodeAddressIntent) {
|
||||
return unicodeAddressIntent;
|
||||
const reasons = [...unicodeAddressIntent.reasons];
|
||||
if (currentTurnBridgeText !== bridgeText && !reasons.includes("current_turn_noise_normalized")) {
|
||||
reasons.push("current_turn_noise_normalized");
|
||||
}
|
||||
if (
|
||||
unicodeAddressIntent.intent === "customer_revenue_and_payments" &&
|
||||
[text, repairedText, turnNoiseNormalizedBridgeText, currentTurnBridgeText].some((sample) =>
|
||||
hasSpecificCounterpartyRevenueBridgeSignal(sample)
|
||||
) &&
|
||||
!reasons.includes("specific_counterparty_revenue_bridge_signal_detected")
|
||||
) {
|
||||
reasons.push("specific_counterparty_revenue_bridge_signal_detected");
|
||||
}
|
||||
return {
|
||||
...unicodeAddressIntent,
|
||||
reasons
|
||||
};
|
||||
}
|
||||
|
||||
const hasLooseVatPayableBridge =
|
||||
|
||||
+15
-6
@@ -787,15 +787,24 @@ export function composeCounterpartyAnalyticsReply(
|
||||
}
|
||||
|
||||
const visible = rankedByTotal.slice(0, limit);
|
||||
const heading = isSupplier
|
||||
? `Топ-${visible.length} поставщиков по сумме выплат:`
|
||||
: `Топ-${visible.length} заказчиков по сумме поступлений:`;
|
||||
const singleCandidateOnly = rankedByTotal.length === 1;
|
||||
const heading = singleCandidateOnly
|
||||
? isSupplier
|
||||
? "Найденный поставщик по сумме выплат:"
|
||||
: "Найденный заказчик по сумме поступлений:"
|
||||
: isSupplier
|
||||
? `Топ-${visible.length} поставщиков по сумме выплат:`
|
||||
: `Топ-${visible.length} заказчиков по сумме поступлений:`;
|
||||
const leadingCounterparty = visible[0] ?? null;
|
||||
lines.unshift(heading);
|
||||
if (leadingCounterparty) {
|
||||
const directAnswerLine = isSupplier
|
||||
? `Крупнейший поставщик по подтвержденным выплатам за доступное время: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям).`
|
||||
: `Самый доходный клиент за доступное время по подтвержденным поступлениям: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это денежный поток, а не чистая прибыль.`;
|
||||
const directAnswerLine = singleCandidateOnly
|
||||
? isSupplier
|
||||
? `В выбранном срезе найден один поставщик: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это не полноценный сравнительный рейтинг.`
|
||||
: `В выбранном срезе найден один клиент: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это не полноценный сравнительный рейтинг; сумма является денежным потоком, а не чистой прибылью.`
|
||||
: isSupplier
|
||||
? `Крупнейший поставщик по подтвержденным выплатам за доступное время: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям).`
|
||||
: `Самый доходный клиент за доступное время по подтвержденным поступлениям: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это денежный поток, а не чистая прибыль.`;
|
||||
lines.unshift(directAnswerLine);
|
||||
}
|
||||
lines.push(
|
||||
|
||||
@@ -1053,6 +1053,24 @@ export function createAssistantRoutePolicy(deps) {
|
||||
}
|
||||
const metaAnswerFollowupSignal = metaSignals.metaAnswerFollowupSignal;
|
||||
const answerInspectionFollowupSignal = metaSignals.answerInspectionFollowupSignal;
|
||||
const customerValueRankingAddressSignal = [
|
||||
rawUserMessage,
|
||||
effectiveAddressUserMessage,
|
||||
repairedRawUserMessage,
|
||||
repairedEffectiveAddressUserMessage
|
||||
].some((value) => {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(value ?? "")).toLowerCase()).replace(/ё/g, "е");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (capabilityMetaQuery || dataScopeMetaQuery) {
|
||||
return false;
|
||||
}
|
||||
const hasRankingCue = /(?:сам(?:ый|ая|ое|ые)|топ|рейтинг|больше\s+всего|максимальн|лидер|highest|top|best)/iu.test(normalized);
|
||||
const hasValueCue = /(?:доход|выруч|оборот|денег|принес|поступлен|revenue|turnover|value|money)/iu.test(normalized);
|
||||
const hasCustomerCue = /(?:клиент|покупател|контрагент|customer|counterparty|кто\s+у\s+нас|кто\s+нам|кто\s+больше)/iu.test(normalized);
|
||||
return hasRankingCue && hasValueCue && hasCustomerCue;
|
||||
});
|
||||
const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected &&
|
||||
llmPreDecomposeMeta?.applied &&
|
||||
llmContractMode === "address_query") ||
|
||||
@@ -1064,6 +1082,7 @@ export function createAssistantRoutePolicy(deps) {
|
||||
hasLooseAllTimeAddressLookupSignal(effectiveAddressUserMessage) ||
|
||||
hasLooseAllTimeAddressLookupSignal(repairedRawUserMessage) ||
|
||||
hasLooseAllTimeAddressLookupSignal(repairedEffectiveAddressUserMessage) ||
|
||||
customerValueRankingAddressSignal ||
|
||||
hasAddressFollowupContextSignal(rawUserMessage) ||
|
||||
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
||||
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
||||
@@ -1108,7 +1127,9 @@ export function createAssistantRoutePolicy(deps) {
|
||||
resolvedIntentResolution.intent === "unknown" &&
|
||||
(!llmContractIntent || llmContractIntent === "unknown"));
|
||||
const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint;
|
||||
const protectAddressLaneFromFallback = laneProtectionArbitration.protectAddressLaneFromFallback;
|
||||
const protectAddressLaneFromFallback = Boolean(
|
||||
laneProtectionArbitration.protectAddressLaneFromFallback || customerValueRankingAddressSignal
|
||||
);
|
||||
const vatExplainFollowupSignal = Boolean(followupContext &&
|
||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||
|
||||
Reference in New Issue
Block a user