ДОМЕНЫ - ВОПРОСЫ - fix(ui-assistant-chat): render markdown bold, add readable block spacing, force scroll-to-bottom on send + ЮИ
This commit is contained in:
@@ -81,6 +81,32 @@ function formatMoney(value) {
|
||||
}
|
||||
return value.toFixed(2);
|
||||
}
|
||||
function formatNumberWithDots(value, fractionDigits = 0) {
|
||||
if (!Number.isFinite(value)) {
|
||||
if (fractionDigits > 0) {
|
||||
return `0,${"0".repeat(fractionDigits)}`;
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
const sign = value < 0 ? "-" : "";
|
||||
const absolute = Math.abs(value);
|
||||
const fixed = absolute.toFixed(Math.max(0, fractionDigits));
|
||||
const [intPartRaw, fractionPartRaw] = fixed.split(".");
|
||||
const groupedInt = intPartRaw.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||
if (fractionDigits <= 0) {
|
||||
return `${sign}${groupedInt}`;
|
||||
}
|
||||
return `${sign}${groupedInt},${fractionPartRaw ?? "0".repeat(fractionDigits)}`;
|
||||
}
|
||||
function formatMoneyRub(value) {
|
||||
return `${formatNumberWithDots(value, 2)} ₽`;
|
||||
}
|
||||
function emphasizeNumericTokens(line) {
|
||||
if (!line) {
|
||||
return line;
|
||||
}
|
||||
return line.replace(/(?<!\*)\d(?:[\d.,:/-]*\d)?(?!\*)/g, (token) => `**${token}**`);
|
||||
}
|
||||
function parseIsoDateToken(value) {
|
||||
const source = String(value ?? "").trim();
|
||||
const match = source.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
@@ -1891,6 +1917,7 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
|
||||
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
|
||||
const periodTo = normalizeIsoDateOnly(options.periodTo);
|
||||
const totalOutstandingAmount = confirmedBalances.reduce((sum, item) => sum + item.outstandingAmount, 0);
|
||||
const periodScopeLine = !asOfDate && (periodFrom || periodTo)
|
||||
? `- Период анализа: ${formatDateRu(periodFrom ?? "...")}..${formatDateRu(periodTo ?? "...")}.`
|
||||
: null;
|
||||
@@ -1902,9 +1929,10 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
return acc;
|
||||
}, { supplier_or_contractor: 0, bank_or_credit: 0, tax_or_state: 0, other: 0 });
|
||||
const lines = [
|
||||
`Итого подтвержденный долг на ${formatDateRu(payablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
||||
"",
|
||||
"Блок 1. Статус результата",
|
||||
"- Режим результата: подтвержденный срез обязательств к оплате (exact route).",
|
||||
"- Эвристический shortlist в этом режиме не используется."
|
||||
"- Режим результата: подтвержденный срез обязательств к оплате (exact route)."
|
||||
];
|
||||
lines.push("");
|
||||
lines.push("Блок 2. Что учтено");
|
||||
@@ -1918,25 +1946,25 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
}
|
||||
lines.push("");
|
||||
lines.push("Блок 3. Сводка");
|
||||
lines.push(`- Строк в выборке: ${rows.length}.`);
|
||||
lines.push(`- Контрагентов с подтвержденным остатком к оплате: ${confirmedBalances.length}.`);
|
||||
lines.push(`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`);
|
||||
lines.push(`- Контрагентов с подтвержденным остатком к оплате: ${formatNumberWithDots(confirmedBalances.length)}.`);
|
||||
lines.push("");
|
||||
lines.push("Блок 4. Категории обязательств");
|
||||
lines.push(`- ${liabilityCategoryLabel("supplier_or_contractor")}: ${categoryCounts.supplier_or_contractor}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("bank_or_credit")}: ${categoryCounts.bank_or_credit}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("tax_or_state")}: ${categoryCounts.tax_or_state}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("other")}: ${categoryCounts.other}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("supplier_or_contractor")}: ${formatNumberWithDots(categoryCounts.supplier_or_contractor)}.`);
|
||||
lines.push(`- ${liabilityCategoryLabel("bank_or_credit")}: ${formatNumberWithDots(categoryCounts.bank_or_credit)}.`);
|
||||
lines.push(`- ${liabilityCategoryLabel("tax_or_state")}: ${formatNumberWithDots(categoryCounts.tax_or_state)}.`);
|
||||
lines.push(`- ${liabilityCategoryLabel("other")}: ${formatNumberWithDots(categoryCounts.other)}.`);
|
||||
lines.push("");
|
||||
lines.push("Блок 5. Подтвержденные позиции к оплате");
|
||||
if (confirmedBalances.length > 0) {
|
||||
lines.push(...confirmedBalances.slice(0, 10).map((item, index) => `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoney(item.outstandingAmount)} | операций: ${item.operations}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`));
|
||||
lines.push(...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)}`));
|
||||
}
|
||||
else {
|
||||
lines.push("- Подтвержденных открытых обязательств к оплате на дату среза не найдено.");
|
||||
}
|
||||
return {
|
||||
responseType: confirmedBalances.length > 0 ? "FACTUAL_LIST" : "FACTUAL_SUMMARY",
|
||||
text: lines.join("\n"),
|
||||
text: lines.map(emphasizeNumericTokens).join("\n"),
|
||||
semantics: {
|
||||
result_mode: "confirmed_balance",
|
||||
evidence_strength: confirmedBalances.length > 0 ? "strong" : "medium",
|
||||
@@ -1958,7 +1986,7 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
const carryoverLine = asOfDate || periodFrom || periodTo
|
||||
? "- В список могут попадать обязательства, возникшие раньше выбранного периода, если они потенциально оставались открытыми на дату среза."
|
||||
: null;
|
||||
const formatHeuristicItem = (item, index) => `${index + 1}. ${item.name} | сумма сигнала: ${formatMoney(item.totalAmount)} | операций: ${item.operations}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`;
|
||||
const formatHeuristicItem = (item, index) => `${index + 1}. ${item.name} | сумма к проверке: ${formatMoneyRub(item.totalAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`;
|
||||
const pushCategorySlice = (lines, title, items, limit) => {
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
@@ -1971,9 +1999,9 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
const lines = [
|
||||
"Блок 1. Статус результата",
|
||||
forcedFallbackFromConfirmed
|
||||
? "- Режим результата: эвристический скоринг в рамках fallback, потому что подтвержденный срез обязательств к оплате недоступен."
|
||||
: "- Режим результата: эвристический скоринг (shortlist кандидатов по признакам незакрытых обязательств в контуре 60/76).",
|
||||
"- Тип результата: кандидаты для ручной проверки, а не финальный платежный реестр.",
|
||||
? "- Точный реестр обязательств сейчас недоступен, поэтому показан предварительный список на проверку."
|
||||
: "- Формат результата: предварительный список на проверку.",
|
||||
"- Это рабочий список для проверки, а не финальный платежный реестр.",
|
||||
"",
|
||||
"Блок 2. Как читать результат",
|
||||
"- Это shortlist кандидатов: нужна ручная проверка бухгалтером.",
|
||||
@@ -1982,8 +2010,8 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
...(carryoverLine ? [carryoverLine] : []),
|
||||
"",
|
||||
"Блок 3. Сводка выборки",
|
||||
`- Строк в выборке: ${rows.length}.`,
|
||||
`- Контрагентов-кандидатов: ${counterparties.length}.`
|
||||
`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`,
|
||||
`- Контрагентов-кандидатов: ${formatNumberWithDots(counterparties.length)}.`
|
||||
];
|
||||
if (counterparties.length > 0) {
|
||||
const categoryCounts = counterparties.reduce((acc, item) => {
|
||||
@@ -1996,10 +2024,10 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
const other = counterparties.filter((item) => item.category === "other");
|
||||
lines.push("");
|
||||
lines.push("Блок 4. Категории обязательств");
|
||||
lines.push(`- ${liabilityCategoryLabel("supplier_or_contractor")}: ${categoryCounts.supplier_or_contractor}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("bank_or_credit")}: ${categoryCounts.bank_or_credit}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("tax_or_state")}: ${categoryCounts.tax_or_state}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("other")}: ${categoryCounts.other}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("supplier_or_contractor")}: ${formatNumberWithDots(categoryCounts.supplier_or_contractor)}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("bank_or_credit")}: ${formatNumberWithDots(categoryCounts.bank_or_credit)}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("tax_or_state")}: ${formatNumberWithDots(categoryCounts.tax_or_state)}`);
|
||||
lines.push(`- ${liabilityCategoryLabel("other")}: ${formatNumberWithDots(categoryCounts.other)}`);
|
||||
lines.push("");
|
||||
lines.push("Блок 5. Кандидаты на проверку в первую очередь");
|
||||
pushCategorySlice(lines, "5.1 Поставщики/подрядчики:", suppliers, 6);
|
||||
@@ -2027,7 +2055,10 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
acc[item.category] += 1;
|
||||
return acc;
|
||||
}, { supplier_or_contractor: 0, bank_or_credit: 0, tax_or_state: 0, other: 0 });
|
||||
const totalOutstandingAmount = confirmedBalances.reduce((sum, item) => sum + item.outstandingAmount, 0);
|
||||
const lines = [
|
||||
`Итого подтвержденный долг на ${formatDateRu(payablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
||||
"",
|
||||
"Блок 1. Статус результата",
|
||||
"- Режим результата: подтвержденный срез обязательств к оплате по состоянию на дату среза в контуре 60/76.",
|
||||
"- Тип результата: подтвержденные остатки к оплате.",
|
||||
@@ -2041,21 +2072,21 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
...(carryoverLine ? [carryoverLine] : []),
|
||||
"",
|
||||
"Блок 3. Сводка выборки",
|
||||
`- Строк в выборке: ${rows.length}.`,
|
||||
`- Контрагентов с подтвержденным остатком: ${confirmedBalances.length}.`,
|
||||
`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`,
|
||||
`- Контрагентов с подтвержденным остатком: ${formatNumberWithDots(confirmedBalances.length)}.`,
|
||||
"",
|
||||
"Блок 4. Категории обязательств",
|
||||
`- ${liabilityCategoryLabel("supplier_or_contractor")}: ${categoryCounts.supplier_or_contractor}`,
|
||||
`- ${liabilityCategoryLabel("bank_or_credit")}: ${categoryCounts.bank_or_credit}`,
|
||||
`- ${liabilityCategoryLabel("tax_or_state")}: ${categoryCounts.tax_or_state}`,
|
||||
`- ${liabilityCategoryLabel("other")}: ${categoryCounts.other}`,
|
||||
`- ${liabilityCategoryLabel("supplier_or_contractor")}: ${formatNumberWithDots(categoryCounts.supplier_or_contractor)}`,
|
||||
`- ${liabilityCategoryLabel("bank_or_credit")}: ${formatNumberWithDots(categoryCounts.bank_or_credit)}`,
|
||||
`- ${liabilityCategoryLabel("tax_or_state")}: ${formatNumberWithDots(categoryCounts.tax_or_state)}`,
|
||||
`- ${liabilityCategoryLabel("other")}: ${formatNumberWithDots(categoryCounts.other)}`,
|
||||
"",
|
||||
"Блок 5. Крупнейшие подтвержденные позиции к оплате (по сумме остатка):",
|
||||
...confirmedBalances.slice(0, 10).map((item, index) => `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток к оплате: ${formatMoney(item.outstandingAmount)} | операций в срезе: ${item.operations}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`)
|
||||
...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)}`)
|
||||
];
|
||||
return {
|
||||
responseType: "FACTUAL_LIST",
|
||||
text: lines.join("\n"),
|
||||
text: lines.map(emphasizeNumericTokens).join("\n"),
|
||||
semantics: {
|
||||
result_mode: "confirmed_balance",
|
||||
evidence_strength: "strong",
|
||||
@@ -2066,7 +2097,7 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
const fallbackLines = buildHeuristicLines(true);
|
||||
return {
|
||||
responseType: "FACTUAL_LIST",
|
||||
text: fallbackLines.join("\n"),
|
||||
text: fallbackLines.map(emphasizeNumericTokens).join("\n"),
|
||||
semantics: {
|
||||
result_mode: "heuristic_candidates",
|
||||
evidence_strength: counterparties.length > 0 ? "medium" : "weak",
|
||||
@@ -2077,7 +2108,7 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
const lines = buildHeuristicLines(false);
|
||||
return {
|
||||
responseType: "FACTUAL_LIST",
|
||||
text: lines.join("\n"),
|
||||
text: lines.map(emphasizeNumericTokens).join("\n"),
|
||||
semantics: {
|
||||
result_mode: "heuristic_candidates",
|
||||
evidence_strength: counterparties.length > 0 ? "medium" : "weak",
|
||||
|
||||
Reference in New Issue
Block a user