Укрепить автономные reviewed-маршруты и срезы задолженности

This commit is contained in:
2026-05-12 23:26:20 +03:00
parent 5dedfeee86
commit b99a3be083
46 changed files with 3234 additions and 142 deletions
@@ -1758,6 +1758,10 @@ function resolveUnicodeAddressIntentBridge(text) {
if (!hasContractCue && hasCustomerConcentrationCue) {
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_concentration_bridge_signal_detected");
}
const hasTopYearRevenueRankingCue = /(?:(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0438\u0435|\u043a\u0430\u043a\u0430\u044f|what|which)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,60}(?:\u0433\u043e\u0434|year)|(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,60}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,60}(?:\u0433\u043e\u0434|year))/iu.test(normalized);
if (!hasContractCue && (hasTopYearRevenueRankingCue || hasCustomerRevenueRankingBridgeSignal(normalized))) {
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_ranking_bridge_signal_detected");
}
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
}
@@ -2021,14 +2025,52 @@ function resolveUnicodeAddressIntentBridge(text) {
}
return null;
}
function resolveDirectDebtSnapshotIntent(text) {
const normalized = String(text ?? "").trim().toLowerCase();
if (!normalized) {
return null;
}
if (/(?:ндс|vat)/iu.test(normalized)) {
return null;
}
const hasSnapshotCue = /(?:кто|сколько|есть\s+ли|по\s+состоянию|на\s+сегодня|на\s+дату|срез|остаток|сальдо|баланс|на\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?|на\s+(?:19|20)\d{2}|as\s+of|today|current|balance)/iu.test(normalized);
const hasReceivablesCue = /(?:кто\s+(?:является\s+)?дебитором|дебитор(?:[а-яё]{0,8})?|дебиторск(?:[а-яё]{0,8})?|кто\s+нам\s+долж(?:ен|ны|на|но)?|нам\s+(?:кто-то\s+|кто\s+)?долж(?:ен|ны|на|но)?|нам\s+торч(?:ат|ит|ишь|у|али)?|к\s+получению|к\s+взысканию|who\s+owes\s+us|receivables?|accounts\s+receivable)/iu.test(normalized);
const hasPayablesCue = /(?:кто\s+(?:является\s+)?кредитором|кредитор(?:[а-яё]{0,8})?|кому\s+мы\s+долж(?:ны|н[ао])?|мы\s+долж(?:ны|н[ао])?\s+кому|мы\s+долж(?:ны|н[ао])?|к\s+оплате|who\s+we\s+owe|payables?|accounts\s+payable)/iu.test(normalized);
if (hasReceivablesCue && !hasPayablesCue && hasSnapshotCue) {
return {
intent: "receivables_confirmed_as_of_date",
confidence: "high",
reasons: ["receivables_debt_lifecycle_signal_detected", "direct_debt_snapshot_signal_detected"]
};
}
if (hasPayablesCue && !hasReceivablesCue && hasSnapshotCue) {
return {
intent: "payables_confirmed_as_of_date",
confidence: "high",
reasons: ["payables_debt_lifecycle_signal_detected", "direct_debt_snapshot_signal_detected"]
};
}
return null;
}
function resolveAddressIntent(userMessage) {
const text = String(userMessage ?? "").trim().toLowerCase();
const repairedText = repairLikelyUtf8Mojibake(text).trim().toLowerCase();
const bridgeText = repairedText && repairedText !== text ? `${text} ${repairedText}` : text;
const turnNoiseNormalizedBridgeText = bridgeText
.replace(/(^|[^\p{L}0-9_])\u043d\u0430\u043c\u0441(?=$|[^\p{L}0-9_])/giu, "$1\u043d\u0430\u043c")
.replace(/(^|[^\p{L}0-9_])\u043a\u0430\u043a\u0438\u0435\u043a(?=$|[^\p{L}0-9_])/giu, "$1\u043a\u0430\u043a\u0438\u0435");
.replace(/(^|[^\p{L}0-9_])намс(?=$|[^\p{L}0-9_])/giu, "$1нам")
.replace(/(^|[^\p{L}0-9_])какиек(?=$|[^\p{L}0-9_])/giu, "$1какие");
const currentTurnBridgeText = turnNoiseNormalizedBridgeText !== bridgeText ? `${bridgeText} ${turnNoiseNormalizedBridgeText}` : bridgeText;
const directDebtSnapshotIntent = resolveDirectDebtSnapshotIntent(currentTurnBridgeText);
if (directDebtSnapshotIntent) {
const reasons = [...directDebtSnapshotIntent.reasons];
if (currentTurnBridgeText !== bridgeText && !reasons.includes("current_turn_noise_normalized")) {
reasons.push("current_turn_noise_normalized");
}
return {
...directDebtSnapshotIntent,
reasons
};
}
const unicodeAddressIntent = resolveUnicodeAddressIntentBridge(currentTurnBridgeText);
if (unicodeAddressIntent) {
const reasons = [...unicodeAddressIntent.reasons];
@@ -1822,10 +1822,10 @@ function buildAddressRecipePlan(recipe, filters) {
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
@@ -1840,10 +1840,10 @@ function buildAddressRecipePlan(recipe, filters) {
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: MOVEMENTS_QUERY_TEMPLATE
@@ -266,6 +266,17 @@ function normalizeQuestionText(value) {
.replace(/\s+/g, " ")
.trim();
}
function isReportStyleBusinessQuestion(userMessage) {
const text = normalizeQuestionText(userMessage);
return /(?:обзор|анализ|подроб|разверн|оцен|аудит|report|review|analysis)/iu.test(text);
}
function isDirectBalanceQuestion(userMessage) {
const text = normalizeQuestionText(userMessage);
if (!text || isReportStyleBusinessQuestion(text)) {
return false;
}
return /(?:кто|кому|сколько|какой|какая|какие|есть\s+ли|долж|дебитор|кредитор|payables?|receivables?|who|how\s+much)/iu.test(text);
}
function hasInventoryPurchaseDateActionFocus(userMessage) {
const text = normalizeQuestionText(userMessage);
if (!text) {
@@ -579,25 +590,42 @@ function extractRequestedYearFromQuestion(userMessage) {
return 2000 + shortYear;
}
function extractCounterpartyName(row) {
const skipTokenPattern = /(?:^0$|^<пусто>$|^пустая ссылка$|договор|contract|документ|операц|счет[-\s]?фактур|накладн|акт|поступлен|списани|плат[её]ж|перевод|банк|касса|расчетн|проводк|movement|invoice|payment)/iu;
for (const token of row.analytics) {
const normalized = String(token ?? "").trim();
const isCounterpartyLikeToken = (value, skipPattern) => {
const normalized = String(value ?? "").trim();
if (!normalized) {
continue;
return null;
}
if (/^\d{4}-\d{2}-\d{2}/.test(normalized)) {
continue;
return null;
}
if (/^\d+(?:[./-]\d+)*$/.test(normalized)) {
continue;
return null;
}
if (!/[a-zа-я]/iu.test(normalized)) {
continue;
return null;
}
if (skipTokenPattern.test(normalized)) {
continue;
if (skipPattern.test(normalized)) {
return null;
}
return normalized;
};
const hardSkipTokenPattern = /(?:^0$|^<пусто>$|^пустая ссылка$|договор|contract|документ|операц|счет[-\s]?фактур|накладн|акт|поступлен|списани|плат[её]ж|перевод|касса|расчетн|проводк|movement|invoice|payment)/iu;
const skipTokenPattern = /(?:^0$|^<пусто>$|^пустая ссылка$|договор|contract|документ|операц|счет[-\s]?фактур|накладн|акт|поступлен|списани|плат[её]ж|перевод|банк|касса|расчетн|проводк|movement|invoice|payment)/iu;
const directCounterparty = isCounterpartyLikeToken(row.counterparty, hardSkipTokenPattern);
if (directCounterparty) {
return directCounterparty;
}
if (/остатки\s+на\s+дату/iu.test(row.registrator)) {
const balancePrimaryCounterparty = isCounterpartyLikeToken(row.analytics[0], hardSkipTokenPattern);
if (balancePrimaryCounterparty) {
return balancePrimaryCounterparty;
}
}
for (const token of row.analytics) {
const normalized = isCounterpartyLikeToken(token, skipTokenPattern);
if (normalized) {
return normalized;
}
}
for (const token of row.analytics) {
const normalized = String(token ?? "").trim();
@@ -1151,6 +1179,16 @@ function hasReceivablesSectionPrefix(account) {
const section = extractAccountSectionCode(account);
return section === "62" || section === "76";
}
function normalizeSettlementAccount(value) {
const normalized = String(value ?? "")
.trim()
.replace(",", ".");
return normalized || null;
}
function extractSettlementOrganizationName(row) {
const direct = String(row.organization ?? "").trim();
return direct || null;
}
function resolvePayablesAsOfDate(options) {
const explicit = normalizeIsoDateOnly(options.asOfDate);
if (explicit) {
@@ -1430,6 +1468,211 @@ function buildReceivablesConfirmedBalanceAggregate(rows, asOfDate) {
return left.name.localeCompare(right.name);
});
}
function buildConfirmedDebtBalanceSnapshot(rows, asOfDate, hasRelevantSectionPrefix, positiveSide) {
const bySettlementKey = new Map();
const asOfTimestamp = toUtcDayTimestamp(asOfDate);
for (const row of rows) {
const name = extractCounterpartyName(row);
if (!name) {
continue;
}
const rowTimestamp = toUtcDayTimestamp(row.period);
if (asOfTimestamp !== null && rowTimestamp !== null && rowTimestamp > asOfTimestamp) {
continue;
}
const amount = row.amount;
if (typeof amount !== "number" || !Number.isFinite(amount)) {
continue;
}
const absAmount = Math.abs(amount);
const debitAccount = normalizeSettlementAccount(row.account_dt);
const creditAccount = normalizeSettlementAccount(row.account_kt);
const contributions = [];
if (debitAccount && hasRelevantSectionPrefix(debitAccount)) {
contributions.push({ side: "debit", account: debitAccount });
}
if (creditAccount && hasRelevantSectionPrefix(creditAccount)) {
contributions.push({ side: "credit", account: creditAccount });
}
if (contributions.length === 0) {
continue;
}
const contract = extractSettlementBalanceAnalyticKey(row, name);
const organization = extractSettlementOrganizationName(row);
const classified = classifyPayablesLiabilityCategory(row, name);
const sourceRefs = extractPayablesSourceRefs(row, name, contract);
for (const contribution of contributions) {
const key = [
normalizeEntityToken(organization),
normalizeEntityToken(contribution.account),
normalizeEntityToken(name),
normalizeEntityToken(contract)
].join("|");
const current = bySettlementKey.get(key);
if (!current) {
bySettlementKey.set(key, {
name,
account: contribution.account,
contract,
organization,
debitAmount: contribution.side === "debit" ? absAmount : 0,
creditAmount: contribution.side === "credit" ? absAmount : 0,
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),
contracts: new Set(contract ? [contract] : []),
documents: new Set(row.registrator ? [row.registrator] : []),
sourceRefs: new Set(sourceRefs)
});
continue;
}
if (contribution.side === "debit") {
current.debitAmount += absAmount;
}
else {
current.creditAmount += absAmount;
}
current.operations += 1;
if ((row.period ?? "") < (current.firstPeriod ?? "")) {
current.firstPeriod = row.period;
}
if ((row.period ?? "") > (current.lastPeriod ?? "")) {
current.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);
}
if (contract) {
current.contracts.add(contract);
}
if (row.registrator) {
current.documents.add(row.registrator);
}
for (const ref of sourceRefs) {
current.sourceRefs.add(ref);
}
}
}
const byCounterparty = new Map();
const mirrorGroups = [];
let mirroredOffsetAmount = 0;
for (const group of bySettlementKey.values()) {
const offsetAmount = Math.min(group.debitAmount, group.creditAmount);
const netDebitMinusCredit = group.debitAmount - group.creditAmount;
if (offsetAmount > 0.005) {
mirroredOffsetAmount += offsetAmount;
mirrorGroups.push({
name: group.name,
account: group.account,
contract: group.contract,
organization: group.organization,
debitAmount: group.debitAmount,
creditAmount: group.creditAmount,
offsetAmount,
netAmount: netDebitMinusCredit,
operations: group.operations,
sourceRefs: Array.from(group.sourceRefs).slice(0, 3)
});
}
const sideNetAmount = positiveSide === "credit" ? group.creditAmount - group.debitAmount : group.debitAmount - group.creditAmount;
if (sideNetAmount <= 0.005) {
continue;
}
const current = byCounterparty.get(group.name);
if (!current) {
byCounterparty.set(group.name, {
outstandingAmount: sideNetAmount,
operations: group.operations,
firstPeriod: group.firstPeriod,
lastPeriod: group.lastPeriod,
categoryScores: {
supplier_or_contractor: group.categoryScores.supplier_or_contractor,
bank_or_credit: group.categoryScores.bank_or_credit,
tax_or_state: group.categoryScores.tax_or_state,
other: group.categoryScores.other
},
reasons: new Set(group.reasons),
contracts: new Set(group.contracts),
documents: new Set(group.documents),
sourceRefs: new Set(group.sourceRefs)
});
continue;
}
current.outstandingAmount += sideNetAmount;
current.operations += group.operations;
if ((group.firstPeriod ?? "") < (current.firstPeriod ?? "")) {
current.firstPeriod = group.firstPeriod;
}
if ((group.lastPeriod ?? "") > (current.lastPeriod ?? "")) {
current.lastPeriod = group.lastPeriod;
}
current.categoryScores.supplier_or_contractor += group.categoryScores.supplier_or_contractor;
current.categoryScores.bank_or_credit += group.categoryScores.bank_or_credit;
current.categoryScores.tax_or_state += group.categoryScores.tax_or_state;
current.categoryScores.other += group.categoryScores.other;
for (const reason of group.reasons) {
current.reasons.add(reason);
}
for (const contract of group.contracts) {
current.contracts.add(contract);
}
for (const document of group.documents) {
current.documents.add(document);
}
for (const ref of group.sourceRefs) {
current.sourceRefs.add(ref);
}
}
return {
balances: Array.from(byCounterparty.entries())
.map(([name, item]) => ({
name,
outstandingAmount: item.outstandingAmount,
operations: item.operations,
firstPeriod: item.firstPeriod,
lastPeriod: item.lastPeriod,
category: resolvePayablesLiabilityCategory(item.categoryScores),
categoryReasons: Array.from(item.reasons).slice(0, 2),
contracts: Array.from(item.contracts).slice(0, 2),
documents: Array.from(item.documents).slice(0, 2),
sourceRefs: Array.from(item.sourceRefs).slice(0, 3)
}))
.filter((item) => item.outstandingAmount > 0.005)
.sort((left, right) => {
if (right.outstandingAmount !== left.outstandingAmount) {
return right.outstandingAmount - left.outstandingAmount;
}
if (right.operations !== left.operations) {
return right.operations - left.operations;
}
return left.name.localeCompare(right.name);
}),
mirrorGroups: mirrorGroups.sort((left, right) => {
if (right.offsetAmount !== left.offsetAmount) {
return right.offsetAmount - left.offsetAmount;
}
return left.name.localeCompare(right.name);
}),
mirroredOffsetAmount
};
}
function buildPayablesConfirmedBalanceSnapshot(rows, asOfDate) {
return buildConfirmedDebtBalanceSnapshot(rows, asOfDate, hasPayablesSectionPrefix, "credit");
}
function buildReceivablesConfirmedBalanceSnapshot(rows, asOfDate) {
return buildConfirmedDebtBalanceSnapshot(rows, asOfDate, hasReceivablesSectionPrefix, "debit");
}
function buildCounterpartyRiskAggregate(rows) {
const byCounterparty = new Map();
for (const row of rows) {
@@ -1603,6 +1846,45 @@ function extractContractName(row) {
}
return null;
}
function extractSettlementBalanceAnalyticKey(row, counterparty) {
const counterpartyToken = normalizeSettlementComparableToken(counterparty);
const organizationToken = normalizeSettlementComparableToken(extractSettlementOrganizationName(row));
const contract = extractContractName(row);
if (contract) {
const contractToken = normalizeSettlementComparableToken(contract);
if (contractToken &&
contractToken !== counterpartyToken &&
contractToken !== organizationToken &&
!(Boolean(organizationToken) && contractToken.includes(organizationToken)) &&
!/^организац/.test(contractToken)) {
return contract;
}
}
for (const token of row.analytics) {
const normalized = String(token ?? "").trim();
const normalizedToken = normalizeSettlementComparableToken(normalized);
if (!normalized || !normalizedToken) {
continue;
}
if (/^(?:0|<пусто>|пустая ссылка)$/iu.test(normalized)) {
continue;
}
if (/^\d{4}-\d{2}-\d{2}/.test(normalized) || /^\d+(?:[.,]\d+)?$/.test(normalized)) {
continue;
}
if (/^\d{2}(?:\.\d{1,2})?$/.test(normalized)) {
continue;
}
if (normalizedToken === counterpartyToken ||
normalizedToken === organizationToken ||
(Boolean(organizationToken) && normalizedToken.includes(organizationToken)) ||
/^организац/.test(normalizedToken)) {
continue;
}
return normalized;
}
return null;
}
function normalizeEntityToken(value) {
return String(value ?? "")
.toLowerCase()
@@ -1610,6 +1892,12 @@ function normalizeEntityToken(value) {
.replace(/\s+/g, " ")
.trim();
}
function normalizeSettlementComparableToken(value) {
return normalizeEntityToken(value)
.replace(/[^\p{L}0-9]+/giu, " ")
.replace(/\s+/g, " ")
.trim();
}
function extractPayablesSourceRefs(row, counterparty, contract) {
const refs = new Set();
const counterpartyToken = normalizeEntityToken(counterparty);
@@ -1656,6 +1944,41 @@ function formatPayablesEvidenceSuffix(item) {
}
return parts.length > 0 ? ` | ${parts.join(" | ")}` : "";
}
function formatDebtMirrorGroupLine(item) {
const details = [
item.account ? `счет ${item.account}` : null,
item.contract ? `договор/аналитика: ${item.contract}` : null,
item.organization ? `организация: ${item.organization}` : null
].filter((part) => Boolean(part));
const netText = Math.abs(item.netAmount) <= 0.005
? "чисто: 0 ₽"
: item.netAmount > 0
? `чисто к получению: ${formatMoneyRub(item.netAmount)}`
: `чисто к оплате: ${formatMoneyRub(Math.abs(item.netAmount))}`;
return `${item.name}${details.length > 0 ? ` (${details.join(", ")})` : ""}: дебет ${formatMoneyRub(item.debitAmount)} / кредит ${formatMoneyRub(item.creditAmount)}, ${netText}.`;
}
function debtMirrorCleanScopeLabel(kind) {
return kind === "payables" ? "чистый долг к оплате" : "чистую дебиторку к получению";
}
function appendDebtMirrorCompactDisclosure(lines, snapshot, kind) {
if (snapshot.mirroredOffsetAmount <= 0.005) {
return;
}
lines.push(`Отдельно сверено встречных остатков: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; они не включены в ${debtMirrorCleanScopeLabel(kind)}.`);
const leadingMirror = snapshot.mirrorGroups[0] ?? null;
if (leadingMirror) {
lines.push(`Крупнейший встречный хвост: ${formatDebtMirrorGroupLine(leadingMirror)}`);
}
}
function appendDebtMirrorDisclosure(lines, snapshot, kind) {
if (snapshot.mirroredOffsetAmount <= 0.005) {
return;
}
lines.push("");
lines.push("Встречные остатки к сверке");
lines.push(`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она исключена из ${debtMirrorCleanScopeLabel(kind)}.`);
lines.push(...snapshot.mirrorGroups.slice(0, 3).map((item, index) => `${index + 1}. ${formatDebtMirrorGroupLine(item)}`));
}
function deriveOperationalYearWindow(yearDocs, yearOps) {
const docsSeries = [...yearDocs].sort((a, b) => a.year - b.year);
const fallbackSeries = [...yearOps].sort((a, b) => a.year - b.year);
@@ -2955,7 +3278,8 @@ function composeFactualReplyBody(intent, rows, options = {}) {
}
if (intent === "payables_confirmed_as_of_date") {
const payablesAsOfDate = resolvePayablesAsOfDate(options);
const confirmedBalances = buildPayablesConfirmedBalanceAggregate(rows, payablesAsOfDate);
const balanceSnapshot = buildPayablesConfirmedBalanceSnapshot(rows, payablesAsOfDate);
const confirmedBalances = balanceSnapshot.balances;
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
const periodTo = normalizeIsoDateOnly(options.periodTo);
@@ -2970,6 +3294,35 @@ function composeFactualReplyBody(intent, rows, options = {}) {
acc[item.category] += 1;
return acc;
}, { supplier_or_contractor: 0, bank_or_credit: 0, tax_or_state: 0, other: 0 });
if (isDirectBalanceQuestion(options.userMessage)) {
const leading = confirmedBalances[0] ?? null;
const compactLines = leading
? [
`Коротко: на ${formatDateRu(payablesAsOfDate)} мы должны ${formatMoneyRub(totalOutstandingAmount)}; крупнейшая позиция — ${leading.name} (${formatMoneyRub(leading.outstandingAmount)}).`,
"Крупнейшие позиции к оплате:"
]
: [`Коротко: на ${formatDateRu(payablesAsOfDate)} подтвержденных обязательств к оплате не найдено.`];
if (leading) {
compactLines.push(...confirmedBalances.slice(0, 5).map((item, index) => {
const lastPeriod = item.lastPeriod ? `, последнее движение: ${item.lastPeriod}` : "";
return `${index + 1}. ${item.name}${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.${lastPeriod}).`;
}));
if (confirmedBalances.length > 5) {
compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`);
}
}
appendDebtMirrorCompactDisclosure(compactLines, balanceSnapshot, "payables");
compactLines.push(`Основа: подтвержденный остаток по счетам 60/76, срез ${formatDateRu(payablesAsOfDate)}.`);
return {
responseType: confirmedBalances.length > 0 ? "FACTUAL_LIST" : "FACTUAL_SUMMARY",
text: joinLines(compactLines),
semantics: {
result_mode: "confirmed_balance",
evidence_strength: confirmedBalances.length > 0 ? "strong" : "medium",
balance_confirmed: true
}
};
}
const lines = [
`Коротко: подтвержденный долг к оплате на ${formatDateRu(payablesAsOfDate)}${formatMoneyRub(totalOutstandingAmount)}.`,
"Это подтвержденный срез обязательств к оплате по точному остатку."
@@ -2988,6 +3341,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
lines.push("Сводка");
lines.push(`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`);
lines.push(`- Контрагентов с подтвержденным остатком к оплате: ${formatNumberWithDots(confirmedBalances.length)}.`);
appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables");
lines.push("");
lines.push("Категории обязательств");
lines.push(`- ${liabilityCategoryLabel("supplier_or_contractor")}: ${formatNumberWithDots(categoryCounts.supplier_or_contractor)}.`);
@@ -3020,7 +3374,8 @@ function composeFactualReplyBody(intent, rows, options = {}) {
}
if (intent === "receivables_confirmed_as_of_date") {
const receivablesAsOfDate = resolveReceivablesAsOfDate(options);
const confirmedBalances = buildReceivablesConfirmedBalanceAggregate(rows, receivablesAsOfDate);
const balanceSnapshot = buildReceivablesConfirmedBalanceSnapshot(rows, receivablesAsOfDate);
const confirmedBalances = balanceSnapshot.balances;
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
const periodTo = normalizeIsoDateOnly(options.periodTo);
@@ -3035,6 +3390,35 @@ function composeFactualReplyBody(intent, rows, options = {}) {
acc[item.category] += 1;
return acc;
}, { supplier_or_contractor: 0, bank_or_credit: 0, tax_or_state: 0, other: 0 });
if (isDirectBalanceQuestion(options.userMessage)) {
const leading = confirmedBalances[0] ?? null;
const compactLines = leading
? [
`Коротко: на ${formatDateRu(receivablesAsOfDate)} нам должны ${formatMoneyRub(totalOutstandingAmount)}; крупнейшая позиция — ${leading.name} (${formatMoneyRub(leading.outstandingAmount)}).`,
"Крупнейшие позиции к получению:"
]
: [`Коротко: на ${formatDateRu(receivablesAsOfDate)} подтвержденной дебиторской задолженности не найдено.`];
if (leading) {
compactLines.push(...confirmedBalances.slice(0, 5).map((item, index) => {
const lastPeriod = item.lastPeriod ? `, последнее движение: ${item.lastPeriod}` : "";
return `${index + 1}. ${item.name}${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.${lastPeriod}).`;
}));
if (confirmedBalances.length > 5) {
compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`);
}
}
appendDebtMirrorCompactDisclosure(compactLines, balanceSnapshot, "receivables");
compactLines.push(`Основа: подтвержденный остаток по счетам 62/76, срез ${formatDateRu(receivablesAsOfDate)}.`);
return {
responseType: confirmedBalances.length > 0 ? "FACTUAL_LIST" : "FACTUAL_SUMMARY",
text: joinLines(compactLines),
semantics: {
result_mode: "confirmed_balance",
evidence_strength: confirmedBalances.length > 0 ? "strong" : "medium",
balance_confirmed: true
}
};
}
const lines = [
`Коротко: подтвержденная дебиторская задолженность на ${formatDateRu(receivablesAsOfDate)}${formatMoneyRub(totalOutstandingAmount)}.`,
"Это подтвержденный срез дебиторской задолженности, а не эвристический shortlist."
@@ -3053,6 +3437,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
lines.push("Сводка");
lines.push(`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`);
lines.push(`- Контрагентов с подтвержденным остатком к получению: ${formatNumberWithDots(confirmedBalances.length)}.`);
appendDebtMirrorDisclosure(lines, balanceSnapshot, "receivables");
lines.push("");
lines.push("Категории дебиторской задолженности");
lines.push(`- ${receivablesCategoryLabel("supplier_or_contractor")}: ${formatNumberWithDots(categoryCounts.supplier_or_contractor)}.`);
@@ -3160,7 +3545,8 @@ function composeFactualReplyBody(intent, rows, options = {}) {
return lines;
};
if (options.requestedResultMode === "confirmed_balance") {
const confirmedBalances = buildPayablesConfirmedBalanceAggregate(rows, payablesAsOfDate);
const balanceSnapshot = buildPayablesConfirmedBalanceSnapshot(rows, payablesAsOfDate);
const confirmedBalances = balanceSnapshot.balances;
if (confirmedBalances.length > 0) {
const categoryCounts = confirmedBalances.reduce((acc, item) => {
acc[item.category] += 1;
@@ -3195,6 +3581,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
"Блок 5. Крупнейшие подтвержденные позиции к оплате (по сумме остатка):",
...confirmedBalances.slice(0, 10).map((item, index) => `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток к оплате: ${formatMoneyRub(item.outstandingAmount)} | операций в срезе: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`)
];
appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables");
return {
responseType: "FACTUAL_LIST",
text: joinLines(lines),
@@ -391,7 +391,11 @@ function composeCounterpartyAnalyticsReply(intent, rows, options = {}, deps) {
/(?:какой|кто|which|who|какой|кто)/iu.test(normalizedQuestion) &&
/(?:больше\s+всего|сам(?:ый|ая|ое|ые)|наибольш|прин[её]с|highest|most|больше\s+всего|сам(?:ый|ая|РѕРµ|ые)|наибол|РїСЂРёРЅ[её]СЃ)/iu.test(normalizedQuestion) &&
!/(?:\btop\b|топ|рейтинг|список|первые|покажи\s+топ|дай\s+топ|покаж\w*\s+топ|дай\s+топ)/iu.test(normalizedQuestion);
const effectiveLimit = asksSingleBestCounterparty ? 1 : limit;
const asksExplicitRankingList = /(?:\btop\b|топ|рейтинг|список|первые|покажи\s+(?:топ|список)|дай\s+(?:топ|список)|show\s+(?:top|list))/iu.test(normalizedQuestion);
const hasSingleBestCounterpartyCue = /(?:сам\p{L}*|больше\s+всего|наибольш|прин[её]с|определ\p{L}*|найд\p{L}*|highest|largest|most)/iu.test(normalizedQuestion) &&
/(?:клиент|заказчик|покупател|контрагент|customer|client|counterparty|buyer)/iu.test(normalizedQuestion);
const semanticSingleBestCounterparty = focus === "top_by_total" && hasSingleBestCounterpartyCue && !asksExplicitRankingList;
const effectiveLimit = asksSingleBestCounterparty || semanticSingleBestCounterparty ? 1 : limit;
const byCounterparty = new Map();
const byYear = new Map();
const deals = [];
@@ -126,6 +126,14 @@ function hasExplicitLooseByAnchorToken(text) {
return !pronounTokens.has(token) && !genericTokens.has(token);
}
const FOLLOWUP_LOW_QUALITY_COUNTERPARTY_TOKENS = new Set([
"мы",
"нам",
"нас",
"наш",
"наша",
"наше",
"наши",
"унас",
"есть",
"же",
"что",
@@ -1148,6 +1156,12 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
previousOrganization ??
(followupContext.previous_anchor_type === "organization" ? previousAnchorValue : null);
const finalCounterparty = toNonEmptyString(merged.counterparty);
if (finalCounterparty && isLowQualityCounterpartyAnchor(finalCounterparty)) {
delete merged.counterparty;
if (!reasons.includes("counterparty_cleared_low_quality_followup_anchor")) {
reasons.push("counterparty_cleared_low_quality_followup_anchor");
}
}
if (shouldSuppressInventoryCounterpartyAlias(intent, finalCounterparty, finalOrganizationReference)) {
delete merged.counterparty;
if (!reasons.includes("counterparty_cleared_as_organization_scope_alias")) {
@@ -371,6 +371,14 @@ function isReferentialCounterpartyPlaceholder(value) {
return false;
}
return new Set([
"мы",
"нам",
"нас",
"наш",
"наша",
"наше",
"наши",
"унас",
"он",
"она",
"оно",
@@ -592,6 +592,35 @@ function businessOverviewDebtDueDateAgingText(overview) {
}
return `Due-date aging на ${aging.as_of_date} проверен: строк с установленным сроком оплаты ${aging.rows_with_payment_terms}, подтвержденной просрочки не найдено; не просрочено по расчету ${aging.not_yet_due_amount_human_ru}.`;
}
function businessOverviewVendorProcurementQualityText(overview) {
const quality = overview.vendor_procurement_quality;
if (!quality) {
return null;
}
const period = quality.period_scope ?? "проверенное окно";
const total = quality.total_outgoing_amount_human_ru;
const top = quality.top_outgoing_counterparty;
const topName = top?.axis_value ?? "получатель не распознан";
const topShare = quality.top_outgoing_share_pct === null ? "" : `, около ${quality.top_outgoing_share_pct}%`;
const topAmount = top?.total_amount_human_ru ? ` (${top.total_amount_human_ru})` : "";
const nonFinancial = quality.top_non_financial_supplier;
const nonFinancialShare = quality.top_non_financial_supplier_share_pct === null ? "" : `, около ${quality.top_non_financial_supplier_share_pct}%`;
const nonFinancialText = nonFinancial
? ` Крупнейший небанковский получатель: ${nonFinancial.axis_value}${nonFinancialShare}${nonFinancial.total_amount_human_ru ? ` (${nonFinancial.total_amount_human_ru})` : ""}.`
: "";
const contractText = quality.used_contracts === null
? ""
: quality.total_contracts === null
? ` Договорный профиль: используется ${quality.used_contracts} договоров.`
: ` Договорный профиль: используется ${quality.used_contracts}/${quality.total_contracts} договоров${quality.used_contract_share_pct === null ? "" : ` (${quality.used_contract_share_pct}%)`}.`;
if (quality.evidence_status === "financial_institution_leads_outgoing_cash") {
return `Проверенный procurement-concentration route за ${period}: крупнейший получатель исходящих денег ${topName}${topShare}${topAmount}, всего исходящих платежей ${total}. По названию это банк/финансовая организация, поэтому зависимость от обычного поставщика этим не подтверждается.${nonFinancialText}${contractText} Надежность поставщиков, качество поставок, назначение каждого платежа и полная структура расходов этим маршрутом не доказаны.`;
}
if (quality.evidence_status === "reviewed_procurement_concentration") {
return `Проверенный procurement-concentration route за ${period}: крупнейший поставщик/получатель исходящих платежей ${topName}${topShare}${topAmount}, всего исходящих платежей ${total}.${contractText} Это проверенный сигнал концентрации закупок/исходящих платежей, но не аудит надежности поставщика, качества поставок и полной структуры расходов.`;
}
return `Procurement-concentration route за ${period} отработал по исходящим платежам на ${total}, но надежной небанковской концентрации поставщика по найденным строкам не хватает.${contractText} Полный vendor-risk аудит не подтвержден.`;
}
function headlineFor(mode, pilot) {
const askedMonthlyBreakdown = pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" ||
pilot.derived_value_flow?.aggregation_axis === "month";
@@ -632,6 +661,10 @@ function headlineFor(mode, pilot) {
return `Коротко: точно подтвердить резерв под неликвиды по текущим данным нельзя; ${inventoryBasis}. Можно честно говорить только о необходимости отдельной проверки склада, списаний/резервов и ликвидационной стоимости, не превращая proxy в доказанный факт резерва.`;
}
if (isVendorRiskBoundaryTurn(pilot)) {
const vendorQualityText = businessOverviewVendorProcurementQualityText(overview);
if (vendorQualityText) {
return vendorQualityText;
}
const supplierLeader = overview.top_suppliers?.[0] ?? null;
const proxyLabel = isFinancialInstitutionBucket(supplierLeader)
? "outgoing cash concentration proxy"
@@ -897,7 +930,12 @@ function buildMustNotClaim(pilot) {
claims.push("Do not present business overview cash-flow spread as profit or margin.");
claims.push("Do not present business overview yearly operating-flow breakdown as profit, financial result, or a complete annual P&L.");
claims.push("Do not present business overview trading-margin proxy as clean profit, accounting financial result, or exact cost-of-sales margin.");
claims.push("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
if (pilot.derived_business_overview?.vendor_procurement_quality) {
claims.push("Do not present reviewed procurement concentration as supplier reliability, delivery quality, payment-purpose classification, or full expense structure.");
}
else {
claims.push("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
}
claims.push("Do not present business overview document/account-section activity profile as process quality, accounting correctness, or completeness of all 1C activity.");
claims.push("Do not present business overview counterparty or contract profile as CRM quality, counterparty due diligence, contract-risk audit, or legal completeness.");
claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked.");
@@ -1279,6 +1317,10 @@ function derivedBusinessOverviewConfirmedLines(pilot) {
if (outgoingLeaderLine) {
lines.push(outgoingLeaderLine);
}
const vendorQualityText = businessOverviewVendorProcurementQualityText(overview);
if (vendorQualityText) {
lines.push(vendorQualityText);
}
if (overview.yearly_breakdown?.length) {
lines.push(`Годовая раскладка операционного денежного потока построена по подтвержденным строкам 1С за ${yearCountHumanRu(overview.yearly_breakdown.length)}.`);
}
@@ -1667,6 +1709,10 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
if (pilot.derived_business_overview?.top_suppliers?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_supplier_concentration");
}
if (pilot.derived_business_overview?.vendor_procurement_quality) {
pushReason(reasonCodes, "answer_contains_business_overview_vendor_procurement_quality");
pushReason(reasonCodes, `answer_contains_business_overview_vendor_procurement_quality_${pilot.derived_business_overview.vendor_procurement_quality.evidence_status}`);
}
if (pilot.derived_business_overview?.yearly_breakdown?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_yearly_operating_breakdown");
}
@@ -3021,6 +3021,46 @@ function deriveBusinessOverviewInventoryStalenessRiskProxy(input) {
inference_basis: "purchase_date_age_and_sales_to_stock_proxy_confirmed_1c_rows"
};
}
function deriveBusinessOverviewVendorProcurementQuality(input) {
if (!input.rankedOutgoing ||
input.rankedOutgoing.ranked_values.length <= 0 ||
input.outgoing.rows_with_amount <= 0 ||
input.outgoing.total_amount <= 0) {
return null;
}
const topOutgoingCounterparty = input.rankedOutgoing.ranked_values[0] ?? null;
const topNonFinancialSupplier = input.rankedOutgoing.ranked_values.find((item) => !(0, counterpartyRoleHeuristics_1.isLikelyFinancialInstitutionCounterparty)(item.axis_value)) ?? null;
const financialInstitutionLeadsOutgoingCash = topOutgoingCounterparty
? (0, counterpartyRoleHeuristics_1.isLikelyFinancialInstitutionCounterparty)(topOutgoingCounterparty.axis_value)
: false;
const evidenceStatus = !topNonFinancialSupplier
? "insufficient_supplier_concentration_basis"
: financialInstitutionLeadsOutgoingCash
? "financial_institution_leads_outgoing_cash"
: "reviewed_procurement_concentration";
return {
period_scope: input.periodScope,
rows_with_amount: input.outgoing.rows_with_amount,
total_outgoing_amount: input.outgoing.total_amount,
total_outgoing_amount_human_ru: input.outgoing.total_amount_human_ru,
top_outgoing_counterparty: topOutgoingCounterparty,
top_outgoing_share_pct: topOutgoingCounterparty
? percentageOfTotal(topOutgoingCounterparty.total_amount, input.outgoing.total_amount)
: null,
top_non_financial_supplier: topNonFinancialSupplier,
top_non_financial_supplier_share_pct: topNonFinancialSupplier
? percentageOfTotal(topNonFinancialSupplier.total_amount, input.outgoing.total_amount)
: null,
financial_institution_leads_outgoing_cash: financialInstitutionLeadsOutgoingCash,
supplier_only_count: input.counterpartyProfile?.supplier_only_count ?? null,
mixed_role_count: input.counterpartyProfile?.mixed_role_count ?? null,
used_contracts: input.contractUsageProfile?.used_contracts ?? null,
total_contracts: input.contractUsageProfile?.total_contracts ?? null,
used_contract_share_pct: input.contractUsageProfile?.used_contract_share_pct ?? null,
evidence_status: evidenceStatus,
inference_basis: "supplier_payout_concentration_counterparty_contract_profile_confirmed_1c_rows"
};
}
function inventoryStalenessRiskBandRu(riskBand) {
if (riskBand === "high") {
return "высокая зона внимания";
@@ -3091,7 +3131,7 @@ function buildBusinessOverviewMissingProofFamilies(input) {
must_not_claim: "confirmed_obsolete_stock_reserve_writeoff_or_liquidation_value"
});
}
if (input.hasSupplierConcentrationSignal) {
if (input.hasSupplierConcentrationSignal && !input.vendorProcurementQuality) {
pushUnique({
family: "vendor_risk_procurement_quality",
current_status: "proxy_only_currently",
@@ -3155,6 +3195,13 @@ function deriveBusinessOverview(input) {
inventoryPosition,
inventoryTurnoverProxy
});
const vendorProcurementQuality = deriveBusinessOverviewVendorProcurementQuality({
rankedOutgoing,
outgoing,
counterpartyProfile,
contractUsageProfile,
periodScope: input.periodScope
});
const checkedSignalCount = [
incoming.rows_with_amount > 0,
outgoing.rows_with_amount > 0,
@@ -3171,7 +3218,8 @@ function deriveBusinessOverview(input) {
Boolean(contractUsageProfile),
Boolean(inventoryPosition),
Boolean(inventoryTurnoverProxy),
Boolean(inventoryStalenessRiskProxy)
Boolean(inventoryStalenessRiskProxy),
Boolean(vendorProcurementQuality)
].filter(Boolean).length;
if (checkedSignalCount <= 0) {
return null;
@@ -3202,6 +3250,7 @@ function deriveBusinessOverview(input) {
inventoryPosition,
inventoryTurnoverProxy,
inventoryStalenessRiskProxy,
vendorProcurementQuality,
hasSupplierConcentrationSignal: (rankedOutgoing?.ranked_values.length ?? 0) > 0
});
return {
@@ -3229,6 +3278,7 @@ function deriveBusinessOverview(input) {
document_activity_profile: documentActivityProfile,
counterparty_profile: counterpartyProfile,
contract_usage_profile: contractUsageProfile,
vendor_procurement_quality: vendorProcurementQuality,
coverage_limited_by_probe_limit: incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
checked_signal_count: checkedSignalCount,
missing_signal_families: missingSignalFamilies,
@@ -4641,6 +4691,10 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
if (derivedBusinessOverview.top_suppliers.length > 0) {
pushReason(reasonCodes, "pilot_derived_business_overview_top_suppliers_from_confirmed_rows");
}
if (derivedBusinessOverview.vendor_procurement_quality) {
pushReason(reasonCodes, "pilot_derived_business_overview_vendor_procurement_quality_from_confirmed_rows");
pushReason(reasonCodes, `pilot_derived_business_overview_vendor_procurement_quality_${derivedBusinessOverview.vendor_procurement_quality.evidence_status}`);
}
if (derivedBusinessOverview.yearly_breakdown.length > 0) {
pushReason(reasonCodes, "pilot_derived_business_overview_yearly_operating_breakdown_from_confirmed_rows");
}
@@ -601,7 +601,7 @@ function recipeFor(input) {
extraReasons: primitiveSelection.reasonCodes
});
}
if (dataNeedGraph?.ranking_need && !hasSubjectCandidates(dataNeedGraph)) {
if (dataNeedGraph?.ranking_need) {
pushUnique(axes, "aggregate_axis");
pushUnique(axes, "amount");
pushUnique(axes, "coverage_target");
@@ -621,7 +621,10 @@ function recipeFor(input) {
reason: dataNeedGraph.ranking_need === "bottom_asc"
? "planner_selected_bottom_ranked_value_flow_from_data_need_graph"
: "planner_selected_top_ranked_value_flow_from_data_need_graph",
extraReasons: primitiveSelection.reasonCodes
extraReasons: [
...primitiveSelection.reasonCodes,
...(hasSubjectCandidates(dataNeedGraph) ? ["planner_kept_ranking_over_subject_scope_candidates"] : [])
]
});
}
if (openScopeTotalWithoutSubject) {
@@ -773,6 +773,47 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
return reply.length > 0 && !hasInternalMechanics(reply) ? reply : null;
}
if (vendorRiskBoundary) {
const vendorProcurementQuality = toRecordObject(overview.vendor_procurement_quality);
if (vendorProcurementQuality) {
const status = toNonEmptyString(vendorProcurementQuality.evidence_status);
const totalOutgoing = moneyText(vendorProcurementQuality.total_outgoing_amount_human_ru);
const topOutgoingRecord = toRecordObject(vendorProcurementQuality.top_outgoing_counterparty);
const topOutgoingName = toNonEmptyString(topOutgoingRecord?.axis_value);
const topOutgoingAmount = moneyText(topOutgoingRecord?.total_amount_human_ru);
const topOutgoingShare = typeof vendorProcurementQuality.top_outgoing_share_pct === "number" &&
Number.isFinite(vendorProcurementQuality.top_outgoing_share_pct)
? `${vendorProcurementQuality.top_outgoing_share_pct}%`
: null;
const nonFinancialRecord = toRecordObject(vendorProcurementQuality.top_non_financial_supplier);
const nonFinancialName = toNonEmptyString(nonFinancialRecord?.axis_value);
const nonFinancialAmount = moneyText(nonFinancialRecord?.total_amount_human_ru);
const nonFinancialShare = typeof vendorProcurementQuality.top_non_financial_supplier_share_pct === "number" &&
Number.isFinite(vendorProcurementQuality.top_non_financial_supplier_share_pct)
? `${vendorProcurementQuality.top_non_financial_supplier_share_pct}%`
: null;
const periodScope = toNonEmptyString(vendorProcurementQuality.period_scope) ?? period;
const totalText = totalOutgoing ? `; всего исходящих платежей в проверенном срезе ${totalOutgoing}` : "";
if (status === "financial_institution_leads_outgoing_cash") {
lines.push(`Коротко: проверенный procurement-concentration route за ${periodScope} не подтверждает зависимость от обычного поставщика: крупнейший получатель исходящих денег ${topOutgoingName ?? "не распознан"}${topOutgoingShare ? ` держит около ${topOutgoingShare}` : ""}${topOutgoingAmount ? ` (${topOutgoingAmount})` : ""}, но по названию это банк/финансовая организация${totalText}.`);
if (nonFinancialName) {
lines.push(`Крупнейший небанковский получатель исходящих денег: ${nonFinancialName}${nonFinancialShare ? `, около ${nonFinancialShare}` : ""}${nonFinancialAmount ? ` (${nonFinancialAmount})` : ""}. Это уже сигнал закупочной/исходящей концентрации, но не аудит надежности поставщика.`);
}
}
else if (status === "reviewed_procurement_concentration") {
lines.push(`Коротко: проверенный procurement-concentration route за ${periodScope} нашел основную зависимость исходящего потока: ${topOutgoingName ?? nonFinancialName ?? "получатель не распознан"}${topOutgoingShare ? ` держит около ${topOutgoingShare}` : nonFinancialShare ? ` держит около ${nonFinancialShare}` : ""}${topOutgoingAmount ? ` (${topOutgoingAmount})` : nonFinancialAmount ? ` (${nonFinancialAmount})` : ""}${totalText}.`);
}
else {
lines.push(`Коротко: procurement-concentration route за ${periodScope} отработал, но надежной небанковской концентрации поставщика по найденным исходящим платежам не хватает${totalText}.`);
}
const contractText = typeof vendorProcurementQuality.used_contracts === "number" && Number.isFinite(vendorProcurementQuality.used_contracts)
? typeof vendorProcurementQuality.total_contracts === "number" && Number.isFinite(vendorProcurementQuality.total_contracts)
? ` Договорный профиль: используется ${vendorProcurementQuality.used_contracts}/${vendorProcurementQuality.total_contracts} договоров${typeof vendorProcurementQuality.used_contract_share_pct === "number" && Number.isFinite(vendorProcurementQuality.used_contract_share_pct) ? ` (${vendorProcurementQuality.used_contract_share_pct}%)` : ""}.`
: ` Договорный профиль: используется ${vendorProcurementQuality.used_contracts} договоров.`
: "";
lines.push(`Что не доказано этим маршрутом: надежность поставщика, качество поставок, договорные условия, назначение каждого платежа и полная структура всех расходов.${contractText}`);
const reply = lines.join("\n").trim();
return reply.length > 0 && !hasInternalMechanics(reply) ? reply : null;
}
const supplierBasis = topSupplier
? topSupplierLooksFinancial
? `крупнейший получатель исходящих денег: ${topSupplier}; по названию это банк/финансовая организация, поэтому это не доказанная зависимость от обычного поставщика${nonFinancialSupplier ? `; крупнейший небанковский получатель исходящих денег: ${nonFinancialSupplier}` : ""}`
@@ -167,6 +167,21 @@ function isMetadataDiscoveryTurn(entryPoint) {
toNonEmptyString(pilot?.pilot_scope) === "metadata_inspection_v1" ||
reasonCodes.some((reason) => toNonEmptyString(reason) === "mcp_discovery_metadata_signal_detected"));
}
function isExplicitMetadataDiscoveryTurn(entryPoint) {
const turnInput = toRecordObject(entryPoint?.turn_input);
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
const graph = readDiscoveryDataNeedGraph(entryPoint);
const reasonCodes = [
...(Array.isArray(entryPoint?.reason_codes) ? entryPoint.reason_codes : []),
...(Array.isArray(turnInput?.reason_codes) ? turnInput.reason_codes : [])
];
const decompositionCandidates = Array.isArray(graph?.decomposition_candidates) ? graph.decomposition_candidates : [];
return Boolean(toNonEmptyString(turnMeaning?.asked_domain_family) === "metadata" ||
toNonEmptyString(turnMeaning?.unsupported_but_understood_family) === "1c_metadata_surface" ||
toNonEmptyString(graph?.business_fact_family) === "schema_surface" ||
decompositionCandidates.some((candidate) => toNonEmptyString(candidate) === "inspect_metadata_surface") ||
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 ?? ""));
}
@@ -180,6 +195,9 @@ function hasMetadataDiscoveryPriority(input, entryPoint) {
if (!isMetadataDiscoveryTurn(entryPoint)) {
return false;
}
if (!isExplicitMetadataDiscoveryTurn(entryPoint)) {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
return !isInventoryExactAddressIntent(detectedIntent);
}
@@ -250,6 +268,55 @@ function readStringArray(value) {
? value.map((item) => toNonEmptyString(item)).filter((item) => Boolean(item))
: [];
}
function hasConfirmedAddressExecution(input) {
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
const truthMode = toNonEmptyString(input.addressRuntimeMeta?.truth_mode);
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
const bindingStatus = toNonEmptyString(input.addressRuntimeMeta?.capability_binding_status);
const bindingViolations = readStringArray(input.addressRuntimeMeta?.capability_binding_violations);
return Boolean(mcpCallStatus === "matched_non_empty" &&
truthMode === "confirmed" &&
selectedRecipe?.startsWith("address_") &&
(bindingStatus === "bound" || bindingStatus === "bound_with_limits") &&
bindingViolations.length === 0);
}
function hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
if (!isMetadataDiscoveryTurn(entryPoint) || isExplicitMetadataDiscoveryTurn(entryPoint)) {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
return Boolean(detectedIntent &&
hasConfirmedAddressExecution(input) &&
isDetectedIntentAlignedWithTurnMeaning(detectedIntent, readDiscoveryTurnMeaning(entryPoint)));
}
function hasBusinessOverviewDirectMoneyClarification(entryPoint) {
const graph = readDiscoveryDataNeedGraph(entryPoint);
const businessFactFamily = toNonEmptyString(graph?.business_fact_family);
const reasonCodes = readStringArray(graph?.reason_codes);
const clarificationGaps = readStringArray(graph?.clarification_gaps);
return Boolean(businessFactFamily === "business_overview" &&
reasonCodes.includes("data_need_graph_business_overview_direct_money_answer") &&
(toNonEmptyString(graph?.ranking_need) || reasonCodes.includes("data_need_graph_ranking_top_desc")) &&
clarificationGaps.includes("organization"));
}
function hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
return Boolean(detectedIntent === "customer_revenue_and_payments" &&
hasConfirmedAddressExecution(input) &&
hasBusinessOverviewDirectMoneyClarification(entryPoint));
}
function hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
@@ -317,16 +384,7 @@ function hasExactMatchedFactualAddressReply(input, entryPoint) {
return false;
}
}
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
const truthMode = toNonEmptyString(input.addressRuntimeMeta?.truth_mode);
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
const bindingStatus = toNonEmptyString(input.addressRuntimeMeta?.capability_binding_status);
const bindingViolations = readStringArray(input.addressRuntimeMeta?.capability_binding_violations);
return Boolean(mcpCallStatus === "matched_non_empty" &&
truthMode === "confirmed" &&
selectedRecipe?.startsWith("address_") &&
(bindingStatus === "bound" || bindingStatus === "bound_with_limits") &&
bindingViolations.length === 0);
return hasConfirmedAddressExecution(input);
}
function hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
@@ -506,6 +564,8 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
const exactMatchedFactualAddressReply = hasExactMatchedFactualAddressReply(input, entryPoint);
const runtimeAdjustedExactReply = hasRuntimeAdjustedExactReply(input, entryPoint);
const runtimeMatchedExactReply = hasRuntimeMatchedExactReply(input, entryPoint);
const staleMetadataDiscoveryFallbackAgainstExactAddressReply = hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(input, entryPoint);
const exactValueFlowReplyForBusinessOverviewDirectMoneyNeed = hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(input, entryPoint);
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
@@ -561,6 +621,12 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
if (runtimeMatchedExactReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_runtime_matched_exact_reply_over_stale_discovery_turn_meaning");
}
if (staleMetadataDiscoveryFallbackAgainstExactAddressReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_address_reply_over_stale_metadata_discovery");
}
if (exactValueFlowReplyForBusinessOverviewDirectMoneyNeed) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_value_flow_reply_over_business_overview_direct_money_clarification");
}
if (deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate");
}
@@ -585,6 +651,8 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
!exactMatchedFactualAddressReply &&
!runtimeAdjustedExactReply &&
!runtimeMatchedExactReply &&
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
candidate.eligible_for_future_hot_runtime &&
@@ -2315,6 +2315,18 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio
);
}
const hasTopYearRevenueRankingCue =
/(?:(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0438\u0435|\u043a\u0430\u043a\u0430\u044f|what|which)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,60}(?:\u0433\u043e\u0434|year)|(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,60}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,60}(?:\u0433\u043e\u0434|year))/iu.test(
normalized
);
if (!hasContractCue && (hasTopYearRevenueRankingCue || hasCustomerRevenueRankingBridgeSignal(normalized))) {
return unicodeBridgeResolution(
"customer_revenue_and_payments",
"high",
"unicode_customer_revenue_ranking_bridge_signal_detected"
);
}
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
}
@@ -3007,16 +3019,67 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio
return null;
}
function resolveDirectDebtSnapshotIntent(text: string): AddressIntentResolution | null {
const normalized = String(text ?? "").trim().toLowerCase();
if (!normalized) {
return null;
}
if (/(?:ндс|vat)/iu.test(normalized)) {
return null;
}
const hasSnapshotCue =
/(?:кто|сколько|есть\s+ли|по\s+состоянию|на\s+сегодня|на\s+дату|срез|остаток|сальдо|баланс|на\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?|на\s+(?:19|20)\d{2}|as\s+of|today|current|balance)/iu.test(
normalized
);
const hasReceivablesCue =
/(?:кто\s+(?:является\s+)?дебитором|дебитор(?:[а-яё]{0,8})?|дебиторск(?:[а-яё]{0,8})?|кто\s+нам\s+долж(?:ен|ны|на|но)?|нам\s+(?:кто-то\s+|кто\s+)?долж(?:ен|ны|на|но)?|нам\s+торч(?:ат|ит|ишь|у|али)?|к\s+получению|к\s+взысканию|who\s+owes\s+us|receivables?|accounts\s+receivable)/iu.test(
normalized
);
const hasPayablesCue =
/(?:кто\s+(?:является\s+)?кредитором|кредитор(?:[а-яё]{0,8})?|кому\s+мы\s+долж(?:ны|н[ао])?|мы\s+долж(?:ны|н[ао])?\s+кому|мы\s+долж(?:ны|н[ао])?|к\s+оплате|who\s+we\s+owe|payables?|accounts\s+payable)/iu.test(
normalized
);
if (hasReceivablesCue && !hasPayablesCue && hasSnapshotCue) {
return {
intent: "receivables_confirmed_as_of_date",
confidence: "high",
reasons: ["receivables_debt_lifecycle_signal_detected", "direct_debt_snapshot_signal_detected"]
};
}
if (hasPayablesCue && !hasReceivablesCue && hasSnapshotCue) {
return {
intent: "payables_confirmed_as_of_date",
confidence: "high",
reasons: ["payables_debt_lifecycle_signal_detected", "direct_debt_snapshot_signal_detected"]
};
}
return null;
}
export function resolveAddressIntent(userMessage: string): AddressIntentResolution {
const text = String(userMessage ?? "").trim().toLowerCase();
const repairedText = repairLikelyUtf8Mojibake(text).trim().toLowerCase();
const bridgeText = repairedText && repairedText !== text ? `${text} ${repairedText}` : text;
const turnNoiseNormalizedBridgeText = bridgeText
.replace(/(^|[^\p{L}0-9_])\u043d\u0430\u043c\u0441(?=$|[^\p{L}0-9_])/giu, "$1\u043d\u0430\u043c")
.replace(/(^|[^\p{L}0-9_])\u043a\u0430\u043a\u0438\u0435\u043a(?=$|[^\p{L}0-9_])/giu, "$1\u043a\u0430\u043a\u0438\u0435");
.replace(/(^|[^\p{L}0-9_])намс(?=$|[^\p{L}0-9_])/giu, "$1нам")
.replace(/(^|[^\p{L}0-9_])какиек(?=$|[^\p{L}0-9_])/giu, "$1какие");
const currentTurnBridgeText =
turnNoiseNormalizedBridgeText !== bridgeText ? `${bridgeText} ${turnNoiseNormalizedBridgeText}` : bridgeText;
const directDebtSnapshotIntent = resolveDirectDebtSnapshotIntent(currentTurnBridgeText);
if (directDebtSnapshotIntent) {
const reasons = [...directDebtSnapshotIntent.reasons];
if (currentTurnBridgeText !== bridgeText && !reasons.includes("current_turn_noise_normalized")) {
reasons.push("current_turn_noise_normalized");
}
return {
...directDebtSnapshotIntent,
reasons
};
}
const unicodeAddressIntent = resolveUnicodeAddressIntentBridge(currentTurnBridgeText);
if (unicodeAddressIntent) {
const reasons = [...unicodeAddressIntent.reasons];
@@ -2071,10 +2071,10 @@ export function buildAddressRecipePlan(
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
@@ -2090,10 +2090,10 @@ export function buildAddressRecipePlan(
? toDateTimeExpr(filters.period_from, true)
: null) ??
"ТЕКУЩАЯДАТА()";
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
.replaceAll("__LIMIT__", String(resolvedLimit))
.replaceAll("__AS_OF_EXPR__", asOfExpr)
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
})()
: MOVEMENTS_QUERY_TEMPLATE
@@ -401,6 +401,21 @@ function normalizeQuestionText(value: string | null | undefined): string {
.trim();
}
function isReportStyleBusinessQuestion(userMessage: string | null | undefined): boolean {
const text = normalizeQuestionText(userMessage);
return /(?:обзор|анализ|подроб|разверн|оцен|аудит|report|review|analysis)/iu.test(text);
}
function isDirectBalanceQuestion(userMessage: string | null | undefined): boolean {
const text = normalizeQuestionText(userMessage);
if (!text || isReportStyleBusinessQuestion(text)) {
return false;
}
return /(?:кто|кому|сколько|какой|какая|какие|есть\s+ли|долж|дебитор|кредитор|payables?|receivables?|who|how\s+much)/iu.test(
text
);
}
function hasInventoryPurchaseDateActionFocus(userMessage: string | null | undefined): boolean {
const text = normalizeQuestionText(userMessage);
if (!text) {
@@ -780,27 +795,46 @@ function extractRequestedYearFromQuestion(userMessage: string | null | undefined
}
function extractCounterpartyName(row: ComposeStageRow): string | null {
const isCounterpartyLikeToken = (value: unknown, skipPattern: RegExp): string | null => {
const normalized = String(value ?? "").trim();
if (!normalized) {
return null;
}
if (/^\d{4}-\d{2}-\d{2}/.test(normalized)) {
return null;
}
if (/^\d+(?:[./-]\d+)*$/.test(normalized)) {
return null;
}
if (!/[a-zа-я]/iu.test(normalized)) {
return null;
}
if (skipPattern.test(normalized)) {
return null;
}
return normalized;
};
const hardSkipTokenPattern =
/(?:^0$|^<пусто>$|^пустая ссылка$|договор|contract|документ|операц|счет[-\s]?фактур|накладн|акт|поступлен|списани|плат[её]ж|перевод|касса|расчетн|проводк|movement|invoice|payment)/iu;
const skipTokenPattern =
/(?:^0$|^<пусто>$|^пустая ссылка$|договор|contract|документ|операц|счет[-\s]?фактур|накладн|акт|поступлен|списани|плат[её]ж|перевод|банк|касса|расчетн|проводк|movement|invoice|payment)/iu;
const directCounterparty = isCounterpartyLikeToken(row.counterparty, hardSkipTokenPattern);
if (directCounterparty) {
return directCounterparty;
}
if (/остатки\s+на\s+дату/iu.test(row.registrator)) {
const balancePrimaryCounterparty = isCounterpartyLikeToken(row.analytics[0], hardSkipTokenPattern);
if (balancePrimaryCounterparty) {
return balancePrimaryCounterparty;
}
}
for (const token of row.analytics) {
const normalized = String(token ?? "").trim();
if (!normalized) {
continue;
const normalized = isCounterpartyLikeToken(token, skipTokenPattern);
if (normalized) {
return normalized;
}
if (/^\d{4}-\d{2}-\d{2}/.test(normalized)) {
continue;
}
if (/^\d+(?:[./-]\d+)*$/.test(normalized)) {
continue;
}
if (!/[a-zа-я]/iu.test(normalized)) {
continue;
}
if (skipTokenPattern.test(normalized)) {
continue;
}
return normalized;
}
for (const token of row.analytics) {
const normalized = String(token ?? "").trim();
@@ -1457,6 +1491,25 @@ interface PayablesConfirmedBalanceAggregate {
sourceRefs: string[];
}
interface DebtMirrorBalanceGroup {
name: string;
account: string | null;
contract: string | null;
organization: string | null;
debitAmount: number;
creditAmount: number;
offsetAmount: number;
netAmount: number;
operations: number;
sourceRefs: string[];
}
interface ConfirmedDebtBalanceSnapshot {
balances: PayablesConfirmedBalanceAggregate[];
mirrorGroups: DebtMirrorBalanceGroup[];
mirroredOffsetAmount: number;
}
function liabilityCategoryLabel(category: PayablesLiabilityCategory): string {
if (category === "supplier_or_contractor") {
return "поставщики/подрядчики";
@@ -1572,6 +1625,18 @@ function hasReceivablesSectionPrefix(account: string | null): boolean {
return section === "62" || section === "76";
}
function normalizeSettlementAccount(value: string | null | undefined): string | null {
const normalized = String(value ?? "")
.trim()
.replace(",", ".");
return normalized || null;
}
function extractSettlementOrganizationName(row: ComposeStageRow): string | null {
const direct = String(row.organization ?? "").trim();
return direct || null;
}
function resolvePayablesAsOfDate(options: ComposeFactualReplyOptions): string {
const explicit = normalizeIsoDateOnly(options.asOfDate);
if (explicit) {
@@ -1906,6 +1971,259 @@ function buildReceivablesConfirmedBalanceAggregate(
});
}
function buildConfirmedDebtBalanceSnapshot(
rows: ComposeStageRow[],
asOfDate: string,
hasRelevantSectionPrefix: (account: string | null) => boolean,
positiveSide: "debit" | "credit"
): ConfirmedDebtBalanceSnapshot {
const bySettlementKey = new Map<
string,
{
name: string;
account: string | null;
contract: string | null;
organization: string | null;
debitAmount: number;
creditAmount: number;
operations: number;
firstPeriod: string | null;
lastPeriod: string | null;
categoryScores: Record<PayablesLiabilityCategory, number>;
reasons: Set<string>;
contracts: Set<string>;
documents: Set<string>;
sourceRefs: Set<string>;
}
>();
const asOfTimestamp = toUtcDayTimestamp(asOfDate);
for (const row of rows) {
const name = extractCounterpartyName(row);
if (!name) {
continue;
}
const rowTimestamp = toUtcDayTimestamp(row.period);
if (asOfTimestamp !== null && rowTimestamp !== null && rowTimestamp > asOfTimestamp) {
continue;
}
const amount = row.amount;
if (typeof amount !== "number" || !Number.isFinite(amount)) {
continue;
}
const absAmount = Math.abs(amount);
const debitAccount = normalizeSettlementAccount(row.account_dt);
const creditAccount = normalizeSettlementAccount(row.account_kt);
const contributions: Array<{ side: "debit" | "credit"; account: string }> = [];
if (debitAccount && hasRelevantSectionPrefix(debitAccount)) {
contributions.push({ side: "debit", account: debitAccount });
}
if (creditAccount && hasRelevantSectionPrefix(creditAccount)) {
contributions.push({ side: "credit", account: creditAccount });
}
if (contributions.length === 0) {
continue;
}
const contract = extractSettlementBalanceAnalyticKey(row, name);
const organization = extractSettlementOrganizationName(row);
const classified = classifyPayablesLiabilityCategory(row, name);
const sourceRefs = extractPayablesSourceRefs(row, name, contract);
for (const contribution of contributions) {
const key = [
normalizeEntityToken(organization),
normalizeEntityToken(contribution.account),
normalizeEntityToken(name),
normalizeEntityToken(contract)
].join("|");
const current = bySettlementKey.get(key);
if (!current) {
bySettlementKey.set(key, {
name,
account: contribution.account,
contract,
organization,
debitAmount: contribution.side === "debit" ? absAmount : 0,
creditAmount: contribution.side === "credit" ? absAmount : 0,
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),
contracts: new Set(contract ? [contract] : []),
documents: new Set(row.registrator ? [row.registrator] : []),
sourceRefs: new Set(sourceRefs)
});
continue;
}
if (contribution.side === "debit") {
current.debitAmount += absAmount;
} else {
current.creditAmount += absAmount;
}
current.operations += 1;
if ((row.period ?? "") < (current.firstPeriod ?? "")) {
current.firstPeriod = row.period;
}
if ((row.period ?? "") > (current.lastPeriod ?? "")) {
current.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);
}
if (contract) {
current.contracts.add(contract);
}
if (row.registrator) {
current.documents.add(row.registrator);
}
for (const ref of sourceRefs) {
current.sourceRefs.add(ref);
}
}
}
const byCounterparty = new Map<
string,
{
outstandingAmount: number;
operations: number;
firstPeriod: string | null;
lastPeriod: string | null;
categoryScores: Record<PayablesLiabilityCategory, number>;
reasons: Set<string>;
contracts: Set<string>;
documents: Set<string>;
sourceRefs: Set<string>;
}
>();
const mirrorGroups: DebtMirrorBalanceGroup[] = [];
let mirroredOffsetAmount = 0;
for (const group of bySettlementKey.values()) {
const offsetAmount = Math.min(group.debitAmount, group.creditAmount);
const netDebitMinusCredit = group.debitAmount - group.creditAmount;
if (offsetAmount > 0.005) {
mirroredOffsetAmount += offsetAmount;
mirrorGroups.push({
name: group.name,
account: group.account,
contract: group.contract,
organization: group.organization,
debitAmount: group.debitAmount,
creditAmount: group.creditAmount,
offsetAmount,
netAmount: netDebitMinusCredit,
operations: group.operations,
sourceRefs: Array.from(group.sourceRefs).slice(0, 3)
});
}
const sideNetAmount = positiveSide === "credit" ? group.creditAmount - group.debitAmount : group.debitAmount - group.creditAmount;
if (sideNetAmount <= 0.005) {
continue;
}
const current = byCounterparty.get(group.name);
if (!current) {
byCounterparty.set(group.name, {
outstandingAmount: sideNetAmount,
operations: group.operations,
firstPeriod: group.firstPeriod,
lastPeriod: group.lastPeriod,
categoryScores: {
supplier_or_contractor: group.categoryScores.supplier_or_contractor,
bank_or_credit: group.categoryScores.bank_or_credit,
tax_or_state: group.categoryScores.tax_or_state,
other: group.categoryScores.other
},
reasons: new Set(group.reasons),
contracts: new Set(group.contracts),
documents: new Set(group.documents),
sourceRefs: new Set(group.sourceRefs)
});
continue;
}
current.outstandingAmount += sideNetAmount;
current.operations += group.operations;
if ((group.firstPeriod ?? "") < (current.firstPeriod ?? "")) {
current.firstPeriod = group.firstPeriod;
}
if ((group.lastPeriod ?? "") > (current.lastPeriod ?? "")) {
current.lastPeriod = group.lastPeriod;
}
current.categoryScores.supplier_or_contractor += group.categoryScores.supplier_or_contractor;
current.categoryScores.bank_or_credit += group.categoryScores.bank_or_credit;
current.categoryScores.tax_or_state += group.categoryScores.tax_or_state;
current.categoryScores.other += group.categoryScores.other;
for (const reason of group.reasons) {
current.reasons.add(reason);
}
for (const contract of group.contracts) {
current.contracts.add(contract);
}
for (const document of group.documents) {
current.documents.add(document);
}
for (const ref of group.sourceRefs) {
current.sourceRefs.add(ref);
}
}
return {
balances: Array.from(byCounterparty.entries())
.map(([name, item]) => ({
name,
outstandingAmount: item.outstandingAmount,
operations: item.operations,
firstPeriod: item.firstPeriod,
lastPeriod: item.lastPeriod,
category: resolvePayablesLiabilityCategory(item.categoryScores),
categoryReasons: Array.from(item.reasons).slice(0, 2),
contracts: Array.from(item.contracts).slice(0, 2),
documents: Array.from(item.documents).slice(0, 2),
sourceRefs: Array.from(item.sourceRefs).slice(0, 3)
}))
.filter((item) => item.outstandingAmount > 0.005)
.sort((left, right) => {
if (right.outstandingAmount !== left.outstandingAmount) {
return right.outstandingAmount - left.outstandingAmount;
}
if (right.operations !== left.operations) {
return right.operations - left.operations;
}
return left.name.localeCompare(right.name);
}),
mirrorGroups: mirrorGroups.sort((left, right) => {
if (right.offsetAmount !== left.offsetAmount) {
return right.offsetAmount - left.offsetAmount;
}
return left.name.localeCompare(right.name);
}),
mirroredOffsetAmount
};
}
function buildPayablesConfirmedBalanceSnapshot(rows: ComposeStageRow[], asOfDate: string): ConfirmedDebtBalanceSnapshot {
return buildConfirmedDebtBalanceSnapshot(rows, asOfDate, hasPayablesSectionPrefix, "credit");
}
function buildReceivablesConfirmedBalanceSnapshot(rows: ComposeStageRow[], asOfDate: string): ConfirmedDebtBalanceSnapshot {
return buildConfirmedDebtBalanceSnapshot(rows, asOfDate, hasReceivablesSectionPrefix, "debit");
}
function buildCounterpartyRiskAggregate(rows: ComposeStageRow[]): CounterpartyRiskAggregate[] {
const byCounterparty = new Map<string, CounterpartyRiskAggregate>();
@@ -2109,6 +2427,51 @@ function extractContractName(row: ComposeStageRow): string | null {
return null;
}
function extractSettlementBalanceAnalyticKey(row: ComposeStageRow, counterparty: string): string | null {
const counterpartyToken = normalizeSettlementComparableToken(counterparty);
const organizationToken = normalizeSettlementComparableToken(extractSettlementOrganizationName(row));
const contract = extractContractName(row);
if (contract) {
const contractToken = normalizeSettlementComparableToken(contract);
if (
contractToken &&
contractToken !== counterpartyToken &&
contractToken !== organizationToken &&
!(Boolean(organizationToken) && contractToken.includes(organizationToken)) &&
!/^организац/.test(contractToken)
) {
return contract;
}
}
for (const token of row.analytics) {
const normalized = String(token ?? "").trim();
const normalizedToken = normalizeSettlementComparableToken(normalized);
if (!normalized || !normalizedToken) {
continue;
}
if (/^(?:0|<пусто>|пустая ссылка)$/iu.test(normalized)) {
continue;
}
if (/^\d{4}-\d{2}-\d{2}/.test(normalized) || /^\d+(?:[.,]\d+)?$/.test(normalized)) {
continue;
}
if (/^\d{2}(?:\.\d{1,2})?$/.test(normalized)) {
continue;
}
if (
normalizedToken === counterpartyToken ||
normalizedToken === organizationToken ||
(Boolean(organizationToken) && normalizedToken.includes(organizationToken)) ||
/^организац/.test(normalizedToken)
) {
continue;
}
return normalized;
}
return null;
}
function normalizeEntityToken(value: string | null | undefined): string {
return String(value ?? "")
.toLowerCase()
@@ -2117,6 +2480,13 @@ function normalizeEntityToken(value: string | null | undefined): string {
.trim();
}
function normalizeSettlementComparableToken(value: string | null | undefined): string {
return normalizeEntityToken(value)
.replace(/[^\p{L}0-9]+/giu, " ")
.replace(/\s+/g, " ")
.trim();
}
function extractPayablesSourceRefs(
row: ComposeStageRow,
counterparty: string,
@@ -2172,6 +2542,58 @@ function formatPayablesEvidenceSuffix(item: PayablesConfirmedBalanceAggregate):
return parts.length > 0 ? ` | ${parts.join(" | ")}` : "";
}
function formatDebtMirrorGroupLine(item: DebtMirrorBalanceGroup): string {
const details = [
item.account ? `счет ${item.account}` : null,
item.contract ? `договор/аналитика: ${item.contract}` : null,
item.organization ? `организация: ${item.organization}` : null
].filter((part): part is string => Boolean(part));
const netText =
Math.abs(item.netAmount) <= 0.005
? "чисто: 0 ₽"
: item.netAmount > 0
? `чисто к получению: ${formatMoneyRub(item.netAmount)}`
: `чисто к оплате: ${formatMoneyRub(Math.abs(item.netAmount))}`;
return `${item.name}${details.length > 0 ? ` (${details.join(", ")})` : ""}: дебет ${formatMoneyRub(item.debitAmount)} / кредит ${formatMoneyRub(item.creditAmount)}, ${netText}.`;
}
function debtMirrorCleanScopeLabel(kind: "payables" | "receivables"): string {
return kind === "payables" ? "чистый долг к оплате" : "чистую дебиторку к получению";
}
function appendDebtMirrorCompactDisclosure(
lines: string[],
snapshot: ConfirmedDebtBalanceSnapshot,
kind: "payables" | "receivables"
): void {
if (snapshot.mirroredOffsetAmount <= 0.005) {
return;
}
lines.push(
`Отдельно сверено встречных остатков: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; они не включены в ${debtMirrorCleanScopeLabel(kind)}.`
);
const leadingMirror = snapshot.mirrorGroups[0] ?? null;
if (leadingMirror) {
lines.push(`Крупнейший встречный хвост: ${formatDebtMirrorGroupLine(leadingMirror)}`);
}
}
function appendDebtMirrorDisclosure(
lines: string[],
snapshot: ConfirmedDebtBalanceSnapshot,
kind: "payables" | "receivables"
): void {
if (snapshot.mirroredOffsetAmount <= 0.005) {
return;
}
lines.push("");
lines.push("Встречные остатки к сверке");
lines.push(
`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она исключена из ${debtMirrorCleanScopeLabel(kind)}.`
);
lines.push(...snapshot.mirrorGroups.slice(0, 3).map((item, index) => `${index + 1}. ${formatDebtMirrorGroupLine(item)}`));
}
function deriveOperationalYearWindow(
yearDocs: YearAggPoint[],
yearOps: YearAggPoint[]
@@ -3813,7 +4235,8 @@ function composeFactualReplyBody(
if (intent === "payables_confirmed_as_of_date") {
const payablesAsOfDate = resolvePayablesAsOfDate(options);
const confirmedBalances = buildPayablesConfirmedBalanceAggregate(rows, payablesAsOfDate);
const balanceSnapshot = buildPayablesConfirmedBalanceSnapshot(rows, payablesAsOfDate);
const confirmedBalances = balanceSnapshot.balances;
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
const periodTo = normalizeIsoDateOnly(options.periodTo);
@@ -3834,6 +4257,39 @@ function composeFactualReplyBody(
{ supplier_or_contractor: 0, bank_or_credit: 0, tax_or_state: 0, other: 0 }
);
if (isDirectBalanceQuestion(options.userMessage)) {
const leading = confirmedBalances[0] ?? null;
const compactLines: string[] = leading
? [
`Коротко: на ${formatDateRu(payablesAsOfDate)} мы должны ${formatMoneyRub(totalOutstandingAmount)}; крупнейшая позиция — ${leading.name} (${formatMoneyRub(leading.outstandingAmount)}).`,
"Крупнейшие позиции к оплате:"
]
: [`Коротко: на ${formatDateRu(payablesAsOfDate)} подтвержденных обязательств к оплате не найдено.`];
if (leading) {
compactLines.push(
...confirmedBalances.slice(0, 5).map((item, index) => {
const lastPeriod = item.lastPeriod ? `, последнее движение: ${item.lastPeriod}` : "";
return `${index + 1}. ${item.name}${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.${lastPeriod}).`;
})
);
if (confirmedBalances.length > 5) {
compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`);
}
}
appendDebtMirrorCompactDisclosure(compactLines, balanceSnapshot, "payables");
compactLines.push(`Основа: подтвержденный остаток по счетам 60/76, срез ${formatDateRu(payablesAsOfDate)}.`);
return {
responseType: confirmedBalances.length > 0 ? "FACTUAL_LIST" : "FACTUAL_SUMMARY",
text: joinLines(compactLines),
semantics: {
result_mode: "confirmed_balance",
evidence_strength: confirmedBalances.length > 0 ? "strong" : "medium",
balance_confirmed: true
}
};
}
const lines: string[] = [
`Коротко: подтвержденный долг к оплате на ${formatDateRu(payablesAsOfDate)}${formatMoneyRub(totalOutstandingAmount)}.`,
"Это подтвержденный срез обязательств к оплате по точному остатку."
@@ -3854,6 +4310,7 @@ function composeFactualReplyBody(
lines.push("Сводка");
lines.push(`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`);
lines.push(`- Контрагентов с подтвержденным остатком к оплате: ${formatNumberWithDots(confirmedBalances.length)}.`);
appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables");
lines.push("");
lines.push("Категории обязательств");
@@ -3891,7 +4348,8 @@ function composeFactualReplyBody(
if (intent === "receivables_confirmed_as_of_date") {
const receivablesAsOfDate = resolveReceivablesAsOfDate(options);
const confirmedBalances = buildReceivablesConfirmedBalanceAggregate(rows, receivablesAsOfDate);
const balanceSnapshot = buildReceivablesConfirmedBalanceSnapshot(rows, receivablesAsOfDate);
const confirmedBalances = balanceSnapshot.balances;
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
const periodTo = normalizeIsoDateOnly(options.periodTo);
@@ -3912,6 +4370,39 @@ function composeFactualReplyBody(
{ supplier_or_contractor: 0, bank_or_credit: 0, tax_or_state: 0, other: 0 }
);
if (isDirectBalanceQuestion(options.userMessage)) {
const leading = confirmedBalances[0] ?? null;
const compactLines: string[] = leading
? [
`Коротко: на ${formatDateRu(receivablesAsOfDate)} нам должны ${formatMoneyRub(totalOutstandingAmount)}; крупнейшая позиция — ${leading.name} (${formatMoneyRub(leading.outstandingAmount)}).`,
"Крупнейшие позиции к получению:"
]
: [`Коротко: на ${formatDateRu(receivablesAsOfDate)} подтвержденной дебиторской задолженности не найдено.`];
if (leading) {
compactLines.push(
...confirmedBalances.slice(0, 5).map((item, index) => {
const lastPeriod = item.lastPeriod ? `, последнее движение: ${item.lastPeriod}` : "";
return `${index + 1}. ${item.name}${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.${lastPeriod}).`;
})
);
if (confirmedBalances.length > 5) {
compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`);
}
}
appendDebtMirrorCompactDisclosure(compactLines, balanceSnapshot, "receivables");
compactLines.push(`Основа: подтвержденный остаток по счетам 62/76, срез ${formatDateRu(receivablesAsOfDate)}.`);
return {
responseType: confirmedBalances.length > 0 ? "FACTUAL_LIST" : "FACTUAL_SUMMARY",
text: joinLines(compactLines),
semantics: {
result_mode: "confirmed_balance",
evidence_strength: confirmedBalances.length > 0 ? "strong" : "medium",
balance_confirmed: true
}
};
}
const lines: string[] = [
`Коротко: подтвержденная дебиторская задолженность на ${formatDateRu(receivablesAsOfDate)}${formatMoneyRub(totalOutstandingAmount)}.`,
"Это подтвержденный срез дебиторской задолженности, а не эвристический shortlist."
@@ -3932,6 +4423,7 @@ function composeFactualReplyBody(
lines.push("Сводка");
lines.push(`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`);
lines.push(`- Контрагентов с подтвержденным остатком к получению: ${formatNumberWithDots(confirmedBalances.length)}.`);
appendDebtMirrorDisclosure(lines, balanceSnapshot, "receivables");
lines.push("");
lines.push("Категории дебиторской задолженности");
@@ -4062,7 +4554,8 @@ function composeFactualReplyBody(
};
if (options.requestedResultMode === "confirmed_balance") {
const confirmedBalances = buildPayablesConfirmedBalanceAggregate(rows, payablesAsOfDate);
const balanceSnapshot = buildPayablesConfirmedBalanceSnapshot(rows, payablesAsOfDate);
const confirmedBalances = balanceSnapshot.balances;
if (confirmedBalances.length > 0) {
const categoryCounts = confirmedBalances.reduce<Record<PayablesLiabilityCategory, number>>(
(acc, item) => {
@@ -4103,6 +4596,7 @@ function composeFactualReplyBody(
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток к оплате: ${formatMoneyRub(item.outstandingAmount)} | операций в срезе: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`
)
];
appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables");
return {
responseType: "FACTUAL_LIST",
text: joinLines(lines),
@@ -534,7 +534,18 @@ export function composeCounterpartyAnalyticsReply(
!/(?:\btop\b|топ|рейтинг|список|первые|покажи\s+топ|дай\s+топ|РїРѕРєР°Р\w*\s+СРѕРї|РґР°Р\s+СРѕРї)/iu.test(
normalizedQuestion
);
const effectiveLimit = asksSingleBestCounterparty ? 1 : limit;
const asksExplicitRankingList =
/(?:\btop\b|топ|рейтинг|список|первые|покажи\s+(?:топ|список)|дай\s+(?:топ|список)|show\s+(?:top|list))/iu.test(
normalizedQuestion
);
const hasSingleBestCounterpartyCue =
/(?:сам\p{L}*|больше\s+всего|наибольш|прин[её]с|определ\p{L}*|найд\p{L}*|highest|largest|most)/iu.test(
normalizedQuestion
) &&
/(?:клиент|заказчик|покупател|контрагент|customer|client|counterparty|buyer)/iu.test(normalizedQuestion);
const semanticSingleBestCounterparty =
focus === "top_by_total" && hasSingleBestCounterpartyCue && !asksExplicitRankingList;
const effectiveLimit = asksSingleBestCounterparty || semanticSingleBestCounterparty ? 1 : limit;
const byCounterparty = new Map<string, CounterpartyValuePoint>();
const byYear = new Map<number, CounterpartyYearPoint>();
@@ -220,6 +220,14 @@ function hasExplicitLooseByAnchorToken(text: string): boolean {
}
const FOLLOWUP_LOW_QUALITY_COUNTERPARTY_TOKENS = new Set([
"мы",
"нам",
"нас",
"наш",
"наша",
"наше",
"наши",
"унас",
"есть",
"же",
"что",
@@ -1445,6 +1453,12 @@ function mergeFollowupFilters(
previousOrganization ??
(followupContext.previous_anchor_type === "organization" ? previousAnchorValue : null);
const finalCounterparty = toNonEmptyString(merged.counterparty);
if (finalCounterparty && isLowQualityCounterpartyAnchor(finalCounterparty)) {
delete merged.counterparty;
if (!reasons.includes("counterparty_cleared_low_quality_followup_anchor")) {
reasons.push("counterparty_cleared_low_quality_followup_anchor");
}
}
if (shouldSuppressInventoryCounterpartyAlias(intent, finalCounterparty, finalOrganizationReference)) {
delete merged.counterparty;
if (!reasons.includes("counterparty_cleared_as_organization_scope_alias")) {
@@ -575,6 +575,14 @@ function isReferentialCounterpartyPlaceholder(
return false;
}
return new Set([
"мы",
"нам",
"нас",
"наш",
"наша",
"наше",
"наши",
"унас",
"он",
"она",
"оно",
@@ -733,6 +733,37 @@ function businessOverviewDebtDueDateAgingText(overview: BusinessOverview): strin
return `Due-date aging на ${aging.as_of_date} проверен: строк с установленным сроком оплаты ${aging.rows_with_payment_terms}, подтвержденной просрочки не найдено; не просрочено по расчету ${aging.not_yet_due_amount_human_ru}.`;
}
function businessOverviewVendorProcurementQualityText(overview: BusinessOverview): string | null {
const quality = overview.vendor_procurement_quality;
if (!quality) {
return null;
}
const period = quality.period_scope ?? "проверенное окно";
const total = quality.total_outgoing_amount_human_ru;
const top = quality.top_outgoing_counterparty;
const topName = top?.axis_value ?? "получатель не распознан";
const topShare = quality.top_outgoing_share_pct === null ? "" : `, около ${quality.top_outgoing_share_pct}%`;
const topAmount = top?.total_amount_human_ru ? ` (${top.total_amount_human_ru})` : "";
const nonFinancial = quality.top_non_financial_supplier;
const nonFinancialShare =
quality.top_non_financial_supplier_share_pct === null ? "" : `, около ${quality.top_non_financial_supplier_share_pct}%`;
const nonFinancialText = nonFinancial
? ` Крупнейший небанковский получатель: ${nonFinancial.axis_value}${nonFinancialShare}${nonFinancial.total_amount_human_ru ? ` (${nonFinancial.total_amount_human_ru})` : ""}.`
: "";
const contractText = quality.used_contracts === null
? ""
: quality.total_contracts === null
? ` Договорный профиль: используется ${quality.used_contracts} договоров.`
: ` Договорный профиль: используется ${quality.used_contracts}/${quality.total_contracts} договоров${quality.used_contract_share_pct === null ? "" : ` (${quality.used_contract_share_pct}%)`}.`;
if (quality.evidence_status === "financial_institution_leads_outgoing_cash") {
return `Проверенный procurement-concentration route за ${period}: крупнейший получатель исходящих денег ${topName}${topShare}${topAmount}, всего исходящих платежей ${total}. По названию это банк/финансовая организация, поэтому зависимость от обычного поставщика этим не подтверждается.${nonFinancialText}${contractText} Надежность поставщиков, качество поставок, назначение каждого платежа и полная структура расходов этим маршрутом не доказаны.`;
}
if (quality.evidence_status === "reviewed_procurement_concentration") {
return `Проверенный procurement-concentration route за ${period}: крупнейший поставщик/получатель исходящих платежей ${topName}${topShare}${topAmount}, всего исходящих платежей ${total}.${contractText} Это проверенный сигнал концентрации закупок/исходящих платежей, но не аудит надежности поставщика, качества поставок и полной структуры расходов.`;
}
return `Procurement-concentration route за ${period} отработал по исходящим платежам на ${total}, но надежной небанковской концентрации поставщика по найденным строкам не хватает.${contractText} Полный vendor-risk аудит не подтвержден.`;
}
function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpDiscoveryPilotExecutionContract): string {
const askedMonthlyBreakdown =
pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" ||
@@ -774,6 +805,10 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
return `Коротко: точно подтвердить резерв под неликвиды по текущим данным нельзя; ${inventoryBasis}. Можно честно говорить только о необходимости отдельной проверки склада, списаний/резервов и ликвидационной стоимости, не превращая proxy в доказанный факт резерва.`;
}
if (isVendorRiskBoundaryTurn(pilot)) {
const vendorQualityText = businessOverviewVendorProcurementQualityText(overview);
if (vendorQualityText) {
return vendorQualityText;
}
const supplierLeader = overview.top_suppliers?.[0] ?? null;
const proxyLabel = isFinancialInstitutionBucket(supplierLeader)
? "outgoing cash concentration proxy"
@@ -1051,7 +1086,11 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
claims.push("Do not present business overview cash-flow spread as profit or margin.");
claims.push("Do not present business overview yearly operating-flow breakdown as profit, financial result, or a complete annual P&L.");
claims.push("Do not present business overview trading-margin proxy as clean profit, accounting financial result, or exact cost-of-sales margin.");
claims.push("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
if (pilot.derived_business_overview?.vendor_procurement_quality) {
claims.push("Do not present reviewed procurement concentration as supplier reliability, delivery quality, payment-purpose classification, or full expense structure.");
} else {
claims.push("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
}
claims.push("Do not present business overview document/account-section activity profile as process quality, accounting correctness, or completeness of all 1C activity.");
claims.push("Do not present business overview counterparty or contract profile as CRM quality, counterparty due diligence, contract-risk audit, or legal completeness.");
claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked.");
@@ -1486,6 +1525,10 @@ function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilot
if (outgoingLeaderLine) {
lines.push(outgoingLeaderLine);
}
const vendorQualityText = businessOverviewVendorProcurementQualityText(overview);
if (vendorQualityText) {
lines.push(vendorQualityText);
}
if (overview.yearly_breakdown?.length) {
lines.push(
`Годовая раскладка операционного денежного потока построена по подтвержденным строкам 1С за ${yearCountHumanRu(overview.yearly_breakdown.length)}.`
@@ -1936,6 +1979,10 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
if (pilot.derived_business_overview?.top_suppliers?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_supplier_concentration");
}
if (pilot.derived_business_overview?.vendor_procurement_quality) {
pushReason(reasonCodes, "answer_contains_business_overview_vendor_procurement_quality");
pushReason(reasonCodes, `answer_contains_business_overview_vendor_procurement_quality_${pilot.derived_business_overview.vendor_procurement_quality.evidence_status}`);
}
if (pilot.derived_business_overview?.yearly_breakdown?.length) {
pushReason(reasonCodes, "answer_contains_business_overview_yearly_operating_breakdown");
}
@@ -188,6 +188,28 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverviewContractUsageProfil
inference_basis: "contract_usage_overview_confirmed_1c_rows";
}
export interface AssistantMcpDiscoveryDerivedBusinessOverviewVendorProcurementQuality {
period_scope: string | null;
rows_with_amount: number;
total_outgoing_amount: number;
total_outgoing_amount_human_ru: string;
top_outgoing_counterparty: AssistantMcpDiscoveryRankedValueFlowBucket | null;
top_outgoing_share_pct: number | null;
top_non_financial_supplier: AssistantMcpDiscoveryRankedValueFlowBucket | null;
top_non_financial_supplier_share_pct: number | null;
financial_institution_leads_outgoing_cash: boolean;
supplier_only_count: number | null;
mixed_role_count: number | null;
used_contracts: number | null;
total_contracts: number | null;
used_contract_share_pct: number | null;
evidence_status:
| "reviewed_procurement_concentration"
| "financial_institution_leads_outgoing_cash"
| "insufficient_supplier_concentration_basis";
inference_basis: "supplier_payout_concentration_counterparty_contract_profile_confirmed_1c_rows";
}
export type AssistantMcpDiscoveryBusinessOverviewMissingProofFamilyId =
| "accounting_profit_margin"
| "debt_due_date_aging_quality"
@@ -246,6 +268,7 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverview {
document_activity_profile: AssistantMcpDiscoveryDerivedBusinessOverviewDocumentActivityProfile | null;
counterparty_profile: AssistantMcpDiscoveryDerivedBusinessOverviewCounterpartyProfile | null;
contract_usage_profile: AssistantMcpDiscoveryDerivedBusinessOverviewContractUsageProfile | null;
vendor_procurement_quality: AssistantMcpDiscoveryDerivedBusinessOverviewVendorProcurementQuality | null;
coverage_limited_by_probe_limit: boolean;
checked_signal_count: number;
missing_signal_families: string[];
@@ -4125,6 +4148,59 @@ function deriveBusinessOverviewInventoryStalenessRiskProxy(input: {
};
}
function deriveBusinessOverviewVendorProcurementQuality(input: {
rankedOutgoing: AssistantMcpDiscoveryDerivedRankedValueFlow | null;
outgoing: AssistantMcpDiscoveryValueFlowSideSummary;
counterpartyProfile: AssistantMcpDiscoveryDerivedBusinessOverviewCounterpartyProfile | null;
contractUsageProfile: AssistantMcpDiscoveryDerivedBusinessOverviewContractUsageProfile | null;
periodScope: string | null;
}): AssistantMcpDiscoveryDerivedBusinessOverviewVendorProcurementQuality | null {
if (
!input.rankedOutgoing ||
input.rankedOutgoing.ranked_values.length <= 0 ||
input.outgoing.rows_with_amount <= 0 ||
input.outgoing.total_amount <= 0
) {
return null;
}
const topOutgoingCounterparty = input.rankedOutgoing.ranked_values[0] ?? null;
const topNonFinancialSupplier =
input.rankedOutgoing.ranked_values.find((item) => !isLikelyFinancialInstitutionCounterparty(item.axis_value)) ?? null;
const financialInstitutionLeadsOutgoingCash = topOutgoingCounterparty
? isLikelyFinancialInstitutionCounterparty(topOutgoingCounterparty.axis_value)
: false;
const evidenceStatus: AssistantMcpDiscoveryDerivedBusinessOverviewVendorProcurementQuality["evidence_status"] =
!topNonFinancialSupplier
? "insufficient_supplier_concentration_basis"
: financialInstitutionLeadsOutgoingCash
? "financial_institution_leads_outgoing_cash"
: "reviewed_procurement_concentration";
return {
period_scope: input.periodScope,
rows_with_amount: input.outgoing.rows_with_amount,
total_outgoing_amount: input.outgoing.total_amount,
total_outgoing_amount_human_ru: input.outgoing.total_amount_human_ru,
top_outgoing_counterparty: topOutgoingCounterparty,
top_outgoing_share_pct: topOutgoingCounterparty
? percentageOfTotal(topOutgoingCounterparty.total_amount, input.outgoing.total_amount)
: null,
top_non_financial_supplier: topNonFinancialSupplier,
top_non_financial_supplier_share_pct: topNonFinancialSupplier
? percentageOfTotal(topNonFinancialSupplier.total_amount, input.outgoing.total_amount)
: null,
financial_institution_leads_outgoing_cash: financialInstitutionLeadsOutgoingCash,
supplier_only_count: input.counterpartyProfile?.supplier_only_count ?? null,
mixed_role_count: input.counterpartyProfile?.mixed_role_count ?? null,
used_contracts: input.contractUsageProfile?.used_contracts ?? null,
total_contracts: input.contractUsageProfile?.total_contracts ?? null,
used_contract_share_pct: input.contractUsageProfile?.used_contract_share_pct ?? null,
evidence_status: evidenceStatus,
inference_basis: "supplier_payout_concentration_counterparty_contract_profile_confirmed_1c_rows"
};
}
function inventoryStalenessRiskBandRu(
riskBand: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy["risk_band"]
): string {
@@ -4150,6 +4226,7 @@ function buildBusinessOverviewMissingProofFamilies(input: {
inventoryPosition: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition | null;
inventoryTurnoverProxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy | null;
inventoryStalenessRiskProxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy | null;
vendorProcurementQuality: AssistantMcpDiscoveryDerivedBusinessOverviewVendorProcurementQuality | null;
hasSupplierConcentrationSignal: boolean;
}): AssistantMcpDiscoveryBusinessOverviewMissingProofFamily[] {
const missing = new Set(input.missingSignalFamilies);
@@ -4215,7 +4292,7 @@ function buildBusinessOverviewMissingProofFamilies(input: {
});
}
if (input.hasSupplierConcentrationSignal) {
if (input.hasSupplierConcentrationSignal && !input.vendorProcurementQuality) {
pushUnique({
family: "vendor_risk_procurement_quality",
current_status: "proxy_only_currently",
@@ -4313,6 +4390,13 @@ function deriveBusinessOverview(input: {
inventoryPosition,
inventoryTurnoverProxy
});
const vendorProcurementQuality = deriveBusinessOverviewVendorProcurementQuality({
rankedOutgoing,
outgoing,
counterpartyProfile,
contractUsageProfile,
periodScope: input.periodScope
});
const checkedSignalCount = [
incoming.rows_with_amount > 0,
outgoing.rows_with_amount > 0,
@@ -4329,7 +4413,8 @@ function deriveBusinessOverview(input: {
Boolean(contractUsageProfile),
Boolean(inventoryPosition),
Boolean(inventoryTurnoverProxy),
Boolean(inventoryStalenessRiskProxy)
Boolean(inventoryStalenessRiskProxy),
Boolean(vendorProcurementQuality)
].filter(Boolean).length;
if (checkedSignalCount <= 0) {
return null;
@@ -4363,6 +4448,7 @@ function deriveBusinessOverview(input: {
inventoryPosition,
inventoryTurnoverProxy,
inventoryStalenessRiskProxy,
vendorProcurementQuality,
hasSupplierConcentrationSignal: (rankedOutgoing?.ranked_values.length ?? 0) > 0
});
return {
@@ -4390,6 +4476,7 @@ function deriveBusinessOverview(input: {
document_activity_profile: documentActivityProfile,
counterparty_profile: counterpartyProfile,
contract_usage_profile: contractUsageProfile,
vendor_procurement_quality: vendorProcurementQuality,
coverage_limited_by_probe_limit:
incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
checked_signal_count: checkedSignalCount,
@@ -5986,6 +6073,10 @@ export async function executeAssistantMcpDiscoveryPilot(
if (derivedBusinessOverview.top_suppliers.length > 0) {
pushReason(reasonCodes, "pilot_derived_business_overview_top_suppliers_from_confirmed_rows");
}
if (derivedBusinessOverview.vendor_procurement_quality) {
pushReason(reasonCodes, "pilot_derived_business_overview_vendor_procurement_quality_from_confirmed_rows");
pushReason(reasonCodes, `pilot_derived_business_overview_vendor_procurement_quality_${derivedBusinessOverview.vendor_procurement_quality.evidence_status}`);
}
if (derivedBusinessOverview.yearly_breakdown.length > 0) {
pushReason(reasonCodes, "pilot_derived_business_overview_yearly_operating_breakdown_from_confirmed_rows");
}
@@ -843,7 +843,7 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
extraReasons: primitiveSelection.reasonCodes
});
}
if (dataNeedGraph?.ranking_need && !hasSubjectCandidates(dataNeedGraph)) {
if (dataNeedGraph?.ranking_need) {
pushUnique(axes, "aggregate_axis");
pushUnique(axes, "amount");
pushUnique(axes, "coverage_target");
@@ -864,7 +864,10 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
dataNeedGraph.ranking_need === "bottom_asc"
? "planner_selected_bottom_ranked_value_flow_from_data_need_graph"
: "planner_selected_top_ranked_value_flow_from_data_need_graph",
extraReasons: primitiveSelection.reasonCodes
extraReasons: [
...primitiveSelection.reasonCodes,
...(hasSubjectCandidates(dataNeedGraph) ? ["planner_kept_ranking_over_subject_scope_candidates"] : [])
]
});
}
if (openScopeTotalWithoutSubject) {
@@ -922,6 +922,58 @@ function buildCompactBusinessOverviewReply(
}
if (vendorRiskBoundary) {
const vendorProcurementQuality = toRecordObject(overview.vendor_procurement_quality);
if (vendorProcurementQuality) {
const status = toNonEmptyString(vendorProcurementQuality.evidence_status);
const totalOutgoing = moneyText(vendorProcurementQuality.total_outgoing_amount_human_ru);
const topOutgoingRecord = toRecordObject(vendorProcurementQuality.top_outgoing_counterparty);
const topOutgoingName = toNonEmptyString(topOutgoingRecord?.axis_value);
const topOutgoingAmount = moneyText(topOutgoingRecord?.total_amount_human_ru);
const topOutgoingShare =
typeof vendorProcurementQuality.top_outgoing_share_pct === "number" &&
Number.isFinite(vendorProcurementQuality.top_outgoing_share_pct)
? `${vendorProcurementQuality.top_outgoing_share_pct}%`
: null;
const nonFinancialRecord = toRecordObject(vendorProcurementQuality.top_non_financial_supplier);
const nonFinancialName = toNonEmptyString(nonFinancialRecord?.axis_value);
const nonFinancialAmount = moneyText(nonFinancialRecord?.total_amount_human_ru);
const nonFinancialShare =
typeof vendorProcurementQuality.top_non_financial_supplier_share_pct === "number" &&
Number.isFinite(vendorProcurementQuality.top_non_financial_supplier_share_pct)
? `${vendorProcurementQuality.top_non_financial_supplier_share_pct}%`
: null;
const periodScope = toNonEmptyString(vendorProcurementQuality.period_scope) ?? period;
const totalText = totalOutgoing ? `; всего исходящих платежей в проверенном срезе ${totalOutgoing}` : "";
if (status === "financial_institution_leads_outgoing_cash") {
lines.push(
`Коротко: проверенный procurement-concentration route за ${periodScope} не подтверждает зависимость от обычного поставщика: крупнейший получатель исходящих денег ${topOutgoingName ?? "не распознан"}${topOutgoingShare ? ` держит около ${topOutgoingShare}` : ""}${topOutgoingAmount ? ` (${topOutgoingAmount})` : ""}, но по названию это банк/финансовая организация${totalText}.`
);
if (nonFinancialName) {
lines.push(
`Крупнейший небанковский получатель исходящих денег: ${nonFinancialName}${nonFinancialShare ? `, около ${nonFinancialShare}` : ""}${nonFinancialAmount ? ` (${nonFinancialAmount})` : ""}. Это уже сигнал закупочной/исходящей концентрации, но не аудит надежности поставщика.`
);
}
} else if (status === "reviewed_procurement_concentration") {
lines.push(
`Коротко: проверенный procurement-concentration route за ${periodScope} нашел основную зависимость исходящего потока: ${topOutgoingName ?? nonFinancialName ?? "получатель не распознан"}${topOutgoingShare ? ` держит около ${topOutgoingShare}` : nonFinancialShare ? ` держит около ${nonFinancialShare}` : ""}${topOutgoingAmount ? ` (${topOutgoingAmount})` : nonFinancialAmount ? ` (${nonFinancialAmount})` : ""}${totalText}.`
);
} else {
lines.push(
`Коротко: procurement-concentration route за ${periodScope} отработал, но надежной небанковской концентрации поставщика по найденным исходящим платежам не хватает${totalText}.`
);
}
const contractText =
typeof vendorProcurementQuality.used_contracts === "number" && Number.isFinite(vendorProcurementQuality.used_contracts)
? typeof vendorProcurementQuality.total_contracts === "number" && Number.isFinite(vendorProcurementQuality.total_contracts)
? ` Договорный профиль: используется ${vendorProcurementQuality.used_contracts}/${vendorProcurementQuality.total_contracts} договоров${typeof vendorProcurementQuality.used_contract_share_pct === "number" && Number.isFinite(vendorProcurementQuality.used_contract_share_pct) ? ` (${vendorProcurementQuality.used_contract_share_pct}%)` : ""}.`
: ` Договорный профиль: используется ${vendorProcurementQuality.used_contracts} договоров.`
: "";
lines.push(
`Что не доказано этим маршрутом: надежность поставщика, качество поставок, договорные условия, назначение каждого платежа и полная структура всех расходов.${contractText}`
);
const reply = lines.join("\n").trim();
return reply.length > 0 && !hasInternalMechanics(reply) ? reply : null;
}
const supplierBasis = topSupplier
? topSupplierLooksFinancial
? `крупнейший получатель исходящих денег: ${topSupplier}; по названию это банк/финансовая организация, поэтому это не доказанная зависимость от обычного поставщика${nonFinancialSupplier ? `; крупнейший небанковский получатель исходящих денег: ${nonFinancialSupplier}` : ""}`
@@ -255,6 +255,26 @@ function isMetadataDiscoveryTurn(
);
}
function isExplicitMetadataDiscoveryTurn(
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): boolean {
const turnInput = toRecordObject(entryPoint?.turn_input);
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
const graph = readDiscoveryDataNeedGraph(entryPoint);
const reasonCodes = [
...(Array.isArray(entryPoint?.reason_codes) ? entryPoint.reason_codes : []),
...(Array.isArray(turnInput?.reason_codes) ? turnInput.reason_codes : [])
];
const decompositionCandidates = Array.isArray(graph?.decomposition_candidates) ? graph.decomposition_candidates : [];
return Boolean(
toNonEmptyString(turnMeaning?.asked_domain_family) === "metadata" ||
toNonEmptyString(turnMeaning?.unsupported_but_understood_family) === "1c_metadata_surface" ||
toNonEmptyString(graph?.business_fact_family) === "schema_surface" ||
decompositionCandidates.some((candidate) => toNonEmptyString(candidate) === "inspect_metadata_surface") ||
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 ?? "")
@@ -274,6 +294,9 @@ function hasMetadataDiscoveryPriority(
if (!isMetadataDiscoveryTurn(entryPoint)) {
return false;
}
if (!isExplicitMetadataDiscoveryTurn(entryPoint)) {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
return !isInventoryExactAddressIntent(detectedIntent);
}
@@ -363,6 +386,75 @@ function readStringArray(value: unknown): string[] {
: [];
}
function hasConfirmedAddressExecution(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
const truthMode = toNonEmptyString(input.addressRuntimeMeta?.truth_mode);
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
const bindingStatus = toNonEmptyString(input.addressRuntimeMeta?.capability_binding_status);
const bindingViolations = readStringArray(input.addressRuntimeMeta?.capability_binding_violations);
return Boolean(
mcpCallStatus === "matched_non_empty" &&
truthMode === "confirmed" &&
selectedRecipe?.startsWith("address_") &&
(bindingStatus === "bound" || bindingStatus === "bound_with_limits") &&
bindingViolations.length === 0
);
}
function hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): boolean {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
if (!isMetadataDiscoveryTurn(entryPoint) || isExplicitMetadataDiscoveryTurn(entryPoint)) {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
return Boolean(
detectedIntent &&
hasConfirmedAddressExecution(input) &&
isDetectedIntentAlignedWithTurnMeaning(detectedIntent, readDiscoveryTurnMeaning(entryPoint))
);
}
function hasBusinessOverviewDirectMoneyClarification(
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): boolean {
const graph = readDiscoveryDataNeedGraph(entryPoint);
const businessFactFamily = toNonEmptyString(graph?.business_fact_family);
const reasonCodes = readStringArray(graph?.reason_codes);
const clarificationGaps = readStringArray(graph?.clarification_gaps);
return Boolean(
businessFactFamily === "business_overview" &&
reasonCodes.includes("data_need_graph_business_overview_direct_money_answer") &&
(toNonEmptyString(graph?.ranking_need) || reasonCodes.includes("data_need_graph_ranking_top_desc")) &&
clarificationGaps.includes("organization")
);
}
function hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): boolean {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
return Boolean(
detectedIntent === "customer_revenue_and_payments" &&
hasConfirmedAddressExecution(input) &&
hasBusinessOverviewDirectMoneyClarification(entryPoint)
);
}
function hasValueFlowActionConflictWithDiscoveryTurnMeaning(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
@@ -443,18 +535,7 @@ function hasExactMatchedFactualAddressReply(
return false;
}
}
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
const truthMode = toNonEmptyString(input.addressRuntimeMeta?.truth_mode);
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
const bindingStatus = toNonEmptyString(input.addressRuntimeMeta?.capability_binding_status);
const bindingViolations = readStringArray(input.addressRuntimeMeta?.capability_binding_violations);
return Boolean(
mcpCallStatus === "matched_non_empty" &&
truthMode === "confirmed" &&
selectedRecipe?.startsWith("address_") &&
(bindingStatus === "bound" || bindingStatus === "bound_with_limits") &&
bindingViolations.length === 0
);
return hasConfirmedAddressExecution(input);
}
function hasOpenScopeValueFlowDiscoveryPriority(
@@ -682,6 +763,14 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
const exactMatchedFactualAddressReply = hasExactMatchedFactualAddressReply(input, entryPoint);
const runtimeAdjustedExactReply = hasRuntimeAdjustedExactReply(input, entryPoint);
const runtimeMatchedExactReply = hasRuntimeMatchedExactReply(input, entryPoint);
const staleMetadataDiscoveryFallbackAgainstExactAddressReply = hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(
input,
entryPoint
);
const exactValueFlowReplyForBusinessOverviewDirectMoneyNeed = hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(
input,
entryPoint
);
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(
@@ -750,6 +839,18 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
"mcp_discovery_response_policy_keep_runtime_matched_exact_reply_over_stale_discovery_turn_meaning"
);
}
if (staleMetadataDiscoveryFallbackAgainstExactAddressReply) {
pushReason(
reasonCodes,
"mcp_discovery_response_policy_keep_exact_address_reply_over_stale_metadata_discovery"
);
}
if (exactValueFlowReplyForBusinessOverviewDirectMoneyNeed) {
pushReason(
reasonCodes,
"mcp_discovery_response_policy_keep_exact_value_flow_reply_over_business_overview_direct_money_clarification"
);
}
if (deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") {
pushReason(
reasonCodes,
@@ -779,6 +880,8 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
!exactMatchedFactualAddressReply &&
!runtimeAdjustedExactReply &&
!runtimeMatchedExactReply &&
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
candidate.eligible_for_future_hot_runtime &&
@@ -96,6 +96,27 @@ describe("address follow-up temporal regressions", () => {
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("receivables_confirmed_as_of_date");
expect(result?.filters.extracted_filters.as_of_date).toBe("2026-04-16");
expect(result?.filters.extracted_filters.counterparty).toBeUndefined();
});
it("does not carry self-scope pronouns as counterparty anchors for short debt mirror follow-up", () => {
const result = runAddressDecomposeStage("а нам?", {
previous_intent: "payables_confirmed_as_of_date",
target_intent: "receivables_confirmed_as_of_date",
previous_filters: {
as_of_date: "2026-05-12",
organization: "ООО Альтернатива Плюс",
counterparty: "нас"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "нас"
});
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("receivables_confirmed_as_of_date");
expect(result?.filters.extracted_filters.as_of_date).toBe("2026-05-12");
expect(result?.filters.extracted_filters.organization).toBe("ООО Альтернатива Плюс");
expect(result?.filters.extracted_filters.counterparty).toBeUndefined();
});
it("keeps same-date inventory pivot anchored to the previous VAT date", () => {
@@ -56,11 +56,11 @@ describe("addressIntentResolver regression bridges", () => {
expect(result.reasons).toContain("unicode_customer_concentration_bridge_signal_detected");
});
it("defers top-year company revenue wording to business overview discovery", () => {
it("routes top-year company revenue wording to exact value-flow ranking", () => {
const result = resolveAddressIntent("какой у нас самый доходный год");
expect(result.intent).toBe("unknown");
expect(result.reasons).toContain("unicode_business_overview_earnings_deferred_to_discovery");
expect(result.intent).toBe("customer_revenue_and_payments");
expect(result.reasons).toContain("unicode_customer_revenue_ranking_bridge_signal_detected");
});
it("defers paired receivables and payables wording to business overview instead of one debt side", () => {
@@ -2608,12 +2608,12 @@ describe("address intent resolver expansion (M2.3a)", () => {
expect(result.intent).toBe("customer_revenue_and_payments");
});
it("defers organization-level yearly profitability wording to business overview discovery", () => {
it("routes organization-level top-year revenue wording to exact value-flow ranking", () => {
const result = resolveAddressIntent(
"\u043a\u0430\u043a\u0438\u0435 \u0441\u0430\u043c\u044b\u0435 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0435 \u0433\u043e\u0434\u0430 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b"
);
expect(result.intent).toBe("unknown");
expect(result.reasons).toContain("unicode_business_overview_earnings_deferred_to_discovery");
expect(result.intent).toBe("customer_revenue_and_payments");
expect(result.reasons).toContain("unicode_customer_revenue_ranking_bridge_signal_detected");
});
it("defers organization-level profit and margin wording to business overview discovery", () => {
@@ -3496,12 +3496,10 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
expect(reply.toLowerCase()).toContain("shortlist");
} else {
expect(result?.debug.balance_confirmed).toBe(true);
expect(reply).toContain("Коротко: подтвержденный долг к оплате");
expect(reply).toContain("Это подтвержденный срез обязательств к оплате");
expect(reply).toMatch(/\n\nЧто учтено/u);
expect(reply).toMatch(/\n\nСводка/u);
expect(reply).toMatch(/\n\nКатегории обязательств/u);
expect(reply).toMatch(/\n\nПодтвержденные позиции к оплате/u);
expect(reply).toContain("Коротко: на 31.05.2020 мы должны");
expect(reply).toContain("Крупнейшие позиции к оплате");
expect(reply).toContain("Основа: подтвержденный остаток по счетам 60/76");
expect(reply.length).toBeLessThan(1800);
expect(reply).not.toContain("Блок 1");
expect(reply).not.toContain("эвристический");
}
@@ -3793,17 +3791,16 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
expect(["FACTUAL_LIST", "LIMITED_WITH_REASON", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
});
it("defers yearly profitability wording out of the exact customer value aggregate recipe", async () => {
it("routes organization-level yearly revenue ranking wording into the exact customer value aggregate recipe", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
"\u043a\u0430\u043a\u0438\u0435 \u0441\u0430\u043c\u044b\u0435 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0435 \u0433\u043e\u0434\u0430 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b"
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("unknown");
expect(result?.debug.selected_recipe).toBeNull();
expect(result?.debug.limited_reason_category).toBe("unsupported");
expect(result?.debug.mcp_call_status).toBe("skipped");
expect(result?.response_type).toBe("LIMITED_WITH_REASON");
expect(result?.debug.detected_intent).toBe("customer_revenue_and_payments");
expect(result?.debug.selected_recipe).toBe("address_customer_revenue_and_payments_v1");
expect(result?.debug.mcp_call_status).not.toBe("skipped");
expect(["FACTUAL_LIST", "LIMITED_WITH_REASON", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
});
it("routes typo highest-check wording into customer value aggregate recipe", async () => {
@@ -53,6 +53,7 @@ describe("receivables confirmed as-of route", () => {
const plan = buildAddressRecipePlan(selected.selected_recipe!, filters);
expect(plan.query).toContain("РегистрБухгалтерии.Хозрасчетный.Остатки");
expect(plan.query).toContain("СуммаРазвернутыйОстатокДт");
expect(plan.query).toContain("СуммаРазвернутыйОстатокКт");
expect(plan.query).toContain("Остатки.Счет");
expect(plan.query).toContain("62");
expect(plan.query).toContain("76");
@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import { composeCounterpartyAnalyticsReply } from "../src/services/address_runtime/counterpartyAnalyticsReplyBuilders";
import { composeFactualReply } from "../src/services/address_runtime/composeStage";
import { composeInventoryReply } from "../src/services/address_runtime/inventoryReplyBuilders";
describe("address reply builders regressions", () => {
@@ -51,6 +52,88 @@ describe("address reply builders regressions", () => {
expect(result?.text.split("\n")[0]).toContain("Чапурнов");
});
it("keeps canonical single-best customer wording compact instead of returning the default top list", () => {
const result = composeCounterpartyAnalyticsReply(
"customer_revenue_and_payments",
[
{
counterparty: "Крупный клиент",
amount: 250000,
period: "2020-03-31",
registrator: "Поступление 1"
} as any,
{
counterparty: "Малый клиент",
amount: 100000,
period: "2020-04-30",
registrator: "Поступление 2"
} as any
],
{
userMessage: "определить самого доходного клиента за весь период"
},
{
formatPercent: () => null,
formatDateRu: (value: string) => value,
formatMoneyRub: (value: number) => `${value} руб.`,
extractYearFromIso: (value: string | null) => (value ? Number(value.slice(0, 4)) : null),
detectCounterpartyProfileFocus: () => "full_profile",
detectCounterpartyLifecycleFocus: () => "active_customers_all_time",
hasCounterpartyLifecycleLongevityQuestion: () => false,
hasCounterpartyActivityAgeQuestion: () => false,
detectRankingLimit: () => 20,
detectValueRankingFocus: () => "top_by_total",
detectContractValueFocus: () => "top_by_turnover",
detectMinOpsForAvgCheck: () => 1,
extractRequestedYearFromQuestion: () => null,
extractCounterpartyName: (row: any) => row.counterparty,
extractContractName: () => null,
counterpartyLookupMatches: () => false,
toUtcDayTimestamp: () => null,
formatAgeYearsMonthsDays: () => "0 дней",
normalizeQuestionText: (value: string | null | undefined) => String(value ?? "").toLowerCase()
}
);
expect(result?.text.split("\n")[0]).toContain("Крупный клиент");
expect(result?.text).not.toContain("2. Малый клиент");
});
it("keeps direct receivables snapshot answers compact", () => {
const result = composeFactualReply(
"receivables_confirmed_as_of_date",
[
{
period: "2020-05-10",
registrator: "Поступление 1",
account_dt: "62.01",
account_kt: "90.01",
amount: 100000,
analytics: ["Клиент А"],
counterparty: "Клиент А"
},
{
period: "2020-05-11",
registrator: "Поступление 2",
account_dt: "62.01",
account_kt: "90.01",
amount: 50000,
analytics: ["Клиент Б"],
counterparty: "Клиент Б"
}
],
{
userMessage: "кто нам должен денег на май 2020",
asOfDate: "2020-05-31"
}
);
expect(result.text.split("\n")[0]).toContain("Коротко:");
expect(result.text.split("\n")[0]).toContain("Клиент А");
expect(result.text.length).toBeLessThan(1200);
expect(result.text).toContain("Крупнейшие позиции к получению");
});
it("does not overclaim a comparative top customer ranking when only one candidate is present", () => {
const result = composeCounterpartyAnalyticsReply(
"customer_revenue_and_payments",
@@ -190,4 +273,174 @@ describe("address reply builders regressions", () => {
expect(firstLine).toContain("К самым старым закупкам");
expect(firstLine).not.toContain("2026-04-18");
});
it("excludes mirrored 76 settlements from clean payables", () => {
const result = composeFactualReply(
"payables_confirmed_as_of_date",
[
{
period: "2026-05-12",
registrator: "",
account_dt: "51",
account_kt: "76.09",
amount: 3677454.14,
analytics: ["Комитет государственных услуг г. Москвы", "ООО \\Альтернатива Плюс\\", "Финансовое обеспечение заявки"],
counterparty: "Комитет государственных услуг г. Москвы",
organization: "ООО \\Альтернатива Плюс\\"
},
{
period: "2026-05-12",
registrator: "Списание с расчетного счета 1",
account_dt: "76.09",
account_kt: "51",
amount: 1000000,
analytics: ["Комитет государственных услуг г. Москвы", "ООО \\Альтернатива Плюс\\", "Финансовое обеспечение заявки"],
counterparty: "Комитет государственных услуг г. Москвы",
organization: "ООО \\Альтернатива Плюс\\"
},
{
period: "2026-05-12",
registrator: "Списание с расчетного счета 2",
account_dt: "76.09",
account_kt: "51",
amount: 2677454.14,
analytics: ["Комитет государственных услуг г. Москвы", "ООО \\Альтернатива Плюс\\", "Финансовое обеспечение заявки"],
counterparty: "Комитет государственных услуг г. Москвы",
organization: "ООО \\Альтернатива Плюс\\"
},
{
period: "2026-05-12",
registrator: "Поступление товаров 1",
account_dt: "41.01",
account_kt: "60.01",
amount: 7271.2,
analytics: ["Авант мебель"],
counterparty: "Авант мебель",
organization: "ООО Альтернатива Плюс"
}
],
{
userMessage: "мы должны кому-то денег на сегодня?",
asOfDate: "2026-05-12"
}
);
const firstLine = result.text.split("\n")[0] ?? "";
expect(firstLine).toContain("7.271,20");
expect(firstLine).toContain("Авант мебель");
expect(firstLine).not.toContain("3.677.454,14");
expect(result.text).toContain("встречных остатков");
expect(result.text).toContain("Комитет государственных услуг г. Москвы");
expect(result.text).toContain("Финансовое обеспечение заявки");
expect(result.text).not.toContain("договор/аналитика: ООО \\Альтернатива Плюс\\");
});
it("excludes mirrored 76 settlements from clean receivables", () => {
const result = composeFactualReply(
"receivables_confirmed_as_of_date",
[
{
period: "2026-05-12",
registrator: "Реализация 1",
account_dt: "62.01",
account_kt: "90.01",
amount: 9612904.9,
analytics: ["Департамент капитального ремонта города Москвы."],
counterparty: "Департамент капитального ремонта города Москвы.",
organization: "ООО Альтернатива Плюс"
},
{
period: "2026-05-12",
registrator: "",
account_dt: "51",
account_kt: "76.09",
amount: 3677454.14,
analytics: ["Комитет государственных услуг г. Москвы", "Финансовое обеспечение заявки"],
counterparty: "Комитет государственных услуг г. Москвы",
organization: "ООО Альтернатива Плюс"
},
{
period: "2026-05-12",
registrator: "Списание с расчетного счета 1",
account_dt: "76.09",
account_kt: "51",
amount: 3677454.14,
analytics: ["Комитет государственных услуг г. Москвы", "Финансовое обеспечение заявки"],
counterparty: "Комитет государственных услуг г. Москвы",
organization: "ООО Альтернатива Плюс"
}
],
{
userMessage: "а нам кто должен на сегодня?",
asOfDate: "2026-05-12"
}
);
const firstLine = result.text.split("\n")[0] ?? "";
expect(firstLine).toContain("9.612.904,90");
expect(firstLine).toContain("Департамент капитального ремонта города Москвы.");
expect(firstLine).not.toContain("13.290.359,04");
expect(result.text).toContain("встречных остатков");
expect(result.text).toContain("Комитет государственных услуг г. Москвы");
});
it("keeps bank-named balance counterparties as counterparties, not settlement analytics", () => {
const result = composeFactualReply(
"receivables_confirmed_as_of_date",
[
{
period: "2017-05-31",
registrator: "Остатки на дату",
account_dt: "76.09",
account_kt: null,
amount: 39079.12,
analytics: ["Сбербанк-АСТ, ЗАО", "Финансовое обеспечение заявки"],
organization: "ООО \\Альтернатива Плюс\\"
}
],
{
userMessage: "кто нам должен денег на май 2017?",
asOfDate: "2017-05-31"
}
);
const firstLine = result.text.split("\n")[0] ?? "";
expect(firstLine).toContain("Сбербанк-АСТ, ЗАО");
expect(firstLine).not.toContain("Финансовое обеспечение заявки");
});
it("keeps different 76 contracts separated instead of netting by counterparty only", () => {
const result = composeFactualReply(
"payables_confirmed_as_of_date",
[
{
period: "2026-05-12",
registrator: "",
account_dt: "51",
account_kt: "76.09",
amount: 1000,
analytics: ["Комитет государственных услуг г. Москвы", "Договор А"],
counterparty: "Комитет государственных услуг г. Москвы",
organization: "ООО Альтернатива Плюс"
},
{
period: "2026-05-12",
registrator: "Списание с расчетного счета 1",
account_dt: "76.09",
account_kt: "51",
amount: 1000,
analytics: ["Комитет государственных услуг г. Москвы", "Договор Б"],
counterparty: "Комитет государственных услуг г. Москвы",
organization: "ООО Альтернатива Плюс"
}
],
{
userMessage: "мы должны кому-то денег на сегодня?",
asOfDate: "2026-05-12"
}
);
expect(result.text.split("\n")[0]).toContain("1.000,00");
expect(result.text).not.toContain("встречных остатков");
});
});
@@ -362,12 +362,14 @@ describe("assistant MCP discovery answer adapter", () => {
expect(draft.next_step_line).toContain("прибыль/маржу");
expect(draft.must_not_claim).toContain("Do not present business overview cash-flow spread as profit or margin.");
expect(draft.must_not_claim).toContain("Do not present business overview yearly operating-flow breakdown as profit, financial result, or a complete annual P&L.");
expect(draft.must_not_claim).toContain("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
expect(draft.must_not_claim).toContain("Do not present reviewed procurement concentration as supplier reliability, delivery quality, payment-purpose classification, or full expense structure.");
expect(draft.must_not_claim).toContain("Do not present business overview document/account-section activity profile as process quality, accounting correctness, or completeness of all 1C activity.");
expect(draft.must_not_claim).toContain("Do not present business overview counterparty or contract profile as CRM quality, counterparty due diligence, contract-risk audit, or legal completeness.");
expect(draft.must_not_claim).toContain("Do not present business overview missing proof families as checked, executed, or confirmed routes.");
expect(draft.reason_codes).toContain("answer_contains_business_overview");
expect(draft.reason_codes).toContain("answer_contains_business_overview_supplier_concentration");
expect(draft.reason_codes).toContain("answer_contains_business_overview_vendor_procurement_quality");
expect(draft.reason_codes).toContain("answer_contains_business_overview_vendor_procurement_quality_reviewed_procurement_concentration");
expect(draft.reason_codes).toContain("answer_contains_business_overview_yearly_operating_breakdown");
expect(draft.reason_codes).toContain("answer_contains_business_overview_document_activity_profile");
expect(draft.reason_codes).toContain("answer_contains_business_overview_counterparty_profile");
@@ -258,20 +258,36 @@ describe("assistant MCP discovery pilot executor", () => {
unused_contracts: 372,
used_contract_share_pct: 28.46
});
expect(result.derived_business_overview?.vendor_procurement_quality).toMatchObject({
rows_with_amount: 2,
total_outgoing_amount: 200000,
top_outgoing_share_pct: 75,
top_non_financial_supplier_share_pct: 75,
financial_institution_leads_outgoing_cash: false,
supplier_only_count: 71,
mixed_role_count: 23,
used_contracts: 148,
total_contracts: 520,
used_contract_share_pct: 28.46,
evidence_status: "reviewed_procurement_concentration",
inference_basis: "supplier_payout_concentration_counterparty_contract_profile_confirmed_1c_rows"
});
expect(result.derived_business_overview?.vendor_procurement_quality?.top_outgoing_counterparty).toMatchObject({
axis_value: "Поставщик А",
total_amount: 150000
});
expect(result.derived_business_overview?.missing_proof_families).toEqual(
expect.arrayContaining([
expect.objectContaining({
family: "accounting_profit_margin",
current_status: "reviewed_route_not_wired",
current_supported_evidence: null
}),
expect.objectContaining({
family: "vendor_risk_procurement_quality",
current_status: "proxy_only_currently",
current_supported_evidence: "supplier_concentration_proxy_from_confirmed_outgoing_payment_rows"
})
])
);
expect(result.derived_business_overview?.missing_proof_families.map((item) => item.family)).not.toContain(
"vendor_risk_procurement_quality"
);
expect(result.evidence.confirmed_facts.join("\n")).toContain("В 1С подтверждены входящие поступления");
expect(result.evidence.confirmed_facts.join("\n")).toContain("Самый крупный подтвержденный поставщик");
expect(result.evidence.confirmed_facts.join("\n")).toContain("Годовая раскладка операционного денежного потока");
@@ -289,6 +305,8 @@ describe("assistant MCP discovery pilot executor", () => {
expect(result.reason_codes).toContain("pilot_derived_business_overview_document_activity_profile_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_derived_business_overview_counterparty_profile_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_derived_business_overview_contract_usage_profile_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_derived_business_overview_vendor_procurement_quality_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_derived_business_overview_vendor_procurement_quality_reviewed_procurement_concentration");
expect(result.reason_codes).toContain("pilot_business_overview_missing_proof_families_recorded");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(6);
});
@@ -904,6 +904,40 @@ describe("assistant MCP discovery planner", () => {
expect(result.selected_chain_id).not.toBe("entity_resolution");
});
it("keeps ranked value-flow when subject candidates only scope the ranking question", () => {
const result = planAssistantMcpDiscovery({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: ["Альтернатива Плюс"],
business_fact_family: "value_flow",
action_family: "turnover",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: "top_desc",
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_date_scope: "all_time",
explicit_organization_scope: "Альтернатива Плюс"
}
});
expect(result.planner_status).toBe("ready_for_execution");
expect(result.selected_chain_id).toBe("value_flow_ranking");
expect(result.catalog_chain_template_matches[0]).toBe("value_flow_ranking");
expect(result.catalog_chain_template_alignment.selected_chain_matches_top).toBe(true);
expect(result.reason_codes).toContain("planner_selected_top_ranked_value_flow_from_data_need_graph");
expect(result.reason_codes).toContain("planner_kept_ranking_over_subject_scope_candidates");
});
it("keeps ranked value-flow ready for execution once checked period and organization are known", () => {
const result = planAssistantMcpDiscovery({
dataNeedGraph: {
@@ -215,6 +215,95 @@ describe("assistant MCP discovery response candidate", () => {
expect(candidate.reply_text).not.toContain("операционное нетто");
});
it("uses reviewed procurement-concentration evidence for vendor-risk boundary answers", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
turn_input: {
adapter_status: "ready",
turn_meaning_ref: {
asked_domain_family: "business_overview",
asked_action_family: "vendor_risk_procurement_boundary",
unsupported_but_understood_family: "vendor_risk_procurement_boundary"
},
data_need_graph: {
business_fact_family: "business_overview",
ranking_need: null,
reason_codes: ["data_need_graph_family_business_overview"]
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
pilot: {
pilot_scope: "business_overview_route_template_v1",
derived_business_overview: {
period_scope: "2020",
outgoing_supplier_payout: {
total_amount_human_ru: "1 000 000 руб."
},
top_suppliers: [
{
axis_value: "СБЕРБАНК, ПАО",
total_amount_human_ru: "700 000 руб."
}
],
vendor_procurement_quality: {
period_scope: "2020",
rows_with_amount: 2,
total_outgoing_amount: 1000000,
total_outgoing_amount_human_ru: "1 000 000 руб.",
top_outgoing_counterparty: {
axis_value: "СБЕРБАНК, ПАО",
total_amount: 700000,
total_amount_human_ru: "700 000 руб.",
rows_with_amount: 1,
share_pct: 70,
is_likely_financial_institution: true
},
top_outgoing_share_pct: 70,
top_non_financial_supplier: {
axis_value: "Поставщик А",
total_amount: 300000,
total_amount_human_ru: "300 000 руб.",
rows_with_amount: 1,
share_pct: 30,
is_likely_financial_institution: false
},
top_non_financial_supplier_share_pct: 30,
financial_institution_leads_outgoing_cash: true,
supplier_only_count: null,
mixed_role_count: null,
used_contracts: null,
total_contracts: null,
used_contract_share_pct: null,
evidence_status: "financial_institution_leads_outgoing_cash",
inference_basis: "supplier_payout_concentration_counterparty_contract_profile_confirmed_1c_rows"
}
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Vendor route.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("procurement-concentration route");
expect(candidate.reply_text).toContain("банк/финансовая организация");
expect(candidate.reply_text).toContain("Поставщик А");
expect(candidate.reply_text).toContain("надежность поставщика");
expect(candidate.reply_text).not.toContain("outgoing cash concentration proxy");
expect(candidate.reply_text).not.toContain("business_overview_route_template_v1");
});
it("uses a compact direct answer for business-overview top year questions", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
@@ -367,6 +367,83 @@ describe("assistant MCP discovery response policy", () => {
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
});
it("keeps exact receivables replies when metadata inspection is only a stale fallback candidate", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply:
"Коротко: подтвержденная дебиторская задолженность на 31.05.2017 — 1 234 567,89 руб.",
currentReplySource: "address_query_runtime_v1",
currentReplyType: "factual",
addressRuntimeMeta: {
detected_intent: "receivables_confirmed_as_of_date",
selected_recipe: "address_receivables_confirmed_as_of_date_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: "confirmed_receivables_as_of_date"
},
assistant_mcp_discovery_entry_point_v1: entryPoint({
turn_input: {
adapter_status: "ready",
should_run_discovery: true,
data_need_graph: {
business_fact_family: null,
action_family: "confirmed_snapshot",
clarification_gaps: ["subject"],
reason_codes: [
"data_need_graph_built",
"data_need_graph_family_unknown",
"data_need_graph_has_clarification_gaps"
]
},
turn_meaning_ref: {
asked_domain_family: "receivables",
asked_action_family: "confirmed_snapshot",
explicit_date_scope: "2017-05-31",
unsupported_but_understood_family: "profit_margin_boundary",
stale_replay_forbidden: true
},
reason_codes: [
"mcp_discovery_seeded_from_followup_context",
"mcp_discovery_resumed_from_saved_loop_state",
"mcp_discovery_data_need_graph_built"
]
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
pilot: {
pilot_scope: "metadata_inspection_v1"
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Metadata surface confirmed.",
confirmed_lines: ["Available metadata object sets: Document, AccumulationRegister"],
inference_lines: [],
unknown_lines: ["Detailed metadata fields were not returned by this probe."],
limitation_lines: [],
next_step_line: null
}
}
})
}
});
expect(result.applied).toBe(false);
expect(result.decision).toBe("keep_current_reply");
expect(result.reply_text).toContain("дебиторская задолженность");
expect(result.reason_codes).toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
expect(result.reason_codes).toContain(
"mcp_discovery_response_policy_keep_exact_address_reply_over_stale_metadata_discovery"
);
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_metadata_candidate_priority");
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_candidate_applied");
});
it("keeps aligned factual address lane answers when the exact lane already matched the same semantic intent", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply: "ИП Калинин Н.М. | сумма: 216600 | операций: 2",
@@ -582,6 +659,71 @@ describe("assistant MCP discovery response policy", () => {
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_full_confirmed_factual_address_reply");
});
it("keeps exact top-year value-flow replies over business-overview direct-money clarification", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply:
"Самый доходный год по подтвержденным поступлениям: 2020 (15 744 052,48 руб. по 20 операциям). Это денежный поток, а не чистая прибыль.",
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: [],
assistant_mcp_discovery_entry_point_v1: entryPoint({
turn_input: {
adapter_status: "ready",
should_run_discovery: true,
data_need_graph: {
business_fact_family: "business_overview",
action_family: "profit_margin_boundary",
ranking_need: "top_desc",
clarification_gaps: ["organization"],
reason_codes: [
"data_need_graph_built",
"data_need_graph_family_business_overview",
"data_need_graph_ranking_top_desc",
"data_need_graph_business_overview_direct_money_answer",
"data_need_graph_open_scope_total_needs_organization"
]
},
turn_meaning_ref: {
asked_domain_family: "business_overview",
asked_action_family: "profit_margin_boundary",
unsupported_but_understood_family: "profit_margin_boundary",
stale_replay_forbidden: true
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: false,
requires_user_clarification: true,
answer_draft: {
answer_mode: "needs_clarification",
headline: "Нужно уточнить организацию.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: ["Без организации бизнес-обзор не запускаю."],
limitation_lines: [],
next_step_line: "Уточните организацию."
}
}
})
}
});
expect(result.applied).toBe(false);
expect(result.decision).toBe("keep_current_reply");
expect(result.reply_text).toContain("Самый доходный год");
expect(result.reason_codes).toContain(
"mcp_discovery_response_policy_keep_exact_value_flow_reply_over_business_overview_direct_money_clarification"
);
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_candidate_applied");
});
it("overrides an exact ranking-shaped address reply when bounded open-scope ranking already has organization and period", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply:
@@ -757,6 +757,107 @@ describe("assistant MCP discovery runtime bridge", () => {
expect(result.reason_codes).toContain("answer_contains_business_overview_debt_due_date_aging_no_payment_terms_configured");
});
it("promotes vendor-risk boundary through reviewed procurement concentration evidence", async () => {
const deps = buildSequentialDeps([
{ rows: [] },
{
rows: [
{ Period: "2020-01-20T00:00:00", Amount: 700000, Counterparty: "СБЕРБАНК, ПАО" },
{ Period: "2020-02-20T00:00:00", Amount: 300000, Counterparty: "Поставщик А" }
]
},
{ rows: [] },
{ rows: [] },
{
rows: [
{ Period: "2020-01-01T00:00:00", Registrator: "CP_TOTAL", Amount: 12 },
{ Period: "2020-01-01T00:00:00", Registrator: "CP_SUPPLIER_ACTIVE", Amount: 5 },
{ Period: "2020-01-01T00:00:00", Registrator: "CP_MIXED_ACTIVE", Amount: 2 },
{ Period: "2020-01-01T00:00:00", Registrator: "CP_ACTIVE_UNION", Amount: 7 }
]
},
{
rows: [
{ Period: "2020-01-01T00:00:00", Registrator: "CT_TOTAL", Amount: 11 },
{ Period: "2020-01-01T00:00:00", Registrator: "CT_USED", Amount: 4 }
]
}
]);
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "business_overview",
action_family: "vendor_risk_procurement_boundary",
aggregation_need: null,
time_scope_need: "all_time_scope",
comparison_need: null,
ranking_need: null,
proof_expectation: "vendor_risk_procurement_quality",
clarification_gaps: [],
decomposition_candidates: [
"collect_scoped_movements",
"aggregate_checked_amounts",
"aggregate_ranked_axis_values",
"fetch_supporting_documents",
"probe_coverage",
"explain_evidence_basis"
],
forbidden_overclaim_flags: [
"no_raw_model_claims",
"no_unchecked_vendor_reliability_claim"
],
reason_codes: ["data_need_graph_built", "data_need_graph_family_business_overview"]
},
turnMeaning: {
asked_domain_family: "business_overview",
asked_action_family: "vendor_risk_procurement_boundary",
explicit_organization_scope: "OOO Alternative Plus"
},
deps
});
const overview = result.pilot.derived_business_overview;
const missingFamilies = overview?.missing_proof_families.map((item) => item.family) ?? [];
const userFacing = [
result.answer_draft.headline,
...result.answer_draft.confirmed_lines,
...result.answer_draft.inference_lines,
...result.answer_draft.unknown_lines,
...result.answer_draft.limitation_lines,
result.answer_draft.next_step_line ?? ""
].join("\n");
expect(result.bridge_status).toBe("answer_draft_ready");
expect(result.business_fact_answer_allowed).toBe(true);
expect(result.route_candidate).toMatchObject({
candidate_status: "ready_for_reviewed_execution",
selected_chain_id: "business_overview",
business_fact_family: "business_overview",
action_family: "vendor_risk_procurement_boundary",
executable_now: true
});
expect(overview?.vendor_procurement_quality).toMatchObject({
total_outgoing_amount: 1000000,
top_outgoing_share_pct: 70,
top_non_financial_supplier_share_pct: 30,
financial_institution_leads_outgoing_cash: true,
supplier_only_count: 3,
mixed_role_count: 2,
used_contracts: 4,
total_contracts: 11,
evidence_status: "financial_institution_leads_outgoing_cash"
});
expect(missingFamilies).not.toContain("vendor_risk_procurement_quality");
expect(userFacing).toContain("procurement-concentration route");
expect(userFacing).toContain("банк/финансовая организация");
expect(userFacing).toContain("Поставщик А");
expect(userFacing).toContain("Надежность поставщиков");
expect(userFacing).not.toContain("outgoing cash concentration proxy");
expect(result.reason_codes).toContain("runtime_bridge_route_candidate_ready_for_reviewed_execution");
expect(result.reason_codes).toContain("answer_contains_business_overview_vendor_procurement_quality_financial_institution_leads_outgoing_cash");
});
it("bridges selected-item inventory provenance templates through exact document evidence", async () => {
const deps = buildDeps([
{
@@ -1,4 +1,109 @@
[
{
"generation_id": "gen-ag05121628-50ea6c",
"created_at": "2026-05-12T16:28:41+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | Phase20 continuity/ranking/self-scope accepted replay 2026-05-12",
"count": 6,
"domain": "address_phase20_continuity_stabilization",
"questions": [
"кто у нас самый доходный клиент за все время?",
"какой у нас самый доходный год?",
"кто нам должен денег на май 2017?",
"а какой ндс мы должны примерно заплатить за этот период?",
"мы должны комуто денег на сегодня?",
"а нам?"
],
"generated_by": "Codex",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260512162841_gen-ag05121628-50ea6c.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"autogen_personality_id": null,
"autogen_personality_prompt": null,
"source_session_id": null,
"saved_session_file": "assistant_saved_session_20260512162841_gen-ag05121628-50ea6c.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "ranking catalog alignment, self-scope pronoun, compact debt answers",
"architecture_phase": "MCP автономка / semantic integrity",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase20_continuity_stabilization.json",
"scenario_id": "address_truth_harness_phase20_continuity_stabilization",
"semantic_tags": [
"exact_not_overwritten",
"garbage_anchor_forbidden",
"payables_snapshot",
"period_carryover",
"pronoun_followup",
"receivables_snapshot",
"temporal_tail_not_entity",
"value_flow_ranking",
"vat_followup",
"year_tail_not_entity"
],
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase20_continuity_stabilization_rerun_planner_ranking_fix",
"saved_after_validated_replay": true
}
},
{
"generation_id": "gen-ag05121357-9ea5d6",
"created_at": "2026-05-12T13:57:26+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | Phase 95 vendor/procurement quality reviewed route",
"count": 7,
"domain": "address_phase95_vendor_procurement_quality_reviewed_route",
"questions": [
"по ООО Альтернатива Плюс за 2020 есть ли риск, что мы зависим от одного поставщика?",
"а банк из этого ответа считать обычным поставщиком?",
"по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?",
"а чистая прибыль и маржа за 2020 какие?",
"НДС за 2020 по ООО Альтернатива Плюс какой?",
"а кто принес больше всего денег за 2020?",
"по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?"
],
"generated_by": "codex_agent",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260512135726_gen-ag05121357-9ea5d6.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"autogen_personality_id": null,
"autogen_personality_prompt": null,
"source_session_id": null,
"saved_session_file": "assistant_saved_session_20260512135726_gen-ag05121357-9ea5d6.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "Focused semantic replay for promoting vendor_risk_procurement_quality from proxy-only enablement to reviewed procurement-concentration evidence while preserving debt, profit, VAT, value-flow, and inventory reserve canaries.",
"architecture_phase": "turnaround_11",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase95_vendor_procurement_quality_reviewed_route.json",
"scenario_id": "address_truth_harness_phase95_vendor_procurement_quality_reviewed_route",
"semantic_tags": [
"accounting_profit_margin",
"business_overview",
"canary",
"context_carryover",
"debt_due_date_boundary",
"financial_institution_boundary",
"inventory_reserve_boundary",
"missing_proof_families",
"profit_margin_boundary",
"ready_for_reviewed_execution",
"value_flow_ranking",
"vat_continuity",
"vendor_risk_procurement_boundary",
"vendor_risk_procurement_quality"
],
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase95_vendor_procurement_quality_reviewed_route_live2",
"saved_after_validated_replay": true
}
},
{
"generation_id": "gen-ag05101319-c04f79",
"created_at": "2026-05-10T13:19:22+00:00",
@@ -1392,4 +1497,4 @@
"latest_acceptance": null
}
}
]
]
@@ -0,0 +1,173 @@
{
"saved_at": "2026-05-12T13:57:26+00:00",
"generation_id": "gen-ag05121357-9ea5d6",
"mode": "saved_user_sessions",
"title": "AGENT | Phase 95 vendor/procurement quality reviewed route",
"agent_run": true,
"questions": [
"по ООО Альтернатива Плюс за 2020 есть ли риск, что мы зависим от одного поставщика?",
"а банк из этого ответа считать обычным поставщиком?",
"по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?",
"а чистая прибыль и маржа за 2020 какие?",
"НДС за 2020 по ООО Альтернатива Плюс какой?",
"а кто принес больше всего денег за 2020?",
"по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?"
],
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "Focused semantic replay for promoting vendor_risk_procurement_quality from proxy-only enablement to reviewed procurement-concentration evidence while preserving debt, profit, VAT, value-flow, and inventory reserve canaries.",
"architecture_phase": "turnaround_11",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase95_vendor_procurement_quality_reviewed_route.json",
"scenario_id": "address_truth_harness_phase95_vendor_procurement_quality_reviewed_route",
"semantic_tags": [
"accounting_profit_margin",
"business_overview",
"canary",
"context_carryover",
"debt_due_date_boundary",
"financial_institution_boundary",
"inventory_reserve_boundary",
"missing_proof_families",
"profit_margin_boundary",
"ready_for_reviewed_execution",
"value_flow_ranking",
"vat_continuity",
"vendor_risk_procurement_boundary",
"vendor_risk_procurement_quality"
],
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase95_vendor_procurement_quality_reviewed_route_live2",
"saved_after_validated_replay": true,
"save_gate": {
"schema_version": "agent_semantic_save_gate_v1",
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase95_vendor_procurement_quality_reviewed_route_live2",
"final_status": "accepted",
"review_overall_status": "pass",
"business_overall_status": "pass",
"steps_total": 7,
"steps_passed": 7,
"steps_failed": 0,
"steps_with_business_failures": 0,
"steps_with_business_warnings": 0,
"acceptance_gate_passed": true,
"saved_after_validated_replay": true
}
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "по ООО Альтернатива Плюс за 2020 есть ли риск, что мы зависим от одного поставщика?",
"created_at": "2026-05-12T13:57:26+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "а банк из этого ответа считать обычным поставщиком?",
"created_at": "2026-05-12T13:57:26+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?",
"created_at": "2026-05-12T13:57:26+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "а чистая прибыль и маржа за 2020 какие?",
"created_at": "2026-05-12T13:57:26+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "НДС за 2020 по ООО Альтернатива Плюс какой?",
"created_at": "2026-05-12T13:57:26+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "а кто принес больше всего денег за 2020?",
"created_at": "2026-05-12T13:57:26+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-007",
"role": "user",
"text": "по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?",
"created_at": "2026-05-12T13:57:26+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "Focused semantic replay for promoting vendor_risk_procurement_quality from proxy-only enablement to reviewed procurement-concentration evidence while preserving debt, profit, VAT, value-flow, and inventory reserve canaries.",
"architecture_phase": "turnaround_11",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase95_vendor_procurement_quality_reviewed_route.json",
"scenario_id": "address_truth_harness_phase95_vendor_procurement_quality_reviewed_route",
"semantic_tags": [
"accounting_profit_margin",
"business_overview",
"canary",
"context_carryover",
"debt_due_date_boundary",
"financial_institution_boundary",
"inventory_reserve_boundary",
"missing_proof_families",
"profit_margin_boundary",
"ready_for_reviewed_execution",
"value_flow_ranking",
"vat_continuity",
"vendor_risk_procurement_boundary",
"vendor_risk_procurement_quality"
],
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase95_vendor_procurement_quality_reviewed_route_live2",
"saved_after_validated_replay": true,
"save_gate": {
"schema_version": "agent_semantic_save_gate_v1",
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase95_vendor_procurement_quality_reviewed_route_live2",
"final_status": "accepted",
"review_overall_status": "pass",
"business_overall_status": "pass",
"steps_total": 7,
"steps_passed": 7,
"steps_failed": 0,
"steps_with_business_failures": 0,
"steps_with_business_warnings": 0,
"acceptance_gate_passed": true,
"saved_after_validated_replay": true
}
}
}
}
@@ -0,0 +1,155 @@
{
"saved_at": "2026-05-12T16:28:41+00:00",
"generation_id": "gen-ag05121628-50ea6c",
"mode": "saved_user_sessions",
"title": "AGENT | Phase20 continuity/ranking/self-scope accepted replay 2026-05-12",
"agent_run": true,
"questions": [
"кто у нас самый доходный клиент за все время?",
"какой у нас самый доходный год?",
"кто нам должен денег на май 2017?",
"а какой ндс мы должны примерно заплатить за этот период?",
"мы должны комуто денег на сегодня?",
"а нам?"
],
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "ranking catalog alignment, self-scope pronoun, compact debt answers",
"architecture_phase": "MCP автономка / semantic integrity",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase20_continuity_stabilization.json",
"scenario_id": "address_truth_harness_phase20_continuity_stabilization",
"semantic_tags": [
"exact_not_overwritten",
"garbage_anchor_forbidden",
"payables_snapshot",
"period_carryover",
"pronoun_followup",
"receivables_snapshot",
"temporal_tail_not_entity",
"value_flow_ranking",
"vat_followup",
"year_tail_not_entity"
],
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase20_continuity_stabilization_rerun_planner_ranking_fix",
"saved_after_validated_replay": true,
"save_gate": {
"schema_version": "agent_semantic_save_gate_v1",
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase20_continuity_stabilization_rerun_planner_ranking_fix",
"final_status": "accepted",
"review_overall_status": "pass",
"business_overall_status": "pass",
"steps_total": 6,
"steps_passed": 6,
"steps_failed": 0,
"steps_with_business_failures": 0,
"steps_with_business_warnings": 0,
"acceptance_gate_passed": true,
"saved_after_validated_replay": true
}
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "кто у нас самый доходный клиент за все время?",
"created_at": "2026-05-12T16:28:41+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "какой у нас самый доходный год?",
"created_at": "2026-05-12T16:28:41+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "кто нам должен денег на май 2017?",
"created_at": "2026-05-12T16:28:41+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "а какой ндс мы должны примерно заплатить за этот период?",
"created_at": "2026-05-12T16:28:41+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "мы должны комуто денег на сегодня?",
"created_at": "2026-05-12T16:28:41+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "а нам?",
"created_at": "2026-05-12T16:28:41+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "ranking catalog alignment, self-scope pronoun, compact debt answers",
"architecture_phase": "MCP автономка / semantic integrity",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase20_continuity_stabilization.json",
"scenario_id": "address_truth_harness_phase20_continuity_stabilization",
"semantic_tags": [
"exact_not_overwritten",
"garbage_anchor_forbidden",
"payables_snapshot",
"period_carryover",
"pronoun_followup",
"receivables_snapshot",
"temporal_tail_not_entity",
"value_flow_ranking",
"vat_followup",
"year_tail_not_entity"
],
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase20_continuity_stabilization_rerun_planner_ranking_fix",
"saved_after_validated_replay": true,
"save_gate": {
"schema_version": "agent_semantic_save_gate_v1",
"validation_status": "accepted_live_replay",
"validated_run_dir": "artifacts\\domain_runs\\phase20_continuity_stabilization_rerun_planner_ranking_fix",
"final_status": "accepted",
"review_overall_status": "pass",
"business_overall_status": "pass",
"steps_total": 6,
"steps_passed": 6,
"steps_failed": 0,
"steps_with_business_failures": 0,
"steps_with_business_warnings": 0,
"acceptance_gate_passed": true,
"saved_after_validated_replay": true
}
}
}
}
@@ -0,0 +1,46 @@
{
"suite_id": "assistant_saved_session_gen-ag05121357-9ea5d6",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-05-12T13:57:26+00:00",
"generation_id": "gen-ag05121357-9ea5d6",
"mode": "saved_user_sessions",
"title": "AGENT | Phase 95 vendor/procurement quality reviewed route",
"domain": "address_phase95_vendor_procurement_quality_reviewed_route",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | Phase 95 vendor/procurement quality reviewed route",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "по ООО Альтернатива Плюс за 2020 есть ли риск, что мы зависим от одного поставщика?"
},
{
"user_message": "а банк из этого ответа считать обычным поставщиком?"
},
{
"user_message": "по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?"
},
{
"user_message": "а чистая прибыль и маржа за 2020 какие?"
},
{
"user_message": "НДС за 2020 по ООО Альтернатива Плюс какой?"
},
{
"user_message": "а кто принес больше всего денег за 2020?"
},
{
"user_message": "по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?"
}
]
}
]
}
@@ -0,0 +1,43 @@
{
"suite_id": "assistant_saved_session_gen-ag05121628-50ea6c",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-05-12T16:28:41+00:00",
"generation_id": "gen-ag05121628-50ea6c",
"mode": "saved_user_sessions",
"title": "AGENT | Phase20 continuity/ranking/self-scope accepted replay 2026-05-12",
"domain": "address_phase20_continuity_stabilization",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | Phase20 continuity/ranking/self-scope accepted replay 2026-05-12",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "кто у нас самый доходный клиент за все время?"
},
{
"user_message": "какой у нас самый доходный год?"
},
{
"user_message": "кто нам должен денег на май 2017?"
},
{
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
},
{
"user_message": "мы должны комуто денег на сегодня?"
},
{
"user_message": "а нам?"
}
]
}
]
}