ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - fix(router): keep llm-first non-domain indexing and prevent followup force into address lane
This commit is contained in:
@@ -497,6 +497,9 @@ function classifyPayablesLiabilityCategory(row, counterparty) {
|
||||
};
|
||||
const reasons = new Set();
|
||||
const text = `${counterparty} ${row.registrator} ${row.analytics.join(" ")}`.toLowerCase();
|
||||
const hasBankOrCreditSignal = /(?:банк|сбер|втб|альфа|газпромбанк|кредит|депозит|loan|overdraft|deposit|bank)/iu.test(text);
|
||||
const hasTaxOrStateSignal = /(?:уфк|ифнс|фнс|налог|пфр|фсс|сфр|казнач|бюджет|гос|департамент|министер|муницип|город москвы|федерал|налогов)/iu.test(text);
|
||||
const hasCommercialCounterpartySignal = /(?:\bип\b|ооо|ао|зао|пао|подряд|поставщик|supplier|vendor|contractor|заказчик|клиент|counterparty)/iu.test(text);
|
||||
const accountPrefixes = [extractAccountSectionCode(row.account_dt), extractAccountSectionCode(row.account_kt)].filter((item) => Boolean(item));
|
||||
if (accountPrefixes.includes("60")) {
|
||||
scores.supplier_or_contractor += 3;
|
||||
@@ -511,18 +514,20 @@ function classifyPayablesLiabilityCategory(row, counterparty) {
|
||||
reasons.add("участие счета 68/69");
|
||||
}
|
||||
if (accountPrefixes.includes("76")) {
|
||||
scores.supplier_or_contractor += 1;
|
||||
reasons.add("участие счета 76");
|
||||
scores.other += 1;
|
||||
reasons.add("участие счета 76 (прочие расчеты)");
|
||||
}
|
||||
if (/(?:банк|сбер|втб|альфа|газпромбанк|кредит|депозит|loan|overdraft|deposit)/iu.test(text)) {
|
||||
scores.bank_or_credit += 3;
|
||||
if (hasBankOrCreditSignal) {
|
||||
scores.bank_or_credit += 6;
|
||||
scores.supplier_or_contractor = Math.max(0, scores.supplier_or_contractor - 2);
|
||||
reasons.add("банк/кредит в аналитике");
|
||||
}
|
||||
if (/(?:уфк|ифнс|фнс|налог|пфр|фсс|сфр|казнач|бюджет|гос|департамент|министер|муницип|город москвы|федерал)/iu.test(text)) {
|
||||
scores.tax_or_state += 3;
|
||||
if (hasTaxOrStateSignal) {
|
||||
scores.tax_or_state += 6;
|
||||
scores.supplier_or_contractor = Math.max(0, scores.supplier_or_contractor - 2);
|
||||
reasons.add("налог/госорган в аналитике");
|
||||
}
|
||||
if (/(?:\bип\b|ооо|ао|зао|пао|подряд|поставщик|supplier|vendor|contractor)/iu.test(text)) {
|
||||
if (hasCommercialCounterpartySignal && !hasBankOrCreditSignal && !hasTaxOrStateSignal) {
|
||||
scores.supplier_or_contractor += 2;
|
||||
reasons.add("коммерческий контрагент в аналитике");
|
||||
}
|
||||
@@ -660,6 +665,8 @@ function buildPayablesConfirmedBalanceAggregate(rows, asOfDate) {
|
||||
continue;
|
||||
}
|
||||
const classified = classifyPayablesLiabilityCategory(row, name);
|
||||
const contract = extractContractName(row);
|
||||
const sourceRefs = extractPayablesSourceRefs(row, name, contract);
|
||||
const current = byCounterparty.get(name);
|
||||
if (!current) {
|
||||
byCounterparty.set(name, {
|
||||
@@ -673,7 +680,10 @@ function buildPayablesConfirmedBalanceAggregate(rows, asOfDate) {
|
||||
tax_or_state: classified.scores.tax_or_state,
|
||||
other: classified.scores.other
|
||||
},
|
||||
reasons: new Set(classified.reasons)
|
||||
reasons: new Set(classified.reasons),
|
||||
contracts: new Set(contract ? [contract] : []),
|
||||
documents: new Set(row.registrator ? [row.registrator] : []),
|
||||
sourceRefs: new Set(sourceRefs)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -692,6 +702,15 @@ function buildPayablesConfirmedBalanceAggregate(rows, asOfDate) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
return Array.from(byCounterparty.entries())
|
||||
.map(([name, item]) => ({
|
||||
@@ -701,7 +720,10 @@ function buildPayablesConfirmedBalanceAggregate(rows, asOfDate) {
|
||||
firstPeriod: item.firstPeriod,
|
||||
lastPeriod: item.lastPeriod,
|
||||
category: resolvePayablesLiabilityCategory(item.categoryScores),
|
||||
categoryReasons: Array.from(item.reasons).slice(0, 2)
|
||||
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) => {
|
||||
@@ -887,6 +909,59 @@ function extractContractName(row) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function normalizeEntityToken(value) {
|
||||
return String(value ?? "")
|
||||
.toLowerCase()
|
||||
.replace(/ё/g, "е")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
function extractPayablesSourceRefs(row, counterparty, contract) {
|
||||
const refs = new Set();
|
||||
const counterpartyToken = normalizeEntityToken(counterparty);
|
||||
const contractToken = normalizeEntityToken(contract);
|
||||
const sourceRefTokenPattern = /(?:№|договор|contract|счет|сч[её]т|акт|накладн|плат[её]ж|invoice|payment|order|заявк|реестр|доп\.?\s*соглаш)/iu;
|
||||
for (const token of row.analytics) {
|
||||
const normalized = String(token ?? "").trim();
|
||||
if (!normalized) {
|
||||
continue;
|
||||
}
|
||||
if (/^(?:0|<пусто>|пустая ссылка)$/iu.test(normalized)) {
|
||||
continue;
|
||||
}
|
||||
if (/^\d{4}-\d{2}-\d{2}/.test(normalized)) {
|
||||
continue;
|
||||
}
|
||||
const tokenNormalized = normalizeEntityToken(normalized);
|
||||
if (!tokenNormalized) {
|
||||
continue;
|
||||
}
|
||||
if (tokenNormalized === counterpartyToken || (contractToken && tokenNormalized === contractToken)) {
|
||||
continue;
|
||||
}
|
||||
if (sourceRefTokenPattern.test(normalized) || /(?:[a-zа-я].*\d|\d.*[a-zа-я])/iu.test(normalized)) {
|
||||
refs.add(normalized);
|
||||
}
|
||||
if (refs.size >= 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Array.from(refs);
|
||||
}
|
||||
function formatPayablesEvidenceSuffix(item) {
|
||||
const parts = [];
|
||||
if (item.contracts.length > 0) {
|
||||
parts.push(`договор: ${item.contracts.slice(0, 2).join("; ")}`);
|
||||
}
|
||||
if (item.documents.length > 0) {
|
||||
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 deriveOperationalYearWindow(yearDocs, yearOps) {
|
||||
const docsSeries = [...yearDocs].sort((a, b) => a.year - b.year);
|
||||
const fallbackSeries = [...yearOps].sort((a, b) => a.year - b.year);
|
||||
@@ -1816,11 +1891,9 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
|
||||
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
|
||||
const periodTo = normalizeIsoDateOnly(options.periodTo);
|
||||
const scopeLine = asOfDate
|
||||
? `- Дата среза: ${formatDateRu(asOfDate)}.`
|
||||
: periodFrom || periodTo
|
||||
? `- Период анализа: ${formatDateRu(periodFrom ?? "...")}..${formatDateRu(periodTo ?? "...")}.`
|
||||
: null;
|
||||
const periodScopeLine = !asOfDate && (periodFrom || periodTo)
|
||||
? `- Период анализа: ${formatDateRu(periodFrom ?? "...")}..${formatDateRu(periodTo ?? "...")}.`
|
||||
: null;
|
||||
const carryoverLine = asOfDate || periodFrom || periodTo
|
||||
? "- В срез могут входить обязательства, возникшие до периода, если они оставались открытыми на дату среза."
|
||||
: null;
|
||||
@@ -1836,8 +1909,8 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
lines.push("");
|
||||
lines.push("Блок 2. Что учтено");
|
||||
lines.push(`- Дата среза: ${formatDateRu(payablesAsOfDate)}.`);
|
||||
if (scopeLine) {
|
||||
lines.push(scopeLine);
|
||||
if (periodScopeLine) {
|
||||
lines.push(periodScopeLine);
|
||||
}
|
||||
lines.push("- Контур: обязательства по счетам 60/76.");
|
||||
if (carryoverLine) {
|
||||
@@ -1856,7 +1929,7 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
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(", ")}` : ""}`));
|
||||
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)}`));
|
||||
}
|
||||
else {
|
||||
lines.push("- Подтвержденных открытых обязательств к оплате на дату среза не найдено.");
|
||||
@@ -1977,8 +2050,8 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
`- ${liabilityCategoryLabel("tax_or_state")}: ${categoryCounts.tax_or_state}`,
|
||||
`- ${liabilityCategoryLabel("other")}: ${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(", ")}` : ""}`)
|
||||
"Блок 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)}`)
|
||||
];
|
||||
return {
|
||||
responseType: "FACTUAL_LIST",
|
||||
|
||||
Reference in New Issue
Block a user