ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Спека exact-маршрута payables на дату: confirmed_balance без эвристического финала
This commit is contained in:
@@ -300,6 +300,10 @@ const CUSTOMER_REVENUE_AND_PAYMENTS_HINTS = [
|
||||
"самые доходные заказчики",
|
||||
"топ клиентов по сумме поступлений",
|
||||
"топ заказчиков по сумме поступлений",
|
||||
"кто больше всего принес денег",
|
||||
"кто больше всего принёс денег",
|
||||
"кто принес больше всего денег",
|
||||
"кто принёс больше всего денег",
|
||||
"кто нам больше всего занес",
|
||||
"кто нам больше всего занёс",
|
||||
"кто нам принес больше всего",
|
||||
@@ -685,6 +689,7 @@ function hasCustomerRevenueAndPaymentsSignal(text) {
|
||||
asksWhoPays;
|
||||
const asksCounterpartySource = /(?:с\s+каких|от\s+каких|от\s+кого|from\s+which|from\s+who)/iu.test(text);
|
||||
const asksIncomingFlow = /(?:приход|поступлен|входящ|зачислен|inflow|incoming)/iu.test(text);
|
||||
const asksWhoBringsMostMoney = /(?:кто\s+(?:нам\s+)?(?:больше\s+всего|сам(?:ый|ая|ое|ые)|наибольш(?:ий|ая|ее|ие))\s+(?:прин[её]с|зан[её]с).*(?:деньг|денег))/iu.test(text);
|
||||
const asksDealBudgetRanking = /(?:сделк|deal|бюджет)/iu.test(text) &&
|
||||
/(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн|минимальн)/iu.test(text);
|
||||
const asksRevenueTotal = /(?:сколько|скока|скок).*(?:денег|выручк|доход|заработ|оборот)/iu.test(text);
|
||||
@@ -708,6 +713,9 @@ function hasCustomerRevenueAndPaymentsSignal(text) {
|
||||
if (!hasFuzzySupplierLexeme && asksWhoPays && (asksRankOrTop || hasCounterpartyLexeme)) {
|
||||
return true;
|
||||
}
|
||||
if (!hasFuzzySupplierLexeme && asksWhoBringsMostMoney) {
|
||||
return true;
|
||||
}
|
||||
if (!hasFuzzySupplierLexeme && (asksRevenueTotal || asksOverallTurnover)) {
|
||||
return true;
|
||||
}
|
||||
@@ -823,6 +831,18 @@ function hasSupplierTailRiskSignal(text) {
|
||||
const hasPeriodCue = /(?:на\s+конец\s+(?:месяц|период)|конец\s+месяц|пару\s+месяц|несколько\s+месяц|больше\s+месяц)/iu.test(text);
|
||||
return hasSupplier && hasTail && (hasRisk || hasPeriodCue);
|
||||
}
|
||||
function hasPayablesDebtLifecycleSignal(text) {
|
||||
const hasOweSignal = /(?:кому\s+мы\s+должны|мы\s+должны|кому\s+должны|должн(?:ы|а|о)\s+(?:заплат|оплат|перечис)|к\s+оплате|на\s+оплату|who\s+we\s+owe|owe\s+to|payables?|кредитор(?:ск)?)/iu.test(text);
|
||||
if (!hasOweSignal) {
|
||||
return false;
|
||||
}
|
||||
const hasPastPaymentSignal = /(?:заплатил(?:и)?|платил(?:и)?|кому\s+ушло|выплатил(?:и)?|списан|outflow|payout)/iu.test(text);
|
||||
const hasTopRankingSignal = /(?:топ|top|больше\s+всего|сам(?:ый|ая|ое|ые)|наибольш|максимальн)/iu.test(text);
|
||||
if (hasPastPaymentSignal && hasTopRankingSignal) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function hasReceivablesLatencyRiskSignal(text) {
|
||||
const hasBuyer = /(?:покупател|клиент|заказчик|customer|buyer)/iu.test(text);
|
||||
const hasCounterparty = /(?:контрагент|counterparty|partner)/iu.test(text);
|
||||
@@ -1204,10 +1224,14 @@ function resolveAddressIntent(userMessage) {
|
||||
};
|
||||
}
|
||||
if (hasAny(text, PAYABLES_STRONG)) {
|
||||
const reasons = ["payables_signal_detected"];
|
||||
if (hasPayablesDebtLifecycleSignal(text)) {
|
||||
reasons.push("payables_debt_lifecycle_signal_detected");
|
||||
}
|
||||
return {
|
||||
intent: "list_payables_counterparties",
|
||||
confidence: "high",
|
||||
reasons: ["payables_signal_detected"]
|
||||
reasons
|
||||
};
|
||||
}
|
||||
if (hasSettlementGapSignal(text)) {
|
||||
@@ -1242,7 +1266,7 @@ function resolveAddressIntent(userMessage) {
|
||||
return {
|
||||
intent: "list_payables_counterparties",
|
||||
confidence: "medium",
|
||||
reasons: ["supplier_tail_risk_signal_detected"]
|
||||
reasons: ["supplier_tail_risk_signal_detected", "payables_debt_lifecycle_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasDocumentsFormingBalanceSignal(text) && hasDocumentsFormingBalanceAccountAnchor(text)) {
|
||||
|
||||
+151
-1
@@ -631,6 +631,92 @@ function isCounterpartyRiskIntent(intent) {
|
||||
intent === "list_open_contracts" ||
|
||||
intent === "open_items_by_counterparty_or_contract");
|
||||
}
|
||||
function isHeuristicCandidatesIntent(intent) {
|
||||
return (intent === "list_receivables_counterparties" ||
|
||||
intent === "list_payables_counterparties" ||
|
||||
intent === "list_open_contracts" ||
|
||||
intent === "open_items_by_counterparty_or_contract");
|
||||
}
|
||||
function isConfirmedBalanceIntent(intent) {
|
||||
return intent === "account_balance_snapshot" || intent === "documents_forming_balance";
|
||||
}
|
||||
function resolveAsOfDateBasis(filters) {
|
||||
const asOfDate = normalizeAnalysisDateHint(filters.as_of_date);
|
||||
if (asOfDate) {
|
||||
return "explicit_as_of_date";
|
||||
}
|
||||
const periodFrom = normalizeAnalysisDateHint(filters.period_from);
|
||||
const periodTo = normalizeAnalysisDateHint(filters.period_to);
|
||||
if (periodFrom && periodTo) {
|
||||
return "period_range";
|
||||
}
|
||||
if (!periodFrom && periodTo) {
|
||||
return "period_end";
|
||||
}
|
||||
if (periodFrom) {
|
||||
return "period_range";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function deriveAddressEvidenceStrength(input) {
|
||||
if (isHeuristicCandidatesIntent(input.intent)) {
|
||||
if (input.rowsMatched <= 0 || input.responseType === "LIMITED_WITH_REASON") {
|
||||
return "weak";
|
||||
}
|
||||
if (input.selectedRecipe === "address_open_items_by_party_or_contract_v1") {
|
||||
return "medium";
|
||||
}
|
||||
return "weak";
|
||||
}
|
||||
if (isConfirmedBalanceIntent(input.intent)) {
|
||||
if (input.rowsMatched > 0) {
|
||||
return "strong";
|
||||
}
|
||||
return input.responseType === "LIMITED_WITH_REASON" ? "weak" : "medium";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function resolveRequestedResultMode(intent, filters) {
|
||||
if (isConfirmedBalanceIntent(intent)) {
|
||||
return "confirmed_balance";
|
||||
}
|
||||
if (isHeuristicCandidatesIntent(intent)) {
|
||||
const asOfDateBasis = resolveAsOfDateBasis(filters);
|
||||
if (asOfDateBasis === "explicit_as_of_date" || asOfDateBasis === "period_end" || asOfDateBasis === "period_range") {
|
||||
return "confirmed_balance";
|
||||
}
|
||||
return "heuristic_candidates";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function deriveAddressResultSemantics(input) {
|
||||
const asOfDateBasis = resolveAsOfDateBasis(input.filters);
|
||||
const requestedResultMode = resolveRequestedResultMode(input.intent, input.filters);
|
||||
if (isHeuristicCandidatesIntent(input.intent)) {
|
||||
return {
|
||||
requested_result_mode: requestedResultMode,
|
||||
result_mode: "heuristic_candidates",
|
||||
evidence_strength: deriveAddressEvidenceStrength(input),
|
||||
balance_confirmed: false,
|
||||
as_of_date_basis: asOfDateBasis
|
||||
};
|
||||
}
|
||||
if (isConfirmedBalanceIntent(input.intent)) {
|
||||
return {
|
||||
requested_result_mode: requestedResultMode,
|
||||
result_mode: "confirmed_balance",
|
||||
evidence_strength: deriveAddressEvidenceStrength(input),
|
||||
balance_confirmed: true,
|
||||
as_of_date_basis: asOfDateBasis ?? "period_end"
|
||||
};
|
||||
}
|
||||
if (requestedResultMode) {
|
||||
return {
|
||||
requested_result_mode: requestedResultMode
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
function resolveFutureGuardReferenceDate(analysisDate, filters) {
|
||||
if (analysisDate) {
|
||||
return analysisDate;
|
||||
@@ -1196,6 +1282,13 @@ function composeLimitedReply(input) {
|
||||
}
|
||||
function buildLimitedExecutionResult(input) {
|
||||
const accountScopeAudit = input.accountScopeAudit ?? buildDefaultAccountScopeAudit(input.filters);
|
||||
const resultSemantics = deriveAddressResultSemantics({
|
||||
intent: input.intent.intent,
|
||||
selectedRecipe: input.selectedRecipe,
|
||||
filters: input.filters,
|
||||
responseType: "LIMITED_WITH_REASON",
|
||||
rowsMatched: input.rowsMatched
|
||||
});
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: composeLimitedReply({
|
||||
@@ -1246,6 +1339,7 @@ function buildLimitedExecutionResult(input) {
|
||||
runtime_readiness: runtimeReadinessForLimitedCategory(input.category),
|
||||
limited_reason_category: input.category,
|
||||
response_type: "LIMITED_WITH_REASON",
|
||||
...resultSemantics,
|
||||
limitations: input.limitations,
|
||||
reasons: input.reasons
|
||||
}
|
||||
@@ -1288,11 +1382,25 @@ class AddressQueryService {
|
||||
const debtLifecycleReceivablesScenario = intent.intent === "list_receivables_counterparties" &&
|
||||
Array.isArray(intent.reasons) &&
|
||||
intent.reasons.includes("receivables_debt_lifecycle_signal_detected");
|
||||
const recipeIntent = debtLifecycleReceivablesScenario ? "open_items_by_counterparty_or_contract" : intent.intent;
|
||||
const debtLifecyclePayablesScenario = intent.intent === "list_payables_counterparties" &&
|
||||
Array.isArray(intent.reasons) &&
|
||||
(intent.reasons.includes("payables_debt_lifecycle_signal_detected") ||
|
||||
intent.reasons.includes("supplier_tail_risk_signal_detected") ||
|
||||
intent.reasons.includes("payables_signal_detected"));
|
||||
const recipeIntent = debtLifecycleReceivablesScenario || debtLifecyclePayablesScenario ? "open_items_by_counterparty_or_contract" : intent.intent;
|
||||
const recipeSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)(recipeIntent, filters.extracted_filters);
|
||||
const requestedResultMode = resolveRequestedResultMode(intent.intent, filters.extracted_filters);
|
||||
if (debtLifecycleReceivablesScenario && recipeIntent !== intent.intent) {
|
||||
baseReasons.push("recipe_override_to_open_items_for_receivables_debt_lifecycle");
|
||||
}
|
||||
if (debtLifecyclePayablesScenario && recipeIntent !== intent.intent) {
|
||||
baseReasons.push("recipe_override_to_open_items_for_payables_debt_lifecycle");
|
||||
}
|
||||
if (requestedResultMode === "confirmed_balance" &&
|
||||
recipeIntent === "open_items_by_counterparty_or_contract" &&
|
||||
!baseReasons.includes("confirmed_balance_unavailable_fallback_to_heuristic_candidates")) {
|
||||
baseReasons.push("confirmed_balance_unavailable_fallback_to_heuristic_candidates");
|
||||
}
|
||||
if (intent.intent === "unknown") {
|
||||
return buildLimitedExecutionResult({
|
||||
mode,
|
||||
@@ -1576,6 +1684,13 @@ class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: factual.responseType,
|
||||
...deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: recipeSelection.selected_recipe.recipe_id,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: factual.responseType,
|
||||
rowsMatched: recoveredRows.length
|
||||
}),
|
||||
limitations: [...filters.warnings, recoveryReason],
|
||||
reasons: [...baseReasons, recoveryReason]
|
||||
}
|
||||
@@ -1692,6 +1807,13 @@ class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: expandedFactual.responseType,
|
||||
...deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: expandedSelection.selected_recipe.recipe_id,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: expandedFactual.responseType,
|
||||
rowsMatched: expandedFilteredRows.length
|
||||
}),
|
||||
limitations: expandedLimitations,
|
||||
reasons: expandedReasons
|
||||
}
|
||||
@@ -1803,6 +1925,13 @@ class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: broadenedFactual.responseType,
|
||||
...deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: broadenedSelection.selected_recipe.recipe_id,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: broadenedFactual.responseType,
|
||||
rowsMatched: broadenedFilteredRows.length
|
||||
}),
|
||||
limitations: broadenedLimitations,
|
||||
reasons: broadenedReasons
|
||||
}
|
||||
@@ -1922,6 +2051,13 @@ class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: historicalFactual.responseType,
|
||||
...deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: historicalSelection.selected_recipe.recipe_id,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: historicalFactual.responseType,
|
||||
rowsMatched: historicalFilteredRows.length
|
||||
}),
|
||||
limitations: historicalLimitations,
|
||||
reasons: historicalReasons
|
||||
}
|
||||
@@ -1986,6 +2122,13 @@ class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: fallbackFactual.responseType,
|
||||
...deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: recipeSelection.selected_recipe.recipe_id,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: fallbackFactual.responseType,
|
||||
rowsMatched: documentBankFallbackRows.length
|
||||
}),
|
||||
limitations: fallbackLimitations,
|
||||
reasons: fallbackReasons
|
||||
}
|
||||
@@ -2142,6 +2285,13 @@ class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: factual.responseType,
|
||||
...deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: recipeSelection.selected_recipe.recipe_id,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: factual.responseType,
|
||||
rowsMatched: filteredRows.length
|
||||
}),
|
||||
limitations: filters.warnings,
|
||||
reasons: baseReasons
|
||||
}
|
||||
|
||||
@@ -470,6 +470,142 @@ function extractCounterpartyName(row) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function liabilityCategoryLabel(category) {
|
||||
if (category === "supplier_or_contractor") {
|
||||
return "поставщики/подрядчики";
|
||||
}
|
||||
if (category === "bank_or_credit") {
|
||||
return "банки/кредиты";
|
||||
}
|
||||
if (category === "tax_or_state") {
|
||||
return "налоги/госорганы";
|
||||
}
|
||||
return "прочие";
|
||||
}
|
||||
function classifyPayablesLiabilityCategory(row, counterparty) {
|
||||
const scores = {
|
||||
supplier_or_contractor: 0,
|
||||
bank_or_credit: 0,
|
||||
tax_or_state: 0,
|
||||
other: 0
|
||||
};
|
||||
const reasons = new Set();
|
||||
const text = `${counterparty} ${row.registrator} ${row.analytics.join(" ")}`.toLowerCase();
|
||||
const accountPrefixes = [extractAccountSectionCode(row.account_dt), extractAccountSectionCode(row.account_kt)].filter((item) => Boolean(item));
|
||||
if (accountPrefixes.includes("60")) {
|
||||
scores.supplier_or_contractor += 3;
|
||||
reasons.add("участие счета 60");
|
||||
}
|
||||
if (accountPrefixes.includes("66") || accountPrefixes.includes("67")) {
|
||||
scores.bank_or_credit += 4;
|
||||
reasons.add("участие счета 66/67");
|
||||
}
|
||||
if (accountPrefixes.includes("68") || accountPrefixes.includes("69")) {
|
||||
scores.tax_or_state += 4;
|
||||
reasons.add("участие счета 68/69");
|
||||
}
|
||||
if (accountPrefixes.includes("76")) {
|
||||
scores.supplier_or_contractor += 1;
|
||||
reasons.add("участие счета 76");
|
||||
}
|
||||
if (/(?:банк|сбер|втб|альфа|газпромбанк|кредит|loan|overdraft)/iu.test(text)) {
|
||||
scores.bank_or_credit += 3;
|
||||
reasons.add("банк/кредит в аналитике");
|
||||
}
|
||||
if (/(?:уфк|ифнс|фнс|налог|пфр|фсс|сфр|казнач|бюджет|гос)/iu.test(text)) {
|
||||
scores.tax_or_state += 3;
|
||||
reasons.add("налог/госорган в аналитике");
|
||||
}
|
||||
if (/(?:\bип\b|ооо|ао|зао|пао|подряд|поставщик|supplier|vendor|contractor)/iu.test(text)) {
|
||||
scores.supplier_or_contractor += 2;
|
||||
reasons.add("коммерческий контрагент в аналитике");
|
||||
}
|
||||
return {
|
||||
scores,
|
||||
reasons: Array.from(reasons)
|
||||
};
|
||||
}
|
||||
function buildPayablesCounterpartyRiskAggregate(rows) {
|
||||
const byCounterparty = new Map();
|
||||
for (const row of rows) {
|
||||
const name = extractCounterpartyName(row);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
const amountRaw = row.amount ?? 0;
|
||||
if (!Number.isFinite(amountRaw)) {
|
||||
continue;
|
||||
}
|
||||
const amount = Math.abs(amountRaw);
|
||||
const classified = classifyPayablesLiabilityCategory(row, name);
|
||||
const current = byCounterparty.get(name);
|
||||
if (!current) {
|
||||
byCounterparty.set(name, {
|
||||
base: {
|
||||
name,
|
||||
totalAmount: amount,
|
||||
operations: 1,
|
||||
firstPeriod: row.period,
|
||||
lastPeriod: row.period
|
||||
},
|
||||
categoryScores: {
|
||||
supplier_or_contractor: classified.scores.supplier_or_contractor,
|
||||
bank_or_credit: classified.scores.bank_or_credit,
|
||||
tax_or_state: classified.scores.tax_or_state,
|
||||
other: classified.scores.other
|
||||
},
|
||||
reasons: new Set(classified.reasons)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
current.base.totalAmount += amount;
|
||||
current.base.operations += 1;
|
||||
if ((row.period ?? "") < (current.base.firstPeriod ?? "")) {
|
||||
current.base.firstPeriod = row.period;
|
||||
}
|
||||
if ((row.period ?? "") > (current.base.lastPeriod ?? "")) {
|
||||
current.base.lastPeriod = row.period;
|
||||
}
|
||||
current.categoryScores.supplier_or_contractor += classified.scores.supplier_or_contractor;
|
||||
current.categoryScores.bank_or_credit += classified.scores.bank_or_credit;
|
||||
current.categoryScores.tax_or_state += classified.scores.tax_or_state;
|
||||
current.categoryScores.other += classified.scores.other;
|
||||
for (const reason of classified.reasons) {
|
||||
current.reasons.add(reason);
|
||||
}
|
||||
}
|
||||
const scoreKeys = ["supplier_or_contractor", "bank_or_credit", "tax_or_state", "other"];
|
||||
const toCategory = (scores) => {
|
||||
let winner = "other";
|
||||
let best = Number.NEGATIVE_INFINITY;
|
||||
for (const key of scoreKeys) {
|
||||
const score = scores[key];
|
||||
if (score > best) {
|
||||
best = score;
|
||||
winner = key;
|
||||
}
|
||||
}
|
||||
if (best <= 0) {
|
||||
return "other";
|
||||
}
|
||||
return winner;
|
||||
};
|
||||
return Array.from(byCounterparty.values())
|
||||
.map((item) => ({
|
||||
...item.base,
|
||||
category: toCategory(item.categoryScores),
|
||||
categoryReasons: Array.from(item.reasons).slice(0, 2)
|
||||
}))
|
||||
.sort((left, right) => {
|
||||
if (right.totalAmount !== left.totalAmount) {
|
||||
return right.totalAmount - left.totalAmount;
|
||||
}
|
||||
if (right.operations !== left.operations) {
|
||||
return right.operations - left.operations;
|
||||
}
|
||||
return left.name.localeCompare(right.name);
|
||||
});
|
||||
}
|
||||
function buildCounterpartyRiskAggregate(rows) {
|
||||
const byCounterparty = new Map();
|
||||
for (const row of rows) {
|
||||
@@ -1528,22 +1664,55 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
};
|
||||
}
|
||||
if (intent === "list_payables_counterparties") {
|
||||
const counterparties = buildCounterpartyRiskAggregate(rows);
|
||||
const counterparties = buildPayablesCounterpartyRiskAggregate(rows);
|
||||
const scopeLine = (() => {
|
||||
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
|
||||
if (asOfDate) {
|
||||
return `Дата среза: ${formatDateRu(asOfDate)}.`;
|
||||
}
|
||||
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
|
||||
const periodTo = normalizeIsoDateOnly(options.periodTo);
|
||||
if (periodFrom || periodTo) {
|
||||
return `Период анализа: ${formatDateRu(periodFrom ?? "...")}..${formatDateRu(periodTo ?? "...")}.`;
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
const lines = [
|
||||
"Проверил поставщиков с признаками незакрытых хвостов по взаиморасчетам (контур 60/76).",
|
||||
"Коротко: собран shortlist кандидатов на ручную проверку по потенциально незакрытым обязательствам (контур 60/76).",
|
||||
"",
|
||||
"Что это значит:",
|
||||
"- Режим результата: эвристический скоринг по движениям.",
|
||||
"- Это не финальный подтвержденный остаток к оплате.",
|
||||
...(scopeLine ? ["", scopeLine] : []),
|
||||
"",
|
||||
`Строк в выборке: ${rows.length}.`,
|
||||
`Контрагентов с сигналом: ${counterparties.length}.`
|
||||
`Контрагентов-кандидатов: ${counterparties.length}.`
|
||||
];
|
||||
if (counterparties.length > 0) {
|
||||
lines.push("Приоритет ручной проверки (по сумме/частоте хвостов):");
|
||||
const categoryCounts = counterparties.reduce((acc, item) => {
|
||||
acc[item.category] += 1;
|
||||
return acc;
|
||||
}, { supplier_or_contractor: 0, bank_or_credit: 0, tax_or_state: 0, other: 0 });
|
||||
lines.push("");
|
||||
lines.push("Категории обязательств:");
|
||||
lines.push(`- ${liabilityCategoryLabel("supplier_or_contractor")}: ${categoryCounts.supplier_or_contractor}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("bank_or_credit")}: ${categoryCounts.bank_or_credit}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("tax_or_state")}: ${categoryCounts.tax_or_state}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("other")}: ${categoryCounts.other}`);
|
||||
lines.push("");
|
||||
lines.push("Приоритет ручной проверки (по сумме/частоте сигналов):");
|
||||
lines.push(...counterparties
|
||||
.slice(0, 8)
|
||||
.map((item, index) => `${index + 1}. ${item.name} | сумма сигнала: ${formatMoney(item.totalAmount)} | операций: ${item.operations}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}`));
|
||||
.map((item, index) => `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | сумма сигнала: ${formatMoney(item.totalAmount)} | операций: ${item.operations}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""} | статус: требует ручной проверки${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`));
|
||||
lines.push("");
|
||||
lines.push("Примеры исходных строк:");
|
||||
lines.push(...formatTopRows(rows, 4));
|
||||
}
|
||||
else {
|
||||
lines.push("Явных признаков системной задолженности по доступному срезу не найдено.");
|
||||
lines.push("");
|
||||
lines.push("Явных кандидатов на незакрытые обязательства по текущему срезу не найдено.");
|
||||
lines.push("");
|
||||
lines.push("Примеры исходных строк:");
|
||||
lines.push(...formatTopRows(rows, 6));
|
||||
}
|
||||
return {
|
||||
|
||||
+200
-52
@@ -1460,6 +1460,11 @@ function buildAddressDebugPayload(addressDebug, llmPreDecomposeMeta = null) {
|
||||
runtime_readiness: addressDebug.runtime_readiness,
|
||||
limited_reason_category: addressDebug.limited_reason_category,
|
||||
response_type: addressDebug.response_type,
|
||||
requested_result_mode: addressDebug.requested_result_mode ?? undefined,
|
||||
result_mode: addressDebug.result_mode ?? undefined,
|
||||
evidence_strength: addressDebug.evidence_strength ?? undefined,
|
||||
balance_confirmed: typeof addressDebug.balance_confirmed === "boolean" ? addressDebug.balance_confirmed : undefined,
|
||||
as_of_date_basis: addressDebug.as_of_date_basis ?? undefined,
|
||||
execution_lane: "address_query",
|
||||
llm_decomposition_applied: Boolean(llmMeta?.applied),
|
||||
llm_decomposition_attempted: Boolean(llmMeta?.attempted),
|
||||
@@ -1588,6 +1593,19 @@ const ADDRESS_PREDECOMPOSE_NOISE_TOKENS = new Set([
|
||||
"kakoi",
|
||||
"vse",
|
||||
"all",
|
||||
"\u043f\u0443\u043d\u043a\u0442",
|
||||
"\u043f\u0443\u043d\u043a\u0442\u0430",
|
||||
"\u043f\u0443\u043d\u043a\u0442\u0443",
|
||||
"\u043f\u0443\u043d\u043a\u0442\u043e\u043c",
|
||||
"\u043f\u043e\u0437\u0438\u0446\u0438\u044f",
|
||||
"\u043f\u043e\u0437\u0438\u0446\u0438\u0438",
|
||||
"\u043f\u043e\u0437\u0438\u0446\u0438\u044e",
|
||||
"\u0441\u0442\u0440\u043e\u043a\u0430",
|
||||
"\u0441\u0442\u0440\u043e\u043a\u0438",
|
||||
"\u0441\u0442\u0440\u043e\u043a\u0443",
|
||||
"item",
|
||||
"row",
|
||||
"line",
|
||||
"blya",
|
||||
"blyat",
|
||||
"епт",
|
||||
@@ -1612,51 +1630,51 @@ const ADDRESS_FALLBACK_STRIP_TOKENS = new Set([
|
||||
"please"
|
||||
]);
|
||||
const ADDRESS_MONTH_ALIAS_MAP = {
|
||||
янв: "01",
|
||||
январ: "01",
|
||||
"\u044f\u043d\u0432": "01",
|
||||
"\u044f\u043d\u0432\u0430\u0440": "01",
|
||||
january: "01",
|
||||
jan: "01",
|
||||
фев: "02",
|
||||
феврал: "02",
|
||||
"\u0444\u0435\u0432": "02",
|
||||
"\u0444\u0435\u0432\u0440\u0430\u043b": "02",
|
||||
february: "02",
|
||||
feb: "02",
|
||||
мар: "03",
|
||||
март: "03",
|
||||
"\u043c\u0430\u0440": "03",
|
||||
"\u043c\u0430\u0440\u0442": "03",
|
||||
march: "03",
|
||||
apr: "04",
|
||||
апр: "04",
|
||||
апрел: "04",
|
||||
"\u0430\u043f\u0440": "04",
|
||||
"\u0430\u043f\u0440\u0435\u043b": "04",
|
||||
april: "04",
|
||||
май: "05",
|
||||
ма: "05",
|
||||
"\u043c\u0430\u0439": "05",
|
||||
"\u043c\u0430": "05",
|
||||
may: "05",
|
||||
июн: "06",
|
||||
июнь: "06",
|
||||
"\u0438\u044e\u043d": "06",
|
||||
"\u0438\u044e\u043d\u044c": "06",
|
||||
june: "06",
|
||||
jun: "06",
|
||||
июл: "07",
|
||||
июль: "07",
|
||||
"\u0438\u044e\u043b": "07",
|
||||
"\u0438\u044e\u043b\u044c": "07",
|
||||
july: "07",
|
||||
jul: "07",
|
||||
авг: "08",
|
||||
август: "08",
|
||||
"\u0430\u0432\u0433": "08",
|
||||
"\u0430\u0432\u0433\u0443\u0441\u0442": "08",
|
||||
august: "08",
|
||||
aug: "08",
|
||||
сен: "09",
|
||||
сент: "09",
|
||||
сентябр: "09",
|
||||
"\u0441\u0435\u043d": "09",
|
||||
"\u0441\u0435\u043d\u0442": "09",
|
||||
"\u0441\u0435\u043d\u0442\u044f\u0431\u0440": "09",
|
||||
september: "09",
|
||||
sep: "09",
|
||||
окт: "10",
|
||||
октябр: "10",
|
||||
"\u043e\u043a\u0442": "10",
|
||||
"\u043e\u043a\u0442\u044f\u0431\u0440": "10",
|
||||
october: "10",
|
||||
oct: "10",
|
||||
ноя: "11",
|
||||
ноябр: "11",
|
||||
"\u043d\u043e\u044f": "11",
|
||||
"\u043d\u043e\u044f\u0431\u0440": "11",
|
||||
november: "11",
|
||||
nov: "11",
|
||||
дек: "12",
|
||||
декабр: "12",
|
||||
"\u0434\u0435\u043a": "12",
|
||||
"\u0434\u0435\u043a\u0430\u0431\u0440": "12",
|
||||
december: "12",
|
||||
dec: "12"
|
||||
};
|
||||
@@ -1883,6 +1901,10 @@ function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage)
|
||||
const bankSignal = ADDRESS_BANK_SIGNAL_PATTERN.test(source);
|
||||
const contractSignal = ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source);
|
||||
const balanceSignal = ADDRESS_BALANCE_SIGNAL_PATTERN.test(source);
|
||||
const hasIndexPointerSignal = /(?:\u043f\u0443\u043d\u043a\u0442|\u043f\u043e\u0437\u0438\u0446|\u0441\u0442\u0440\u043e\u043a|item|row|line)/iu.test(sourceRaw);
|
||||
if (hasIndexPointerSignal && extractDisplayedEntityIndexMention(sourceRaw) !== null) {
|
||||
return null;
|
||||
}
|
||||
if (balanceSignal && account) {
|
||||
let periodClause = "";
|
||||
let rule = "balance_account_rewrite";
|
||||
@@ -2201,6 +2223,14 @@ const FOLLOWUP_DISPLAY_COUNTERPARTY_LEGAL_TOKENS = new Set([
|
||||
"company",
|
||||
"group"
|
||||
]);
|
||||
const FOLLOWUP_DISPLAY_ENTITY_TYPE_BY_INTENT = {
|
||||
counterparty_activity_lifecycle: "counterparty",
|
||||
customer_revenue_and_payments: "counterparty",
|
||||
supplier_payouts_profile: "counterparty",
|
||||
counterparty_population_and_roles: "counterparty",
|
||||
contract_usage_and_value: "contract",
|
||||
list_contracts_by_counterparty: "contract"
|
||||
};
|
||||
function normalizeCounterpartyForFollowupMatch(value) {
|
||||
return compactWhitespace(repairAddressMojibake(String(value ?? ""))
|
||||
.toLowerCase()
|
||||
@@ -2211,7 +2241,25 @@ function normalizeCounterpartyForFollowupMatch(value) {
|
||||
function normalizeCounterpartyTokenForFollowupMatch(value) {
|
||||
return normalizeCounterpartyForFollowupMatch(value).replace(/[._-]+/g, "");
|
||||
}
|
||||
function extractDisplayedCounterpartyCandidates(replyText) {
|
||||
function normalizeCounterpartyStemForFollowupMatch(value) {
|
||||
const compact = normalizeCounterpartyTokenForFollowupMatch(value);
|
||||
if (!compact || !/[а-яё]/iu.test(compact)) {
|
||||
return compact;
|
||||
}
|
||||
const stem = compact.replace(/(?:иями|ями|ами|ией|ей|ий|ов|ев|ом|ем|ах|ях|ую|юю|ая|яя|ое|ее|ые|ие|ого|его|ому|ему|ыми|ими|ым|им|ам|ям|у|ю|а|я|е|и|ы|о)$/iu, "");
|
||||
return stem.length >= 3 ? stem : compact;
|
||||
}
|
||||
function inferDisplayedEntityTypeFromIntent(intent) {
|
||||
const normalized = compactWhitespace(String(intent ?? "").toLowerCase());
|
||||
if (!normalized) {
|
||||
return "unknown";
|
||||
}
|
||||
return FOLLOWUP_DISPLAY_ENTITY_TYPE_BY_INTENT[normalized] ?? "unknown";
|
||||
}
|
||||
function extractDisplayedAddressEntityCandidates(replyText, entityType = "unknown") {
|
||||
if (entityType === "unknown") {
|
||||
return [];
|
||||
}
|
||||
const lines = String(replyText ?? "").split(/\r?\n/);
|
||||
const candidates = [];
|
||||
for (const line of lines) {
|
||||
@@ -2219,10 +2267,15 @@ function extractDisplayedCounterpartyCandidates(replyText) {
|
||||
if (!compactLine) {
|
||||
continue;
|
||||
}
|
||||
if (!/^\d+\.\s+/.test(compactLine)) {
|
||||
const numberedMatch = compactLine.match(/^(\d+)\.\s+(.+)$/);
|
||||
if (!numberedMatch) {
|
||||
continue;
|
||||
}
|
||||
const afterNumber = compactLine.replace(/^\d+\.\s+/, "");
|
||||
const index = Number.parseInt(String(numberedMatch[1] ?? ""), 10);
|
||||
if (!Number.isFinite(index) || index <= 0) {
|
||||
continue;
|
||||
}
|
||||
const afterNumber = String(numberedMatch[2] ?? "");
|
||||
const parts = afterNumber.split("|").map((item) => compactWhitespace(item));
|
||||
let counterpartyCandidate = parts[0] ?? "";
|
||||
if (parts.length >= 2 && /^\d{4}-\d{2}-\d{2}/.test(parts[0] ?? "")) {
|
||||
@@ -2232,9 +2285,20 @@ function extractDisplayedCounterpartyCandidates(replyText) {
|
||||
if (!cleanedCandidate || cleanedCandidate.length < 2) {
|
||||
continue;
|
||||
}
|
||||
candidates.push(cleanedCandidate);
|
||||
candidates.push({
|
||||
index,
|
||||
value: cleanedCandidate,
|
||||
entityType
|
||||
});
|
||||
}
|
||||
return Array.from(new Set(candidates));
|
||||
const dedup = new Map();
|
||||
for (const candidate of candidates) {
|
||||
const key = `${candidate.entityType}:${candidate.index}:${normalizeCounterpartyForFollowupMatch(candidate.value)}`;
|
||||
if (!dedup.has(key)) {
|
||||
dedup.set(key, candidate);
|
||||
}
|
||||
}
|
||||
return Array.from(dedup.values());
|
||||
}
|
||||
function buildCounterpartyAliasesForFollowupMatch(counterpartyName) {
|
||||
const aliases = new Set();
|
||||
@@ -2247,13 +2311,14 @@ function buildCounterpartyAliasesForFollowupMatch(counterpartyName) {
|
||||
.split(/\s+/)
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean);
|
||||
const withoutLegalTokens = normalizedTokens
|
||||
const tokensForAlias = Array.from(new Set(normalizedTokens.flatMap((token) => [token, ...token.split(/-+/).map((part) => part.trim()).filter(Boolean)])));
|
||||
const withoutLegalTokens = tokensForAlias
|
||||
.filter((token) => !FOLLOWUP_DISPLAY_COUNTERPARTY_LEGAL_TOKENS.has(token))
|
||||
.join(" ");
|
||||
if (withoutLegalTokens) {
|
||||
aliases.add(withoutLegalTokens);
|
||||
}
|
||||
for (const token of normalizedTokens) {
|
||||
for (const token of tokensForAlias) {
|
||||
const compactToken = normalizeCounterpartyTokenForFollowupMatch(token);
|
||||
if (compactToken.length < 3) {
|
||||
continue;
|
||||
@@ -2265,6 +2330,10 @@ function buildCounterpartyAliasesForFollowupMatch(counterpartyName) {
|
||||
continue;
|
||||
}
|
||||
aliases.add(compactToken);
|
||||
const stemToken = normalizeCounterpartyStemForFollowupMatch(compactToken);
|
||||
if (stemToken.length >= 4) {
|
||||
aliases.add(stemToken);
|
||||
}
|
||||
}
|
||||
return Array.from(aliases)
|
||||
.map((alias) => compactWhitespace(alias))
|
||||
@@ -2278,31 +2347,95 @@ function hasCounterpartyAliasMention(normalizedMessage, alias) {
|
||||
}
|
||||
const aliasPattern = escapeRegex(trimmedAlias).replace(/\s+/g, "\\s+");
|
||||
const boundaryPattern = new RegExp(`(?:^|[^a-zа-я0-9])${aliasPattern}(?:$|[^a-zа-я0-9])`, "iu");
|
||||
return boundaryPattern.test(normalizedMessage);
|
||||
if (boundaryPattern.test(normalizedMessage)) {
|
||||
return true;
|
||||
}
|
||||
if (trimmedAlias.length < 4 || !/[а-яё]/iu.test(trimmedAlias)) {
|
||||
return false;
|
||||
}
|
||||
const fuzzyPattern = new RegExp(`(?:^|[^a-zа-я0-9])${aliasPattern}[а-яё]{0,3}(?:$|[^a-zа-я0-9])`, "iu");
|
||||
return fuzzyPattern.test(normalizedMessage);
|
||||
}
|
||||
function resolveDisplayedCounterpartyMention(userMessage, displayedCounterparties) {
|
||||
function extractDisplayedEntityIndexMention(userMessage) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
const tokenStart = "(?:^|[^\\p{L}\\p{N}_])";
|
||||
const tokenEnd = "(?=$|[^\\p{L}\\p{N}_])";
|
||||
const pointerPattern = "(?:\\u043f\\u0443\\u043d\\u043a\\u0442(?:\\u0430|\\u0443|\\u043e\\u043c)?|\\u043f\\u043e\\u0437\\u0438\\u0446\\u0438(?:\\u044f|\\u0438|\\u044e|\\u0435\\u0439)|\\u0441\\u0442\\u0440\\u043e\\u043a(?:\\u0430|\\u0438|\\u0435|\\u0443)|item|row|line)";
|
||||
const pointerSignalPattern = new RegExp(`${tokenStart}${pointerPattern}${tokenEnd}`, "iu");
|
||||
const directPattern = new RegExp(`${tokenStart}${pointerPattern}${tokenEnd}\\D{0,8}(\\d{1,3})(?!\\d)`, "iu");
|
||||
const directMatch = normalized.match(directPattern);
|
||||
if (directMatch) {
|
||||
const value = Number.parseInt(String(directMatch[1] ?? ""), 10);
|
||||
return Number.isFinite(value) && value > 0 ? value : null;
|
||||
}
|
||||
const reversePattern = new RegExp(`${tokenStart}(\\d{1,3})(?:-?(?:\\u0439|\\u044f|\\u0435|\\u0433\\u043e|\\u043c\\u0443))?\\s+${pointerPattern}${tokenEnd}`, "iu");
|
||||
const reverseMatch = normalized.match(reversePattern);
|
||||
if (reverseMatch) {
|
||||
const value = Number.parseInt(String(reverseMatch[1] ?? ""), 10);
|
||||
return Number.isFinite(value) && value > 0 ? value : null;
|
||||
}
|
||||
if (pointerSignalPattern.test(normalized)) {
|
||||
const numericMatches = Array.from(normalized.matchAll(/(?:^|[^\p{N}])(\d{1,3})(?!\d)/gu))
|
||||
.map((match) => Number.parseInt(String(match[1] ?? ""), 10))
|
||||
.filter((value) => Number.isFinite(value) && value > 0);
|
||||
if (numericMatches.length === 1) {
|
||||
return numericMatches[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function resolveDisplayedAddressEntityMention(userMessage, displayedEntities) {
|
||||
const normalizedMessage = normalizeCounterpartyForFollowupMatch(userMessage);
|
||||
if (!normalizedMessage) {
|
||||
return null;
|
||||
}
|
||||
if (!Array.isArray(displayedCounterparties) || displayedCounterparties.length === 0) {
|
||||
if (!Array.isArray(displayedEntities) || displayedEntities.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const indexMention = extractDisplayedEntityIndexMention(userMessage);
|
||||
if (indexMention !== null) {
|
||||
const indexedCandidate = displayedEntities.find((candidate) => Number(candidate.index) === indexMention);
|
||||
if (indexedCandidate) {
|
||||
return {
|
||||
value: indexedCandidate.value,
|
||||
entityType: indexedCandidate.entityType,
|
||||
matchKind: "index",
|
||||
index: indexedCandidate.index
|
||||
};
|
||||
}
|
||||
}
|
||||
let bestMatch = null;
|
||||
for (const candidate of displayedCounterparties) {
|
||||
const aliases = buildCounterpartyAliasesForFollowupMatch(candidate);
|
||||
for (const candidate of displayedEntities) {
|
||||
const aliases = buildCounterpartyAliasesForFollowupMatch(candidate.value);
|
||||
for (const alias of aliases) {
|
||||
if (!hasCounterpartyAliasMention(normalizedMessage, alias)) {
|
||||
continue;
|
||||
}
|
||||
const score = alias.length * 10 + (normalizeCounterpartyForFollowupMatch(candidate) === alias ? 1 : 0);
|
||||
const score = alias.length * 10 + (normalizeCounterpartyForFollowupMatch(candidate.value) === alias ? 1 : 0);
|
||||
if (!bestMatch || score > bestMatch.score) {
|
||||
bestMatch = { value: candidate, score };
|
||||
bestMatch = {
|
||||
value: candidate.value,
|
||||
entityType: candidate.entityType,
|
||||
index: candidate.index,
|
||||
matchKind: "alias",
|
||||
score
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bestMatch?.value ?? null;
|
||||
if (!bestMatch) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
value: bestMatch.value,
|
||||
entityType: bestMatch.entityType,
|
||||
matchKind: bestMatch.matchKind,
|
||||
index: bestMatch.index
|
||||
};
|
||||
}
|
||||
function findRecentAddressFilterValue(items, key) {
|
||||
for (let index = items.length - 1; index >= 0; index -= 1) {
|
||||
@@ -2479,12 +2612,17 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
||||
const hasAlternateFollowupSignal = toNonEmptyString(alternateMessage)
|
||||
? hasAddressFollowupContextSignal(alternateMessage)
|
||||
: false;
|
||||
const hasPrimaryIndexReferenceSignal = extractDisplayedEntityIndexMention(userMessage) !== null;
|
||||
const hasAlternateIndexReferenceSignal = toNonEmptyString(alternateMessage)
|
||||
? extractDisplayedEntityIndexMention(String(alternateMessage ?? "")) !== null
|
||||
: false;
|
||||
const hasIndexReferenceSignal = hasPrimaryIndexReferenceSignal || hasAlternateIndexReferenceSignal;
|
||||
const hasStandaloneAddressTopic = hasStandaloneAddressTopicSignal(userMessage) ||
|
||||
(toNonEmptyString(alternateMessage) ? hasStandaloneAddressTopicSignal(alternateMessage) : false);
|
||||
if (hasStandaloneAddressTopic && !hasImplicitContinuationSignal) {
|
||||
if (hasStandaloneAddressTopic && !hasImplicitContinuationSignal && !hasIndexReferenceSignal) {
|
||||
return null;
|
||||
}
|
||||
if (!hasPrimaryFollowupSignal && !hasAlternateFollowupSignal && !hasImplicitContinuationSignal) {
|
||||
if (!hasPrimaryFollowupSignal && !hasAlternateFollowupSignal && !hasImplicitContinuationSignal && !hasIndexReferenceSignal) {
|
||||
return null;
|
||||
}
|
||||
if (!previousAddressDebug) {
|
||||
@@ -2531,16 +2669,24 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
||||
previousFilters.organization = historicalOrganization;
|
||||
}
|
||||
}
|
||||
const displayedCounterparties = extractDisplayedCounterpartyCandidates(toNonEmptyString(previousAddressItem?.text) ?? "");
|
||||
const counterpartyFromFollowupText = resolveDisplayedCounterpartyMention(userMessage, displayedCounterparties) ??
|
||||
const displayedEntityType = inferDisplayedEntityTypeFromIntent(sourceIntent);
|
||||
const displayedEntities = extractDisplayedAddressEntityCandidates(toNonEmptyString(previousAddressItem?.text) ?? "", displayedEntityType);
|
||||
const resolvedEntityFromFollowup = resolveDisplayedAddressEntityMention(userMessage, displayedEntities) ??
|
||||
(toNonEmptyString(alternateMessage)
|
||||
? resolveDisplayedCounterpartyMention(String(alternateMessage ?? ""), displayedCounterparties)
|
||||
? resolveDisplayedAddressEntityMention(String(alternateMessage ?? ""), displayedEntities)
|
||||
: null);
|
||||
if (counterpartyFromFollowupText) {
|
||||
previousFilters.counterparty = counterpartyFromFollowupText;
|
||||
previousAnchorType = "counterparty";
|
||||
previousAnchor = counterpartyFromFollowupText;
|
||||
resolvedCounterpartyFromDisplay = true;
|
||||
if (resolvedEntityFromFollowup) {
|
||||
if (resolvedEntityFromFollowup.entityType === "counterparty") {
|
||||
previousFilters.counterparty = resolvedEntityFromFollowup.value;
|
||||
previousAnchorType = "counterparty";
|
||||
previousAnchor = resolvedEntityFromFollowup.value;
|
||||
resolvedCounterpartyFromDisplay = true;
|
||||
}
|
||||
else if (resolvedEntityFromFollowup.entityType === "contract") {
|
||||
previousFilters.contract = resolvedEntityFromFollowup.value;
|
||||
previousAnchorType = "contract";
|
||||
previousAnchor = resolvedEntityFromFollowup.value;
|
||||
}
|
||||
if (followupSelectionMode !== "switch_to_suggested_intent") {
|
||||
followupSelectionMode = "carry_referenced_entity";
|
||||
}
|
||||
@@ -3373,7 +3519,9 @@ function resolveAddressToolGateDecision(addressInputMessage, followupContext, ll
|
||||
llmContractIntent === "unknown" &&
|
||||
!followupContext &&
|
||||
!hasClassifierSignal &&
|
||||
!strongDataSignalFromRawMessage) {
|
||||
!hasIntentSignal &&
|
||||
!strongDataSignalFromRawMessage &&
|
||||
!strongDataSignalFromEffectiveMessage) {
|
||||
return {
|
||||
runAddressLane: false,
|
||||
decision: "skip_address_lane",
|
||||
@@ -4510,7 +4658,7 @@ function isPlausibleOrganizationName(value) {
|
||||
if (/(?:справочникссылка|документссылка|плансчетовссылка|standardodata|recordtype|cmp:)/i.test(candidate)) {
|
||||
return false;
|
||||
}
|
||||
return /[A-Za-zА-Яа-яЁё]/u.test(candidate);
|
||||
return /[A-Za-z\u0400-\u04FF]/u.test(candidate);
|
||||
}
|
||||
function appendOrganizationFactsFromValue(value, hints, bucket, depth = 0) {
|
||||
if (depth > 4 || value === null || value === undefined) {
|
||||
|
||||
Reference in New Issue
Block a user