ДОМЕНЫ - ВОПРОСЫ - fix(ui-assistant-chat): render markdown bold, add readable block spacing, force scroll-to-bottom on send + ЮИ
This commit is contained in:
@@ -153,6 +153,35 @@ function formatMoney(value: number): string {
|
||||
return value.toFixed(2);
|
||||
}
|
||||
|
||||
function formatNumberWithDots(value: number, fractionDigits = 0): string {
|
||||
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: number): string {
|
||||
return `${formatNumberWithDots(value, 2)} ₽`;
|
||||
}
|
||||
|
||||
function emphasizeNumericTokens(line: string): string {
|
||||
if (!line) {
|
||||
return line;
|
||||
}
|
||||
return line.replace(/(?<!\*)\d(?:[\d.,:/-]*\d)?(?!\*)/g, (token) => `**${token}**`);
|
||||
}
|
||||
|
||||
function parseIsoDateToken(value: string | null | undefined): { year: number; month: number; day: number } | null {
|
||||
const source = String(value ?? "").trim();
|
||||
const match = source.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
@@ -2412,6 +2441,7 @@ export function composeFactualReply(
|
||||
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 ?? "...")}.`
|
||||
@@ -2429,9 +2459,10 @@ export function composeFactualReply(
|
||||
);
|
||||
|
||||
const lines: string[] = [
|
||||
`Итого подтвержденный долг на ${formatDateRu(payablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
||||
"",
|
||||
"Блок 1. Статус результата",
|
||||
"- Режим результата: подтвержденный срез обязательств к оплате (exact route).",
|
||||
"- Эвристический shortlist в этом режиме не используется."
|
||||
"- Режим результата: подтвержденный срез обязательств к оплате (exact route)."
|
||||
];
|
||||
|
||||
lines.push("");
|
||||
@@ -2447,15 +2478,15 @@ export function composeFactualReply(
|
||||
|
||||
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. Подтвержденные позиции к оплате");
|
||||
@@ -2463,7 +2494,7 @@ export function composeFactualReply(
|
||||
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)}`
|
||||
`${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 {
|
||||
@@ -2472,7 +2503,7 @@ export function composeFactualReply(
|
||||
|
||||
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",
|
||||
@@ -2498,7 +2529,7 @@ export function composeFactualReply(
|
||||
: null;
|
||||
|
||||
const formatHeuristicItem = (item: PayablesCounterpartyRiskAggregate, index: number): string =>
|
||||
`${index + 1}. ${item.name} | сумма сигнала: ${formatMoney(item.totalAmount)} | операций: ${item.operations}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`;
|
||||
`${index + 1}. ${item.name} | сумма к проверке: ${formatMoneyRub(item.totalAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`;
|
||||
|
||||
const pushCategorySlice = (
|
||||
lines: string[],
|
||||
@@ -2518,9 +2549,9 @@ export function composeFactualReply(
|
||||
const lines = [
|
||||
"Блок 1. Статус результата",
|
||||
forcedFallbackFromConfirmed
|
||||
? "- Режим результата: эвристический скоринг в рамках fallback, потому что подтвержденный срез обязательств к оплате недоступен."
|
||||
: "- Режим результата: эвристический скоринг (shortlist кандидатов по признакам незакрытых обязательств в контуре 60/76).",
|
||||
"- Тип результата: кандидаты для ручной проверки, а не финальный платежный реестр.",
|
||||
? "- Точный реестр обязательств сейчас недоступен, поэтому показан предварительный список на проверку."
|
||||
: "- Формат результата: предварительный список на проверку.",
|
||||
"- Это рабочий список для проверки, а не финальный платежный реестр.",
|
||||
"",
|
||||
"Блок 2. Как читать результат",
|
||||
"- Это shortlist кандидатов: нужна ручная проверка бухгалтером.",
|
||||
@@ -2529,8 +2560,8 @@ export function composeFactualReply(
|
||||
...(carryoverLine ? [carryoverLine] : []),
|
||||
"",
|
||||
"Блок 3. Сводка выборки",
|
||||
`- Строк в выборке: ${rows.length}.`,
|
||||
`- Контрагентов-кандидатов: ${counterparties.length}.`
|
||||
`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`,
|
||||
`- Контрагентов-кандидатов: ${formatNumberWithDots(counterparties.length)}.`
|
||||
];
|
||||
|
||||
if (counterparties.length > 0) {
|
||||
@@ -2548,10 +2579,10 @@ export function composeFactualReply(
|
||||
|
||||
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. Кандидаты на проверку в первую очередь");
|
||||
@@ -2585,7 +2616,10 @@ export function composeFactualReply(
|
||||
},
|
||||
{ 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: string[] = [
|
||||
`Итого подтвержденный долг на ${formatDateRu(payablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
||||
"",
|
||||
"Блок 1. Статус результата",
|
||||
"- Режим результата: подтвержденный срез обязательств к оплате по состоянию на дату среза в контуре 60/76.",
|
||||
"- Тип результата: подтвержденные остатки к оплате.",
|
||||
@@ -2599,24 +2633,24 @@ export function composeFactualReply(
|
||||
...(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)}`
|
||||
`${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",
|
||||
@@ -2628,7 +2662,7 @@ export function composeFactualReply(
|
||||
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",
|
||||
@@ -2640,7 +2674,7 @@ export function composeFactualReply(
|
||||
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