Open-World: закрепить границу просрочки долгов
This commit is contained in:
@@ -1493,6 +1493,18 @@ function hasOrganizationLevelEarningsOverviewBridgeSignal(text) {
|
||||
/(?:\u043a\u0430\u043a\w*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043c\u044b\b|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time|what|which|how\s+much|show|give|company|business|organization|our|we|us)/iu.test(normalized);
|
||||
return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue;
|
||||
}
|
||||
function hasOrganizationLevelDebtDueDateOverviewBridgeSignal(text) {
|
||||
const normalized = String(text ?? "").trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(normalized)) {
|
||||
return false;
|
||||
}
|
||||
const hasDueDateDebtCue = /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/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;
|
||||
}
|
||||
function hasSpecificCounterpartyRevenueBridgeSignal(text) {
|
||||
const normalized = String(text ?? "").trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
@@ -1653,6 +1665,9 @@ function resolveUnicodeAddressIntentBridge(text) {
|
||||
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
||||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
||||
}
|
||||
if (hasOrganizationLevelDebtDueDateOverviewBridgeSignal(normalized)) {
|
||||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_debt_due_date_deferred_to_discovery");
|
||||
}
|
||||
if (hasBidirectionalValueFlowComparisonSignal(normalized)) {
|
||||
return unicodeBridgeResolution("unknown", "high", "unicode_bidirectional_value_flow_deferred_to_discovery");
|
||||
}
|
||||
|
||||
@@ -311,6 +311,9 @@ function hasReceivablesDebtAgingFocus(userMessage) {
|
||||
const hasCounterpartySignal = /(?:заказчик|клиент|покупател|контрагент|должник|counterpart|customer|client|buyer)/iu.test(text);
|
||||
return hasDebtSignal && hasLongevitySignal && hasCounterpartySignal;
|
||||
}
|
||||
function receivablesDueDateBoundaryLine() {
|
||||
return "Граница доказанности: это возрастной сигнал/хвост дебиторки по 62/76, а не подтвержденная договорная просрочка или due-date aging; для due-date нужны сроки оплаты из договора или условий оплаты.";
|
||||
}
|
||||
function formatAgeYearsMonthsDays(daysRaw) {
|
||||
const safeDays = Math.max(0, Math.floor(daysRaw));
|
||||
const years = Math.floor(safeDays / 365);
|
||||
@@ -3229,7 +3232,8 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||
"Проверил должников по сроку жизни задолженности (контур 62/76).",
|
||||
`Дата среза: ${formatDateRu(asOfDate)}.`,
|
||||
`Строк в выборке: ${rows.length}.`,
|
||||
`Контрагентов с сигналом: ${aging.length}.`
|
||||
`Контрагентов с сигналом: ${aging.length}.`,
|
||||
receivablesDueDateBoundaryLine()
|
||||
];
|
||||
if (aging.length > 0) {
|
||||
lines.push("Приоритет ручной проверки (по возрасту долга, по убыванию):");
|
||||
@@ -3255,7 +3259,8 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||
const lines = [
|
||||
"Проверил покупателей с признаками затянутой оплаты (контур 62/76).",
|
||||
`Строк в выборке: ${rows.length}.`,
|
||||
`Контрагентов с сигналом: ${counterparties.length}.`
|
||||
`Контрагентов с сигналом: ${counterparties.length}.`,
|
||||
receivablesDueDateBoundaryLine()
|
||||
];
|
||||
if (counterparties.length > 0) {
|
||||
lines.push("Приоритет ручной проверки (по сумме/частоте хвостов):");
|
||||
|
||||
+12
-1
@@ -524,8 +524,19 @@ function hasOrganizationLevelEarningsOverviewSignal(text) {
|
||||
/(?:\u043a\u0430\u043a\w*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043c\u044b\b|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time|what|which|how\s+much|show|give|company|business|organization|our|we|us)/iu.test(text);
|
||||
return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue;
|
||||
}
|
||||
function hasOrganizationLevelDebtDueDateOverviewSignal(text) {
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
if (/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(text)) {
|
||||
return false;
|
||||
}
|
||||
const hasDueDateDebtCue = /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/iu.test(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(text);
|
||||
return hasDueDateDebtCue && hasCompanyScopeCue;
|
||||
}
|
||||
function hasBusinessOverviewSignal(text) {
|
||||
if (hasOrganizationLevelEarningsOverviewSignal(text)) {
|
||||
if (hasOrganizationLevelEarningsOverviewSignal(text) || hasOrganizationLevelDebtDueDateOverviewSignal(text)) {
|
||||
return true;
|
||||
}
|
||||
return /(?:\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u0430\u0443\u0434\u0438\u0442|\u043f\u043e\u043b\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u0441\u0432\u043e\u0434\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u043a\u0430\u043a\s+\u0442\u044b\s+\u043e\u0446\u0435\u043d(?:\u0438\u0448\u044c|\u0438)\s+\u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044e|\u0438\u044f)\s+\u0432\s+\u0446\u0435\u043b\u043e\u043c|company\s+(?:analysis|overview)|business\s+(?:overview|audit)|llm[-\s]?audit|бизнес[-\s]?обзор|бизнес[-\s]?аудит)/iu.test(text);
|
||||
|
||||
@@ -126,6 +126,18 @@ function hasOrganizationLevelEarningsOverviewSignal(text) {
|
||||
/(?:\u043a\u0430\u043a\w*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043c\u044b\b|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time|what|which|how\s+much|show|give|company|business|organization|our|we|us)/iu.test(normalized);
|
||||
return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue;
|
||||
}
|
||||
function hasOrganizationLevelDebtDueDateOverviewSignal(text) {
|
||||
const normalized = String(text ?? "");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(normalized)) {
|
||||
return false;
|
||||
}
|
||||
const hasDueDateDebtCue = /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/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;
|
||||
}
|
||||
function detectBroadBusinessEvaluation(text) {
|
||||
const normalized = String(text ?? "");
|
||||
if (!normalized) {
|
||||
@@ -146,6 +158,11 @@ function detectBroadBusinessEvaluation(text) {
|
||||
family: "broad_business_evaluation"
|
||||
};
|
||||
}
|
||||
if (hasOrganizationLevelDebtDueDateOverviewSignal(normalized)) {
|
||||
return {
|
||||
family: "broad_business_evaluation"
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function buildEntityCandidates(counterpartyTurnover) {
|
||||
|
||||
@@ -1873,6 +1873,29 @@ function hasOrganizationLevelEarningsOverviewBridgeSignal(text: string): boolean
|
||||
return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue;
|
||||
}
|
||||
|
||||
function hasOrganizationLevelDebtDueDateOverviewBridgeSignal(text: string): boolean {
|
||||
const normalized = String(text ?? "").trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(
|
||||
normalized
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const hasDueDateDebtCue =
|
||||
/(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/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;
|
||||
}
|
||||
|
||||
function hasSpecificCounterpartyRevenueBridgeSignal(text: string): boolean {
|
||||
const normalized = String(text ?? "").trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
@@ -2134,6 +2157,10 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio
|
||||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
||||
}
|
||||
|
||||
if (hasOrganizationLevelDebtDueDateOverviewBridgeSignal(normalized)) {
|
||||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_debt_due_date_deferred_to_discovery");
|
||||
}
|
||||
|
||||
if (hasBidirectionalValueFlowComparisonSignal(normalized)) {
|
||||
return unicodeBridgeResolution(
|
||||
"unknown",
|
||||
|
||||
@@ -459,6 +459,10 @@ function hasReceivablesDebtAgingFocus(userMessage: string | null | undefined): b
|
||||
return hasDebtSignal && hasLongevitySignal && hasCounterpartySignal;
|
||||
}
|
||||
|
||||
function receivablesDueDateBoundaryLine(): string {
|
||||
return "Граница доказанности: это возрастной сигнал/хвост дебиторки по 62/76, а не подтвержденная договорная просрочка или due-date aging; для due-date нужны сроки оплаты из договора или условий оплаты.";
|
||||
}
|
||||
|
||||
function formatAgeYearsMonthsDays(daysRaw: number): string {
|
||||
const safeDays = Math.max(0, Math.floor(daysRaw));
|
||||
const years = Math.floor(safeDays / 365);
|
||||
@@ -4133,7 +4137,8 @@ function composeFactualReplyBody(
|
||||
"Проверил должников по сроку жизни задолженности (контур 62/76).",
|
||||
`Дата среза: ${formatDateRu(asOfDate)}.`,
|
||||
`Строк в выборке: ${rows.length}.`,
|
||||
`Контрагентов с сигналом: ${aging.length}.`
|
||||
`Контрагентов с сигналом: ${aging.length}.`,
|
||||
receivablesDueDateBoundaryLine()
|
||||
];
|
||||
|
||||
if (aging.length > 0) {
|
||||
@@ -4166,7 +4171,8 @@ function composeFactualReplyBody(
|
||||
const lines = [
|
||||
"Проверил покупателей с признаками затянутой оплаты (контур 62/76).",
|
||||
`Строк в выборке: ${rows.length}.`,
|
||||
`Контрагентов с сигналом: ${counterparties.length}.`
|
||||
`Контрагентов с сигналом: ${counterparties.length}.`,
|
||||
receivablesDueDateBoundaryLine()
|
||||
];
|
||||
if (counterparties.length > 0) {
|
||||
lines.push("Приоритет ручной проверки (по сумме/частоте хвостов):");
|
||||
|
||||
@@ -710,8 +710,30 @@ function hasOrganizationLevelEarningsOverviewSignal(text: string): boolean {
|
||||
return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue;
|
||||
}
|
||||
|
||||
function hasOrganizationLevelDebtDueDateOverviewSignal(text: string): boolean {
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(
|
||||
text
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const hasDueDateDebtCue =
|
||||
/(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/iu.test(
|
||||
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(
|
||||
text
|
||||
);
|
||||
return hasDueDateDebtCue && hasCompanyScopeCue;
|
||||
}
|
||||
|
||||
function hasBusinessOverviewSignal(text: string): boolean {
|
||||
if (hasOrganizationLevelEarningsOverviewSignal(text)) {
|
||||
if (hasOrganizationLevelEarningsOverviewSignal(text) || hasOrganizationLevelDebtDueDateOverviewSignal(text)) {
|
||||
return true;
|
||||
}
|
||||
return /(?:\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u0430\u0443\u0434\u0438\u0442|\u043f\u043e\u043b\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u0441\u0432\u043e\u0434\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u043a\u0430\u043a\s+\u0442\u044b\s+\u043e\u0446\u0435\u043d(?:\u0438\u0448\u044c|\u0438)\s+\u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044e|\u0438\u044f)\s+\u0432\s+\u0446\u0435\u043b\u043e\u043c|company\s+(?:analysis|overview)|business\s+(?:overview|audit)|llm[-\s]?audit|бизнес[-\s]?обзор|бизнес[-\s]?аудит)/iu.test(
|
||||
|
||||
@@ -146,6 +146,29 @@ function hasOrganizationLevelEarningsOverviewSignal(text) {
|
||||
return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue;
|
||||
}
|
||||
|
||||
function hasOrganizationLevelDebtDueDateOverviewSignal(text) {
|
||||
const normalized = String(text ?? "");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(
|
||||
normalized
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const hasDueDateDebtCue =
|
||||
/(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/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;
|
||||
}
|
||||
|
||||
function detectBroadBusinessEvaluation(text) {
|
||||
const normalized = String(text ?? "");
|
||||
if (!normalized) {
|
||||
@@ -172,6 +195,11 @@ function detectBroadBusinessEvaluation(text) {
|
||||
family: "broad_business_evaluation"
|
||||
};
|
||||
}
|
||||
if (hasOrganizationLevelDebtDueDateOverviewSignal(normalized)) {
|
||||
return {
|
||||
family: "broad_business_evaluation"
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1577,6 +1577,7 @@ describe("address compose stage utf8 headers", () => {
|
||||
expect(reply.responseType).toBe("FACTUAL_LIST");
|
||||
expect(reply.text).toContain("Проверил должников по сроку жизни задолженности");
|
||||
expect(reply.text).toContain("Дата среза: 11.04.2026.");
|
||||
expect(reply.text).toContain("не подтвержденная договорная просрочка или due-date aging");
|
||||
expect(reply.text).toContain("Приоритет ручной проверки (по возрасту долга, по убыванию):");
|
||||
expect(reply.text).toContain("1. Контрагент А | договоры:");
|
||||
expect(reply.text).toContain("2. Контрагент Б | договоры:");
|
||||
@@ -2752,6 +2753,14 @@ describe("address intent resolver expansion (M2.3a)", () => {
|
||||
expect(result.intent).toBe("list_receivables_counterparties");
|
||||
});
|
||||
|
||||
it("defers organization-level overdue debt wording to business overview discovery", () => {
|
||||
const result = resolveAddressIntent(
|
||||
"\u043a\u0430\u043a\u0430\u044f \u0443 \u043d\u0430\u0441 \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a\u0430 \u043f\u043e \u0434\u043e\u043b\u0433\u0430\u043c \u0437\u0430 2020?"
|
||||
);
|
||||
expect(result.intent).toBe("unknown");
|
||||
expect(result.reasons).toContain("unicode_business_overview_debt_due_date_deferred_to_discovery");
|
||||
});
|
||||
|
||||
it("routes reconciliation mismatch wording into open contracts intent", () => {
|
||||
const result = resolveAddressIntent(
|
||||
"Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?"
|
||||
|
||||
@@ -1992,6 +1992,37 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||
});
|
||||
|
||||
it("routes organization-level overdue debt wording to business overview instead of exact receivables recipes", () => {
|
||||
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage:
|
||||
"\u043a\u0430\u043a\u0430\u044f \u0443 \u043d\u0430\u0441 \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a\u0430 \u043f\u043e \u0434\u043e\u043b\u0433\u0430\u043c \u0437\u0430 2020?",
|
||||
followupContext: {
|
||||
previous_discovery_pilot_scope: "open_items_by_counterparty_or_contract",
|
||||
previous_filters: {
|
||||
organization: orgName,
|
||||
as_of_date: "2026-04-23"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("business overview evidence with bounded analyst interpretation");
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_organization_scope: orgName,
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.reason_codes).toContain("mcp_discovery_broad_business_evaluation_route_candidate");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_date_scope_from_followup_context");
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||
});
|
||||
|
||||
it("resumes an open-scope total clarification loop from saved state when the user resolves the pending period with all-time wording", () => {
|
||||
const orgName = "ООО Альтернатива Плюс";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
|
||||
@@ -177,5 +177,15 @@ describe("assistantTurnMeaningPolicy", () => {
|
||||
expect(profitMargin.asked_action_family).toBe("broad_evaluation");
|
||||
expect(profitMargin.unsupported_but_understood_family).toBe("broad_business_evaluation");
|
||||
expect(profitMargin.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
||||
|
||||
const overdueDebt = policy.resolveAssistantTurnMeaning({
|
||||
rawUserMessage:
|
||||
"\u043a\u0430\u043a\u0430\u044f \u0443 \u043d\u0430\u0441 \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a\u0430 \u043f\u043e \u0434\u043e\u043b\u0433\u0430\u043c \u0437\u0430 2020?"
|
||||
});
|
||||
expect(overdueDebt.explicit_intent_candidate).toBeNull();
|
||||
expect(overdueDebt.asked_domain_family).toBe("business_summary");
|
||||
expect(overdueDebt.asked_action_family).toBe("broad_evaluation");
|
||||
expect(overdueDebt.unsupported_but_understood_family).toBe("broad_business_evaluation");
|
||||
expect(overdueDebt.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user