From 1a94e723813e72b2b48c67be033ba15d4b39be1c Mon Sep 17 00:00:00 2001 From: dctouch Date: Fri, 22 May 2026 09:05:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B6=D0=B0=D1=82=D1=8C=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BB=D0=B3=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=BE=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D1=8B=20=D0=B8=20=D1=82=D0=B5=D1=85=D0=BD=D0=B8=D1=87?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20net-flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/address_runtime/composeStage.js | 42 +++++++------- .../assistantMcpDiscoveryResponseCandidate.js | 52 ++++-------------- .../services/address_runtime/composeStage.ts | 42 +++++++------- .../assistantMcpDiscoveryResponseCandidate.ts | 55 ++++--------------- 4 files changed, 62 insertions(+), 129 deletions(-) diff --git a/llm_normalizer/backend/dist/services/address_runtime/composeStage.js b/llm_normalizer/backend/dist/services/address_runtime/composeStage.js index bb1e56b..e5b766f 100644 --- a/llm_normalizer/backend/dist/services/address_runtime/composeStage.js +++ b/llm_normalizer/backend/dist/services/address_runtime/composeStage.js @@ -2093,36 +2093,31 @@ function formatPayablesEvidenceSuffix(item) { const suffix = item.documents.length > 1 ? ` (+${item.documents.length - 1})` : ""; parts.push(`документ: ${item.documents[0]}${suffix}`); } - if (item.sourceRefs.length > 0) { - parts.push(`source refs: ${item.sourceRefs.slice(0, 2).join("; ")}`); - } 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}.`; + return `${item.name}: встречная часть ${formatMoneyRub(Math.min(item.debitAmount, item.creditAmount))}, ${netText}.`; } -function debtMirrorCleanScopeLabel(kind) { - return kind === "payables" ? "чистый долг к оплате" : "чистую дебиторку к получению"; +function debtMirrorCleanScopeLabel(kind, grammaticalCase) { + if (kind === "payables") { + return grammaticalCase === "genitive" ? "чистого долга к оплате" : "чистый долг к оплате"; + } + return grammaticalCase === "genitive" ? "чистой дебиторки к получению" : "чистую дебиторку к получению"; } function appendDebtMirrorCompactDisclosure(lines, snapshot, kind) { if (snapshot.mirroredOffsetAmount <= 0.005) { return; } lines.push("", "Для сверки:"); - lines.push(`- Отдельно сверено встречных остатков: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; они не включены в ${debtMirrorCleanScopeLabel(kind)}.`); + lines.push(`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она не включена в ${debtMirrorCleanScopeLabel(kind, "accusative")}.`); const leadingMirror = snapshot.mirrorGroups[0] ?? null; if (leadingMirror) { - lines.push(`- Крупнейший встречный хвост: ${formatDebtMirrorGroupLine(leadingMirror)}`); + lines.push(`- Крупнейший встречный контрагент: ${formatDebtMirrorGroupLine(leadingMirror)}`); } } function appendDebtMirrorDisclosure(lines, snapshot, kind) { @@ -2131,8 +2126,11 @@ function appendDebtMirrorDisclosure(lines, snapshot, kind) { } 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)}`)); + lines.push(`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она исключена из ${debtMirrorCleanScopeLabel(kind, "genitive")}.`); + const leadingMirror = snapshot.mirrorGroups[0] ?? null; + if (leadingMirror) { + lines.push(`- Крупнейший встречный контрагент: ${formatDebtMirrorGroupLine(leadingMirror)}`); + } } function deriveOperationalYearWindow(yearDocs, yearOps) { const docsSeries = [...yearDocs].sort((a, b) => a.year - b.year); @@ -3381,8 +3379,7 @@ function composeFactualReplyBody(intent, rows, options = {}) { : [`Коротко: на ${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}).`; + return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`; })); if (confirmedBalances.length > 5) { compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`); @@ -3429,7 +3426,7 @@ function composeFactualReplyBody(intent, rows, options = {}) { lines.push("Подтвержденные позиции к оплате"); if (confirmedBalances.length > 0) { lines.push(...confirmedBalances.slice(0, 10).flatMap((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)}`, + `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`, "" ])); if (lines[lines.length - 1] === "") { @@ -3477,8 +3474,7 @@ function composeFactualReplyBody(intent, rows, options = {}) { : [`Коротко: на ${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}).`; + return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`; })); if (confirmedBalances.length > 5) { compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`); @@ -3525,7 +3521,7 @@ function composeFactualReplyBody(intent, rows, options = {}) { lines.push("Подтвержденные позиции к получению"); if (confirmedBalances.length > 0) { lines.push(...confirmedBalances.slice(0, 10).flatMap((item, index) => [ - `${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`, + `${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`, "" ])); if (lines[lines.length - 1] === "") { @@ -3559,7 +3555,7 @@ function composeFactualReplyBody(intent, rows, options = {}) { const carryoverLine = asOfDate || periodFrom || periodTo ? "- В список могут попадать обязательства, возникшие раньше выбранного периода, если они потенциально оставались открытыми на дату среза." : null; - 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 formatHeuristicItem = (item, index) => `${index + 1}. ${item.name} | сумма к проверке: ${formatMoneyRub(item.totalAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`; const pushCategorySlice = (lines, title, items, limit) => { if (items.length === 0) { return; @@ -3656,7 +3652,7 @@ function composeFactualReplyBody(intent, rows, options = {}) { `- ${liabilityCategoryLabel("other")}: ${formatNumberWithDots(categoryCounts.other)}`, "", "Блок 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)}`) + ...confirmedBalances.slice(0, 10).map((item, index) => `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток к оплате: ${formatMoneyRub(item.outstandingAmount)} | операций в срезе: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`) ]; appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables"); return { diff --git a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryResponseCandidate.js b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryResponseCandidate.js index 92f1f63..680bfa1 100644 --- a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryResponseCandidate.js +++ b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryResponseCandidate.js @@ -215,7 +215,7 @@ function localizeLine(value) { return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С."; } if (/^Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows$/i.test(value)) { - return "Нетто денежного потока рассчитано только как входящие подтвержденные строки 1С минус исходящие подтвержденные строки 1С."; + return "Нетто денежного потока рассчитано как подтвержденные входящие платежи минус подтвержденные исходящие платежи в 1С."; } if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) { return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С."; @@ -531,26 +531,6 @@ function businessOverviewInventoryLine(overview) { ].filter((item) => Boolean(item)); return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null; } -function rowCountText(value) { - const count = Number(value); - return Number.isFinite(count) ? String(count) : null; -} -function sideRowsText(side) { - const rowsWithAmount = rowCountText(side?.rows_with_amount); - const rowsMatched = rowCountText(side?.rows_matched); - if (rowsWithAmount && rowsMatched) { - return `${rowsWithAmount} из ${rowsMatched}`; - } - return rowsWithAmount ?? rowsMatched; -} -function sideDateText(side) { - const first = toNonEmptyString(side?.first_movement_date); - const latest = toNonEmptyString(side?.latest_movement_date); - if (first && latest) { - return first === latest ? `дата ${first}` : `даты ${first}..${latest}`; - } - return first ? `первая дата ${first}` : latest ? `последняя дата ${latest}` : null; -} function bidirectionalNetLabel(direction) { if (direction === "net_outgoing") { return "нетто в сторону контрагента"; @@ -586,28 +566,24 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) { : "по выбранному контуру"; const period = toNonEmptyString(flow.period_scope); const periodText = period ? ` за период ${period}` : " в проверенном окне"; - const incomingRows = sideRowsText(incoming); - const outgoingRows = sideRowsText(outgoing); - const incomingDates = sideDateText(incoming); - const outgoingDates = sideDateText(outgoing); const netLabel = bidirectionalNetLabel(flow.net_direction); const lines = [ `Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.` ]; const basis = []; - if (incomingRows) { - basis.push(`входящих строк с суммой ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`); + if (incomingAmount) { + basis.push("входящие платежи"); } - if (outgoingRows) { - basis.push(`исходящих строк с суммой ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`); + if (outgoingAmount) { + basis.push("исходящие платежи"); } if (basis.length > 0) { - lines.push(`Основа: ${basis.join("; ")}.`); + lines.push(`Основа: проверенные ${basis.join(" и ")} по этому контрагенту в 1С.`); } if (flow.coverage_limited_by_probe_limit === true) { lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода."); } - lines.push("Метод: нетто рассчитано как подтвержденные входящие строки 1С минус подтвержденные исходящие строки; это не полное бухгалтерское сальдо вне проверенного окна."); + lines.push("Метод: нетто рассчитано как подтвержденные входящие платежи минус подтвержденные исходящие платежи; это не полное бухгалтерское сальдо вне проверенного окна."); const fallbackNextStep = toNonEmptyString(draft.next_step_line); if (fallbackNextStep) { lines.push(`Следующий шаг: ${localizeLine(fallbackNextStep)}`); @@ -673,21 +649,17 @@ function buildPreviousCounterpartyValueFlowSummary(flow, separateSubject, docume const lead = `; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ` + `${netLabel} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}`; const basis = []; - const incomingRows = sideRowsText(incoming); - const outgoingRows = sideRowsText(outgoing); - const incomingDates = sideDateText(incoming); - const outgoingDates = sideDateText(outgoing); - if (incomingRows) { - basis.push(`входящие строки ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`); + if (incomingAmount) { + basis.push("входящие платежи"); } - if (outgoingRows) { - basis.push(`исходящие строки ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`); + if (outgoingAmount) { + basis.push("исходящие платежи"); } const documents = previousDocumentSummaryLine(documentBundle, counterparty); if (documents) { basis.push(documents); } - const basisText = basis.length > 0 ? ` Основа: ${basis.join("; ")}.` : ""; + const basisText = basis.length > 0 ? ` Основа: проверенные ${basis.join(" и ")}.` : ""; return { lead, line: `Отдельно по контрагенту ${counterparty}: подтверждено получили ${incomingAmount ?? "0 руб."}, ` + diff --git a/llm_normalizer/backend/src/services/address_runtime/composeStage.ts b/llm_normalizer/backend/src/services/address_runtime/composeStage.ts index 4ef8345..d8df5f1 100644 --- a/llm_normalizer/backend/src/services/address_runtime/composeStage.ts +++ b/llm_normalizer/backend/src/services/address_runtime/composeStage.ts @@ -2723,29 +2723,24 @@ function formatPayablesEvidenceSuffix(item: PayablesConfirmedBalanceAggregate): const suffix = item.documents.length > 1 ? ` (+${item.documents.length - 1})` : ""; parts.push(`документ: ${item.documents[0]}${suffix}`); } - if (item.sourceRefs.length > 0) { - parts.push(`source refs: ${item.sourceRefs.slice(0, 2).join("; ")}`); - } 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}.`; + return `${item.name}: встречная часть ${formatMoneyRub(Math.min(item.debitAmount, item.creditAmount))}, ${netText}.`; } -function debtMirrorCleanScopeLabel(kind: "payables" | "receivables"): string { - return kind === "payables" ? "чистый долг к оплате" : "чистую дебиторку к получению"; +function debtMirrorCleanScopeLabel(kind: "payables" | "receivables", grammaticalCase: "accusative" | "genitive"): string { + if (kind === "payables") { + return grammaticalCase === "genitive" ? "чистого долга к оплате" : "чистый долг к оплате"; + } + return grammaticalCase === "genitive" ? "чистой дебиторки к получению" : "чистую дебиторку к получению"; } function appendDebtMirrorCompactDisclosure( @@ -2758,11 +2753,11 @@ function appendDebtMirrorCompactDisclosure( } lines.push("", "Для сверки:"); lines.push( - `- Отдельно сверено встречных остатков: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; они не включены в ${debtMirrorCleanScopeLabel(kind)}.` + `- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она не включена в ${debtMirrorCleanScopeLabel(kind, "accusative")}.` ); const leadingMirror = snapshot.mirrorGroups[0] ?? null; if (leadingMirror) { - lines.push(`- Крупнейший встречный хвост: ${formatDebtMirrorGroupLine(leadingMirror)}`); + lines.push(`- Крупнейший встречный контрагент: ${formatDebtMirrorGroupLine(leadingMirror)}`); } } @@ -2777,9 +2772,12 @@ function appendDebtMirrorDisclosure( lines.push(""); lines.push("Встречные остатки к сверке"); lines.push( - `- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она исключена из ${debtMirrorCleanScopeLabel(kind)}.` + `- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она исключена из ${debtMirrorCleanScopeLabel(kind, "genitive")}.` ); - lines.push(...snapshot.mirrorGroups.slice(0, 3).map((item, index) => `${index + 1}. ${formatDebtMirrorGroupLine(item)}`)); + const leadingMirror = snapshot.mirrorGroups[0] ?? null; + if (leadingMirror) { + lines.push(`- Крупнейший встречный контрагент: ${formatDebtMirrorGroupLine(leadingMirror)}`); + } } function deriveOperationalYearWindow( @@ -4341,8 +4339,7 @@ function composeFactualReplyBody( 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}).`; + return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`; }) ); if (confirmedBalances.length > 5) { @@ -4396,7 +4393,7 @@ function composeFactualReplyBody( if (confirmedBalances.length > 0) { lines.push( ...confirmedBalances.slice(0, 10).flatMap((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)}`, + `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`, "" ]) ); @@ -4454,8 +4451,7 @@ function composeFactualReplyBody( 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}).`; + return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`; }) ); if (confirmedBalances.length > 5) { @@ -4509,7 +4505,7 @@ function composeFactualReplyBody( if (confirmedBalances.length > 0) { lines.push( ...confirmedBalances.slice(0, 10).flatMap((item, index) => [ - `${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`, + `${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`, "" ]) ); @@ -4548,7 +4544,7 @@ function composeFactualReplyBody( : null; const formatHeuristicItem = (item: PayablesCounterpartyRiskAggregate, index: number): string => - `${index + 1}. ${item.name} | сумма к проверке: ${formatMoneyRub(item.totalAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`; + `${index + 1}. ${item.name} | сумма к проверке: ${formatMoneyRub(item.totalAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`; const pushCategorySlice = ( lines: string[], @@ -4665,7 +4661,7 @@ function composeFactualReplyBody( "Блок 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)}` + `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток к оплате: ${formatMoneyRub(item.outstandingAmount)} | операций в срезе: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}` ) ]; appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables"); diff --git a/llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts b/llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts index 24c432f..32d115a 100644 --- a/llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts +++ b/llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts @@ -262,7 +262,7 @@ function localizeLine(value: string): string { return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С."; } if (/^Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows$/i.test(value)) { - return "Нетто денежного потока рассчитано только как входящие подтвержденные строки 1С минус исходящие подтвержденные строки 1С."; + return "Нетто денежного потока рассчитано как подтвержденные входящие платежи минус подтвержденные исходящие платежи в 1С."; } if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) { return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С."; @@ -614,29 +614,6 @@ function businessOverviewInventoryLine(overview: Record): strin return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null; } -function rowCountText(value: unknown): string | null { - const count = Number(value); - return Number.isFinite(count) ? String(count) : null; -} - -function sideRowsText(side: Record | null): string | null { - const rowsWithAmount = rowCountText(side?.rows_with_amount); - const rowsMatched = rowCountText(side?.rows_matched); - if (rowsWithAmount && rowsMatched) { - return `${rowsWithAmount} из ${rowsMatched}`; - } - return rowsWithAmount ?? rowsMatched; -} - -function sideDateText(side: Record | null): string | null { - const first = toNonEmptyString(side?.first_movement_date); - const latest = toNonEmptyString(side?.latest_movement_date); - if (first && latest) { - return first === latest ? `дата ${first}` : `даты ${first}..${latest}`; - } - return first ? `первая дата ${first}` : latest ? `последняя дата ${latest}` : null; -} - function bidirectionalNetLabel(direction: unknown): string { if (direction === "net_outgoing") { return "нетто в сторону контрагента"; @@ -678,29 +655,25 @@ function buildCompactBidirectionalValueFlowReply( : "по выбранному контуру"; const period = toNonEmptyString(flow.period_scope); const periodText = period ? ` за период ${period}` : " в проверенном окне"; - const incomingRows = sideRowsText(incoming); - const outgoingRows = sideRowsText(outgoing); - const incomingDates = sideDateText(incoming); - const outgoingDates = sideDateText(outgoing); const netLabel = bidirectionalNetLabel(flow.net_direction); const lines = [ `Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.` ]; const basis: string[] = []; - if (incomingRows) { - basis.push(`входящих строк с суммой ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`); + if (incomingAmount) { + basis.push("входящие платежи"); } - if (outgoingRows) { - basis.push(`исходящих строк с суммой ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`); + if (outgoingAmount) { + basis.push("исходящие платежи"); } if (basis.length > 0) { - lines.push(`Основа: ${basis.join("; ")}.`); + lines.push(`Основа: проверенные ${basis.join(" и ")} по этому контрагенту в 1С.`); } if (flow.coverage_limited_by_probe_limit === true) { lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода."); } - lines.push("Метод: нетто рассчитано как подтвержденные входящие строки 1С минус подтвержденные исходящие строки; это не полное бухгалтерское сальдо вне проверенного окна."); + lines.push("Метод: нетто рассчитано как подтвержденные входящие платежи минус подтвержденные исходящие платежи; это не полное бухгалтерское сальдо вне проверенного окна."); const fallbackNextStep = toNonEmptyString(draft.next_step_line); if (fallbackNextStep) { @@ -785,21 +758,17 @@ function buildPreviousCounterpartyValueFlowSummary( `; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ` + `${netLabel} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}`; const basis: string[] = []; - const incomingRows = sideRowsText(incoming); - const outgoingRows = sideRowsText(outgoing); - const incomingDates = sideDateText(incoming); - const outgoingDates = sideDateText(outgoing); - if (incomingRows) { - basis.push(`входящие строки ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`); + if (incomingAmount) { + basis.push("входящие платежи"); } - if (outgoingRows) { - basis.push(`исходящие строки ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`); + if (outgoingAmount) { + basis.push("исходящие платежи"); } const documents = previousDocumentSummaryLine(documentBundle, counterparty); if (documents) { basis.push(documents); } - const basisText = basis.length > 0 ? ` Основа: ${basis.join("; ")}.` : ""; + const basisText = basis.length > 0 ? ` Основа: проверенные ${basis.join(" и ")}.` : ""; return { lead, line: