ДОМЕНЫ - ВОПРОСЫ - НДС: развести as-of и tax-period intent, ускорить и стабилизировать VAT source probe, починить M23 тесты
This commit is contained in:
@@ -114,6 +114,9 @@ function emphasizeNumericTokens(line) {
|
||||
if (!line) {
|
||||
return line;
|
||||
}
|
||||
const isDigit = (char) => /\d/.test(char);
|
||||
const isLetter = (char) => /[A-Za-zА-Яа-яЁё]/.test(char);
|
||||
const dateLikePunctuation = new Set([".", "-", "/", ":"]);
|
||||
const chunks = line.split(/(`[^`]*`)/g);
|
||||
return chunks
|
||||
.map((chunk, index) => {
|
||||
@@ -123,9 +126,23 @@ function emphasizeNumericTokens(line) {
|
||||
return chunk.replace(/\b-?(?:\d{1,3}(?:[.\s]\d{3})+|\d+)(?:[.,]\d+)?\b/g, (match, offset, source) => {
|
||||
const before = offset > 0 ? source[offset - 1] : "";
|
||||
const after = offset + match.length < source.length ? source[offset + match.length] : "";
|
||||
const before2 = offset > 1 ? source[offset - 2] : "";
|
||||
const after2 = offset + match.length + 1 < source.length ? source[offset + match.length + 1] : "";
|
||||
if (before === "*" || after === "*") {
|
||||
return match;
|
||||
}
|
||||
if (isLetter(before) || isLetter(after)) {
|
||||
return match;
|
||||
}
|
||||
if (offset === 0 && (after === "." || after === ")")) {
|
||||
return match;
|
||||
}
|
||||
if (dateLikePunctuation.has(before) && isDigit(before2)) {
|
||||
return match;
|
||||
}
|
||||
if (dateLikePunctuation.has(after) && isDigit(after2)) {
|
||||
return match;
|
||||
}
|
||||
return `**${match}**`;
|
||||
});
|
||||
})
|
||||
@@ -1997,11 +2014,24 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
];
|
||||
if (vatProbe && vatProbe.status === "ok") {
|
||||
const nonEmptySources = vatProbe.probedSources.filter((item) => item.status === "ok").length;
|
||||
lines.push("", "Покрытие VAT-источников через MCP:", `- Найдено VAT-объектов: ${formatNumberWithDots(vatProbe.objectsTotal)} (документы: ${formatNumberWithDots(vatProbe.documentsTotal)}, регистры: ${formatNumberWithDots(vatProbe.registersTotal)}).`, `- Прямых источников проверено: ${formatNumberWithDots(vatProbe.probedSources.length)}.`, `- Источников с движениями до даты среза: ${formatNumberWithDots(nonEmptySources)}.`);
|
||||
if (vatProbe.probedSources.length > 0) {
|
||||
lines.push(...vatProbe.probedSources.slice(0, 6).map((item, index) => {
|
||||
const statusRank = (status) => status === "ok" ? 0 : status === "empty" ? 1 : 2;
|
||||
const orderedProbeRows = [...vatProbe.probedSources].sort((a, b) => statusRank(a.status) - statusRank(b.status) ||
|
||||
a.fullName.localeCompare(b.fullName, "ru"));
|
||||
const nonErrorProbeRows = orderedProbeRows.filter((item) => item.status !== "error");
|
||||
const visibleProbeRows = (nonErrorProbeRows.length > 0 ? nonErrorProbeRows : orderedProbeRows).slice(0, 6);
|
||||
const erroredSources = vatProbe.probedSources.filter((item) => item.status === "error").length;
|
||||
lines.push("", "Покрытие VAT-источников через MCP:", `- Найдено VAT-объектов: ${formatNumberWithDots(vatProbe.objectsTotal)} (документы: ${formatNumberWithDots(vatProbe.documentsTotal)}, регистры: ${formatNumberWithDots(vatProbe.registersTotal)}).`, `- Прямых источников проверено: ${formatNumberWithDots(vatProbe.probedSources.length)}.`, `- Источников с движениями до даты среза: ${formatNumberWithDots(nonEmptySources)}.`, `- Источников с ошибкой запроса: ${formatNumberWithDots(erroredSources)}.`);
|
||||
if (visibleProbeRows.length > 0) {
|
||||
lines.push(...visibleProbeRows.map((item, index) => {
|
||||
const name = item.synonym ? `${item.fullName} (${item.synonym})` : item.fullName;
|
||||
return `${index + 1}. ${name} | ${formatVatProbeStatusRu(item.status)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}`;
|
||||
const extra = item.status === "ok"
|
||||
? item.lastPeriod
|
||||
? ` | последнее движение: ${item.lastPeriod}`
|
||||
: ""
|
||||
: item.status === "error" && item.error
|
||||
? ` | ошибка: ${item.error}`
|
||||
: "";
|
||||
return `${index + 1}. ${name} | ${formatVatProbeStatusRu(item.status)}${extra}`;
|
||||
}));
|
||||
}
|
||||
if (vatProbe.errors.length > 0) {
|
||||
@@ -2044,6 +2074,61 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
text: joinLines(lines)
|
||||
};
|
||||
}
|
||||
if (intent === "vat_liability_confirmed_for_tax_period") {
|
||||
const rowsByMarker = new Map();
|
||||
for (const row of rows) {
|
||||
const marker = String(row.registrator ?? "").trim().toUpperCase();
|
||||
if (!marker) {
|
||||
continue;
|
||||
}
|
||||
const nextValue = (rowsByMarker.get(marker) ?? 0) + (row.amount ?? 0);
|
||||
rowsByMarker.set(marker, nextValue);
|
||||
}
|
||||
const salesVat = rowsByMarker.get("VAT_BOOK_SALES") ?? 0;
|
||||
const purchaseVat = rowsByMarker.get("VAT_BOOK_PURCHASES") ?? 0;
|
||||
const netVat = salesVat - purchaseVat;
|
||||
const vatToPay = Math.max(0, netVat);
|
||||
const carryoverOrOverpayment = Math.max(0, -netVat);
|
||||
const periodWindowLabel = options.periodFrom && options.periodTo ? `${formatDateRu(options.periodFrom)}..${formatDateRu(options.periodTo)}` : null;
|
||||
const formatConfirmedMoney = (value) => (options.useRubCurrency ? formatMoneyRub(value) : formatMoney(value));
|
||||
const vatProbe = options.vatDirectSourceProbe ?? null;
|
||||
const lines = [
|
||||
`Собран подтвержденный расчет НДС к уплате за налоговый период: ${formatConfirmedMoney(vatToPay)}.`,
|
||||
`Налоговый период расчета: ${periodWindowLabel ?? "не задан (нужен явный период)"}.`,
|
||||
`Потенциальный перенос/переплата: ${formatConfirmedMoney(carryoverOrOverpayment)}.`,
|
||||
"Режим результата: подтвержденный расчет по регистрам книг продаж/покупок (tax-period mode, без surrogate-формулы 68/19).",
|
||||
"",
|
||||
"База расчета:",
|
||||
`- Строк агрегата: ${formatNumberWithDots(rows.length)}.`,
|
||||
`- НДС по книге продаж: ${formatConfirmedMoney(salesVat)}.`,
|
||||
`- НДС по книге покупок (вычеты): ${formatConfirmedMoney(purchaseVat)}.`,
|
||||
`- Нетто НДС (книга продаж - книга покупок): ${formatConfirmedMoney(netVat)}.`
|
||||
];
|
||||
if (vatProbe && vatProbe.status === "ok") {
|
||||
const nonEmptySources = vatProbe.probedSources.filter((item) => item.status === "ok").length;
|
||||
const erroredSources = vatProbe.probedSources.filter((item) => item.status === "error").length;
|
||||
lines.push("", "Покрытие VAT-источников через MCP:", `- Найдено VAT-объектов: ${formatNumberWithDots(vatProbe.objectsTotal)} (документы: ${formatNumberWithDots(vatProbe.documentsTotal)}, регистры: ${formatNumberWithDots(vatProbe.registersTotal)}).`, `- Прямых источников проверено: ${formatNumberWithDots(vatProbe.probedSources.length)}.`, `- Источников с движениями до даты среза: ${formatNumberWithDots(nonEmptySources)}.`, `- Источников с ошибкой запроса: ${formatNumberWithDots(erroredSources)}.`);
|
||||
if (vatProbe.errors.length > 0) {
|
||||
lines.push(`- Ограничения probe: ${vatProbe.errors.slice(0, 2).join("; ")}.`);
|
||||
}
|
||||
lines.push("- Сумма расчета выше получена по книгам продаж/покупок; probe использован для контроля полноты VAT-источников.");
|
||||
}
|
||||
else if (vatProbe && vatProbe.status === "error") {
|
||||
lines.push("", "Покрытие VAT-источников через MCP: probe завершился ошибкой, проверьте доступность регистров книг продаж/покупок.");
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
lines.push("", "За выбранный налоговый период не найдены строки книг продаж/покупок, поэтому подтвержденная сумма к уплате равна 0.");
|
||||
}
|
||||
return {
|
||||
responseType: "FACTUAL_SUMMARY",
|
||||
text: joinLines(lines),
|
||||
semantics: {
|
||||
result_mode: "confirmed_balance",
|
||||
evidence_strength: "strong",
|
||||
balance_confirmed: true
|
||||
}
|
||||
};
|
||||
}
|
||||
if (intent === "vat_payable_confirmed_as_of_date") {
|
||||
const asOfDate = resolvePayablesAsOfDate(options);
|
||||
const confirmedRows = rows.filter((row) => {
|
||||
|
||||
@@ -36,6 +36,9 @@ function hasVatCue(text) {
|
||||
function hasVatForecastCue(text) {
|
||||
return /(?:прогноз|forecast|прикин|оцен|план)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function hasVatTaxPaymentCue(text) {
|
||||
return /(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|в\s+налогов|в\s+бюджет)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function hasDocumentSignal(text) {
|
||||
return /(?:док(?:и|умент|ументы|ументов|ументами)|docs?|documents?|doki|docy|doci)/iu.test(String(text ?? ""));
|
||||
}
|
||||
@@ -432,7 +435,10 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
const hasExplicitPeriodInMessage = hasExplicitPeriodLiteral(userMessage);
|
||||
const currentHasPeriod = hasExplicitPeriodWindow(merged);
|
||||
const previousHasPeriod = hasExplicitPeriodWindow(previous);
|
||||
if (intent === "vat_payable_forecast" && previousHasPeriod && hasFollowupSignal && !hasExplicitPeriodInMessage) {
|
||||
if ((intent === "vat_payable_forecast" || intent === "vat_liability_confirmed_for_tax_period") &&
|
||||
previousHasPeriod &&
|
||||
hasFollowupSignal &&
|
||||
!hasExplicitPeriodInMessage) {
|
||||
const currentPeriodFrom = toNonEmptyString(merged.period_from);
|
||||
const currentPeriodTo = toNonEmptyString(merged.period_to);
|
||||
const todayIso = new Date().toISOString().slice(0, 10);
|
||||
@@ -465,6 +471,7 @@ function resolveMissingRequiredFilters(intent, filters) {
|
||||
payables_confirmed_as_of_date: ["as_of_date"],
|
||||
receivables_confirmed_as_of_date: ["as_of_date"],
|
||||
vat_payable_confirmed_as_of_date: ["as_of_date"],
|
||||
vat_liability_confirmed_for_tax_period: ["period_from", "period_to"],
|
||||
list_documents_by_counterparty: ["counterparty"],
|
||||
bank_operations_by_counterparty: ["counterparty"],
|
||||
list_contracts_by_counterparty: ["counterparty"],
|
||||
@@ -497,9 +504,11 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
|
||||
const hasAnyPartyAnchor = hasPreviousContract || hasPreviousCounterparty;
|
||||
const isVatFollowup = hasVatCue(normalizedMessage);
|
||||
if (detectedIntent.intent === "unknown" && isVatFollowup) {
|
||||
const vatIntent = hasVatForecastCue(normalizedMessage)
|
||||
? "vat_payable_forecast"
|
||||
: "vat_payable_confirmed_as_of_date";
|
||||
const vatIntent = hasVatTaxPaymentCue(normalizedMessage)
|
||||
? "vat_liability_confirmed_for_tax_period"
|
||||
: hasVatForecastCue(normalizedMessage)
|
||||
? "vat_payable_forecast"
|
||||
: "vat_payable_confirmed_as_of_date";
|
||||
return {
|
||||
intent: vatIntent,
|
||||
confidence: "low",
|
||||
|
||||
+2
-1
@@ -95,7 +95,8 @@ function inferAggregationProfile(intent, shape) {
|
||||
intent === "documents_forming_balance" ||
|
||||
intent === "payables_confirmed_as_of_date" ||
|
||||
intent === "receivables_confirmed_as_of_date" ||
|
||||
intent === "vat_payable_confirmed_as_of_date") {
|
||||
intent === "vat_payable_confirmed_as_of_date" ||
|
||||
intent === "vat_liability_confirmed_for_tax_period") {
|
||||
return "balance_snapshot";
|
||||
}
|
||||
if (intent === "open_items_by_counterparty_or_contract" ||
|
||||
|
||||
Reference in New Issue
Block a user