ДОМЕНЫ - ВОПРОСЫ - НДС: Стабилизировать VAT MCP probe: добавить задержки, увеличить таймауты и retry на aborted
This commit is contained in:
parent
c4f87222a8
commit
4205c6b3e6
|
|
@ -16,15 +16,20 @@ const ADDRESS_CONFIRMED_PAYABLES_MIN_LIMIT = 200;
|
||||||
const COUNTERPARTY_CATALOG_LOOKUP_LIMIT = 1000;
|
const COUNTERPARTY_CATALOG_LOOKUP_LIMIT = 1000;
|
||||||
const COUNTERPARTY_CATALOG_CACHE_TTL_MS = 120_000;
|
const COUNTERPARTY_CATALOG_CACHE_TTL_MS = 120_000;
|
||||||
const VAT_METADATA_PROBE_LIMIT = 100;
|
const VAT_METADATA_PROBE_LIMIT = 100;
|
||||||
const VAT_SOURCE_PROBE_MAX_OBJECTS = 6;
|
const VAT_SOURCE_PROBE_MAX_OBJECTS = 4;
|
||||||
const VAT_METADATA_PROBE_TYPES = ["РегистрНакопления", "Документ"];
|
const VAT_METADATA_PROBE_TYPES = ["РегистрНакопления"];
|
||||||
const VAT_METADATA_PROBE_MASKS = ["ндс", "книгапродаж", "книгапокупок", "счетфактур"];
|
const VAT_METADATA_PROBE_MASKS = ["ндс", "книгапродаж", "книгапокупок", "счетфактур"];
|
||||||
const VAT_METADATA_PROBE_CONCURRENCY = 4;
|
const VAT_METADATA_PROBE_CONCURRENCY = 2;
|
||||||
const VAT_METADATA_PROBE_TIMEOUT_MS = 800;
|
const VAT_METADATA_PROBE_STAGGER_MS = 90;
|
||||||
const VAT_METADATA_PROBE_RETRY_TIMEOUT_MS = 1_200;
|
const VAT_METADATA_PROBE_TIMEOUT_MS = 1_200;
|
||||||
const VAT_OBJECT_PROBE_CONCURRENCY = 4;
|
const VAT_METADATA_PROBE_RETRY_TIMEOUT_MS = 1_800;
|
||||||
const VAT_OBJECT_PROBE_TIMEOUT_MS = 800;
|
const VAT_METADATA_PROBE_RETRY_DELAY_MS = 140;
|
||||||
const VAT_OBJECT_PROBE_FALLBACK_TIMEOUT_MS = 800;
|
const VAT_OBJECT_PROBE_CONCURRENCY = 1;
|
||||||
|
const VAT_OBJECT_PROBE_STAGGER_MS = 120;
|
||||||
|
const VAT_OBJECT_PROBE_TIMEOUT_MS = 1_500;
|
||||||
|
const VAT_OBJECT_PROBE_ABORT_RETRY_TIMEOUT_MS = 2_200;
|
||||||
|
const VAT_OBJECT_PROBE_ABORT_RETRY_DELAY_MS = 180;
|
||||||
|
const VAT_OBJECT_PROBE_FALLBACK_TIMEOUT_MS = 1_500;
|
||||||
const PARTY_ANCHOR_STOPWORDS = new Set([
|
const PARTY_ANCHOR_STOPWORDS = new Set([
|
||||||
"ооо",
|
"ооо",
|
||||||
"ао",
|
"ао",
|
||||||
|
|
@ -75,6 +80,28 @@ const ACCOUNT_ALIAS_MAP = {
|
||||||
"62": ["покупатель", "покупателями", "расчеты с покупателями"],
|
"62": ["покупатель", "покупателями", "расчеты с покупателями"],
|
||||||
"76": ["прочие расчеты", "прочими дебиторами и кредиторами"]
|
"76": ["прочие расчеты", "прочими дебиторами и кредиторами"]
|
||||||
};
|
};
|
||||||
|
const VAT_FALLBACK_METADATA_OBJECTS = [
|
||||||
|
{
|
||||||
|
fullName: "РегистрНакопления.НДСЗаписиКнигиПродаж",
|
||||||
|
synonym: "НДС Продажи",
|
||||||
|
objectType: "register"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fullName: "РегистрНакопления.НДСЗаписиКнигиПокупок",
|
||||||
|
synonym: "НДС Покупки",
|
||||||
|
objectType: "register"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fullName: "РегистрНакопления.НДСПредъявленный",
|
||||||
|
synonym: "НДС предъявленный",
|
||||||
|
objectType: "register"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fullName: "РегистрНакопления.НДСВключенныйВСтоимость",
|
||||||
|
synonym: "НДС, включенный в стоимость",
|
||||||
|
objectType: "register"
|
||||||
|
}
|
||||||
|
];
|
||||||
const COUNTERPARTY_CATALOG_LOOKUP_QUERY_TEMPLATE = `
|
const COUNTERPARTY_CATALOG_LOOKUP_QUERY_TEMPLATE = `
|
||||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||||
ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период,
|
ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период,
|
||||||
|
|
@ -252,6 +279,13 @@ async function mapWithConcurrency(items, concurrency, worker) {
|
||||||
await Promise.all(runners);
|
await Promise.all(runners);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
function sleepMs(ms) {
|
||||||
|
const delayMs = Number.isFinite(ms) ? Math.max(0, Math.trunc(ms)) : 0;
|
||||||
|
if (delayMs <= 0) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||||
|
}
|
||||||
async function executeVatMetadataProbeRequest(request) {
|
async function executeVatMetadataProbeRequest(request) {
|
||||||
const firstAttempt = await (0, addressMcpClient_1.executeAddressMcpMetadata)({
|
const firstAttempt = await (0, addressMcpClient_1.executeAddressMcpMetadata)({
|
||||||
...request,
|
...request,
|
||||||
|
|
@ -260,6 +294,7 @@ async function executeVatMetadataProbeRequest(request) {
|
||||||
if (!firstAttempt.error || !isAbortErrorMessage(firstAttempt.error)) {
|
if (!firstAttempt.error || !isAbortErrorMessage(firstAttempt.error)) {
|
||||||
return firstAttempt;
|
return firstAttempt;
|
||||||
}
|
}
|
||||||
|
await sleepMs(VAT_METADATA_PROBE_RETRY_DELAY_MS);
|
||||||
const retryLimit = Math.max(20, Math.min(request.limit, Math.trunc(request.limit / 2)));
|
const retryLimit = Math.max(20, Math.min(request.limit, Math.trunc(request.limit / 2)));
|
||||||
const retryAttempt = await (0, addressMcpClient_1.executeAddressMcpMetadata)({
|
const retryAttempt = await (0, addressMcpClient_1.executeAddressMcpMetadata)({
|
||||||
...request,
|
...request,
|
||||||
|
|
@ -301,6 +336,10 @@ function scoreVatMetadataObject(item) {
|
||||||
}
|
}
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
function vatMetadataObjectPriority(item) {
|
||||||
|
const idx = VAT_FALLBACK_METADATA_OBJECTS.findIndex((fallback) => fallback.fullName === item.fullName);
|
||||||
|
return idx >= 0 ? idx : VAT_FALLBACK_METADATA_OBJECTS.length + 1;
|
||||||
|
}
|
||||||
function buildVatObjectProbeQuery(object, asOfExpr, mode = "latest") {
|
function buildVatObjectProbeQuery(object, asOfExpr, mode = "latest") {
|
||||||
const orderClause = mode === "latest" ? "\nУПОРЯДОЧИТЬ ПО\n Движения.Период УБЫВ" : "";
|
const orderClause = mode === "latest" ? "\nУПОРЯДОЧИТЬ ПО\n Движения.Период УБЫВ" : "";
|
||||||
if (object.objectType === "document") {
|
if (object.objectType === "document") {
|
||||||
|
|
@ -363,7 +402,12 @@ async function probeVatDirectSources(filters) {
|
||||||
name_mask: nameMask,
|
name_mask: nameMask,
|
||||||
limit: VAT_METADATA_PROBE_LIMIT
|
limit: VAT_METADATA_PROBE_LIMIT
|
||||||
})));
|
})));
|
||||||
const metadataResponses = await mapWithConcurrency(metadataRequests, VAT_METADATA_PROBE_CONCURRENCY, (request) => executeVatMetadataProbeRequest(request));
|
const metadataResponses = await mapWithConcurrency(metadataRequests, VAT_METADATA_PROBE_CONCURRENCY, async (request, index) => {
|
||||||
|
if (index > 0 && VAT_METADATA_PROBE_STAGGER_MS > 0) {
|
||||||
|
await sleepMs(index * VAT_METADATA_PROBE_STAGGER_MS);
|
||||||
|
}
|
||||||
|
return executeVatMetadataProbeRequest(request);
|
||||||
|
});
|
||||||
const metadataOutcomes = metadataResponses.map((response, index) => ({
|
const metadataOutcomes = metadataResponses.map((response, index) => ({
|
||||||
request: metadataRequests[index],
|
request: metadataRequests[index],
|
||||||
response
|
response
|
||||||
|
|
@ -403,9 +447,27 @@ async function probeVatDirectSources(filters) {
|
||||||
}
|
}
|
||||||
const discoveredMetadataObjects = Array.from(deduplicatedObjects.values())
|
const discoveredMetadataObjects = Array.from(deduplicatedObjects.values())
|
||||||
.filter((item) => isVatMetadataObject(item))
|
.filter((item) => isVatMetadataObject(item))
|
||||||
.sort((a, b) => scoreVatMetadataObject(b) - scoreVatMetadataObject(a) || a.fullName.localeCompare(b.fullName, "ru"));
|
.sort((a, b) => vatMetadataObjectPriority(a) - vatMetadataObjectPriority(b) ||
|
||||||
const metadataObjects = discoveredMetadataObjects.slice(0, VAT_SOURCE_PROBE_MAX_OBJECTS);
|
scoreVatMetadataObject(b) - scoreVatMetadataObject(a) ||
|
||||||
const probeRows = await mapWithConcurrency(metadataObjects, VAT_OBJECT_PROBE_CONCURRENCY, async (object) => {
|
a.fullName.localeCompare(b.fullName, "ru"));
|
||||||
|
const mergedMetadataObjectsMap = new Map();
|
||||||
|
for (const item of discoveredMetadataObjects) {
|
||||||
|
mergedMetadataObjectsMap.set(item.fullName, item);
|
||||||
|
}
|
||||||
|
for (const fallbackObject of VAT_FALLBACK_METADATA_OBJECTS) {
|
||||||
|
if (!mergedMetadataObjectsMap.has(fallbackObject.fullName)) {
|
||||||
|
mergedMetadataObjectsMap.set(fallbackObject.fullName, fallbackObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const metadataObjects = Array.from(mergedMetadataObjectsMap.values())
|
||||||
|
.sort((a, b) => vatMetadataObjectPriority(a) - vatMetadataObjectPriority(b) ||
|
||||||
|
scoreVatMetadataObject(b) - scoreVatMetadataObject(a) ||
|
||||||
|
a.fullName.localeCompare(b.fullName, "ru"))
|
||||||
|
.slice(0, VAT_SOURCE_PROBE_MAX_OBJECTS);
|
||||||
|
const probeRows = await mapWithConcurrency(metadataObjects, VAT_OBJECT_PROBE_CONCURRENCY, async (object, index) => {
|
||||||
|
if (index > 0 && VAT_OBJECT_PROBE_STAGGER_MS > 0) {
|
||||||
|
await sleepMs(index * VAT_OBJECT_PROBE_STAGGER_MS);
|
||||||
|
}
|
||||||
let probeResult = await (0, addressMcpClient_1.executeAddressMcpQuery)({
|
let probeResult = await (0, addressMcpClient_1.executeAddressMcpQuery)({
|
||||||
query: buildVatObjectProbeQuery(object, asOfExpr, "latest"),
|
query: buildVatObjectProbeQuery(object, asOfExpr, "latest"),
|
||||||
limit: 1,
|
limit: 1,
|
||||||
|
|
@ -413,34 +475,43 @@ async function probeVatDirectSources(filters) {
|
||||||
});
|
});
|
||||||
let fallbackUsed = false;
|
let fallbackUsed = false;
|
||||||
if (probeResult.error) {
|
if (probeResult.error) {
|
||||||
|
let latestError = probeResult.error;
|
||||||
if (isAbortErrorMessage(probeResult.error)) {
|
if (isAbortErrorMessage(probeResult.error)) {
|
||||||
return {
|
await sleepMs(VAT_OBJECT_PROBE_ABORT_RETRY_DELAY_MS);
|
||||||
fullName: object.fullName,
|
const retryLatestResult = await (0, addressMcpClient_1.executeAddressMcpQuery)({
|
||||||
synonym: object.synonym,
|
query: buildVatObjectProbeQuery(object, asOfExpr, "latest"),
|
||||||
objectType: object.objectType,
|
limit: 1,
|
||||||
status: "error",
|
timeout_ms: VAT_OBJECT_PROBE_ABORT_RETRY_TIMEOUT_MS
|
||||||
rowsFetched: probeResult.fetched_rows,
|
});
|
||||||
error: probeResult.error
|
if (!retryLatestResult.error) {
|
||||||
};
|
probeResult = retryLatestResult;
|
||||||
|
latestError = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
probeResult = retryLatestResult;
|
||||||
|
latestError = `${latestError}; retry_latest: ${retryLatestResult.error}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const fallbackResult = await (0, addressMcpClient_1.executeAddressMcpQuery)({
|
if (probeResult.error) {
|
||||||
query: buildVatObjectProbeQuery(object, asOfExpr, "exists"),
|
const fallbackResult = await (0, addressMcpClient_1.executeAddressMcpQuery)({
|
||||||
limit: 1,
|
query: buildVatObjectProbeQuery(object, asOfExpr, "exists"),
|
||||||
timeout_ms: VAT_OBJECT_PROBE_FALLBACK_TIMEOUT_MS
|
limit: 1,
|
||||||
});
|
timeout_ms: VAT_OBJECT_PROBE_FALLBACK_TIMEOUT_MS
|
||||||
if (!fallbackResult.error) {
|
});
|
||||||
probeResult = fallbackResult;
|
if (!fallbackResult.error) {
|
||||||
fallbackUsed = true;
|
probeResult = fallbackResult;
|
||||||
}
|
fallbackUsed = true;
|
||||||
else {
|
}
|
||||||
return {
|
else {
|
||||||
fullName: object.fullName,
|
return {
|
||||||
synonym: object.synonym,
|
fullName: object.fullName,
|
||||||
objectType: object.objectType,
|
synonym: object.synonym,
|
||||||
status: "error",
|
objectType: object.objectType,
|
||||||
rowsFetched: probeResult.fetched_rows,
|
status: "error",
|
||||||
error: `${probeResult.error}; fallback: ${fallbackResult.error}`
|
rowsFetched: probeResult.fetched_rows,
|
||||||
};
|
error: `${latestError ?? probeResult.error}; fallback: ${fallbackResult.error}`
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const firstRow = probeResult.raw_rows[0] ?? null;
|
const firstRow = probeResult.raw_rows[0] ?? null;
|
||||||
|
|
@ -462,7 +533,13 @@ async function probeVatDirectSources(filters) {
|
||||||
sampleRegistrator
|
sampleRegistrator
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const status = metadataResponses.every((item) => item.error) ? "error" : "ok";
|
const hasProbeAttempts = probeRows.length > 0;
|
||||||
|
const hasNonErrorProbeResult = probeRows.some((item) => item.status !== "error");
|
||||||
|
const status = hasProbeAttempts && hasNonErrorProbeResult
|
||||||
|
? "ok"
|
||||||
|
: metadataResponses.every((item) => item.error) && !hasProbeAttempts
|
||||||
|
? "error"
|
||||||
|
: "ok";
|
||||||
const allErrors = [
|
const allErrors = [
|
||||||
...metadataErrors,
|
...metadataErrors,
|
||||||
...probeRows
|
...probeRows
|
||||||
|
|
|
||||||
|
|
@ -2114,7 +2114,10 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||||
lines.push("- Сумма расчета выше получена по книгам продаж/покупок; probe использован для контроля полноты VAT-источников.");
|
lines.push("- Сумма расчета выше получена по книгам продаж/покупок; probe использован для контроля полноты VAT-источников.");
|
||||||
}
|
}
|
||||||
else if (vatProbe && vatProbe.status === "error") {
|
else if (vatProbe && vatProbe.status === "error") {
|
||||||
lines.push("", "Покрытие VAT-источников через MCP: probe завершился ошибкой, проверьте доступность регистров книг продаж/покупок.");
|
lines.push("", "Покрытие VAT-источников через MCP: дополнительный probe недоступен (например, timeout metadata).", "Итоговая сумма НДС выше рассчитана по основному маршруту книг продаж/покупок; probe влияет только на диагностику покрытия.");
|
||||||
|
if (vatProbe.errors.length > 0) {
|
||||||
|
lines.push(`- Детали probe: ${vatProbe.errors.slice(0, 2).join("; ")}.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
lines.push("", "За выбранный налоговый период не найдены строки книг продаж/покупок, поэтому подтвержденная сумма к уплате равна 0.");
|
lines.push("", "За выбранный налоговый период не найдены строки книг продаж/покупок, поэтому подтвержденная сумма к уплате равна 0.");
|
||||||
|
|
|
||||||
|
|
@ -91,15 +91,20 @@ const ADDRESS_CONFIRMED_PAYABLES_MIN_LIMIT = 200;
|
||||||
const COUNTERPARTY_CATALOG_LOOKUP_LIMIT = 1000;
|
const COUNTERPARTY_CATALOG_LOOKUP_LIMIT = 1000;
|
||||||
const COUNTERPARTY_CATALOG_CACHE_TTL_MS = 120_000;
|
const COUNTERPARTY_CATALOG_CACHE_TTL_MS = 120_000;
|
||||||
const VAT_METADATA_PROBE_LIMIT = 100;
|
const VAT_METADATA_PROBE_LIMIT = 100;
|
||||||
const VAT_SOURCE_PROBE_MAX_OBJECTS = 6;
|
const VAT_SOURCE_PROBE_MAX_OBJECTS = 4;
|
||||||
const VAT_METADATA_PROBE_TYPES = ["РегистрНакопления", "Документ"] as const;
|
const VAT_METADATA_PROBE_TYPES = ["РегистрНакопления"] as const;
|
||||||
const VAT_METADATA_PROBE_MASKS = ["ндс", "книгапродаж", "книгапокупок", "счетфактур"] as const;
|
const VAT_METADATA_PROBE_MASKS = ["ндс", "книгапродаж", "книгапокупок", "счетфактур"] as const;
|
||||||
const VAT_METADATA_PROBE_CONCURRENCY = 4;
|
const VAT_METADATA_PROBE_CONCURRENCY = 2;
|
||||||
const VAT_METADATA_PROBE_TIMEOUT_MS = 800;
|
const VAT_METADATA_PROBE_STAGGER_MS = 90;
|
||||||
const VAT_METADATA_PROBE_RETRY_TIMEOUT_MS = 1_200;
|
const VAT_METADATA_PROBE_TIMEOUT_MS = 1_200;
|
||||||
const VAT_OBJECT_PROBE_CONCURRENCY = 4;
|
const VAT_METADATA_PROBE_RETRY_TIMEOUT_MS = 1_800;
|
||||||
const VAT_OBJECT_PROBE_TIMEOUT_MS = 800;
|
const VAT_METADATA_PROBE_RETRY_DELAY_MS = 140;
|
||||||
const VAT_OBJECT_PROBE_FALLBACK_TIMEOUT_MS = 800;
|
const VAT_OBJECT_PROBE_CONCURRENCY = 1;
|
||||||
|
const VAT_OBJECT_PROBE_STAGGER_MS = 120;
|
||||||
|
const VAT_OBJECT_PROBE_TIMEOUT_MS = 1_500;
|
||||||
|
const VAT_OBJECT_PROBE_ABORT_RETRY_TIMEOUT_MS = 2_200;
|
||||||
|
const VAT_OBJECT_PROBE_ABORT_RETRY_DELAY_MS = 180;
|
||||||
|
const VAT_OBJECT_PROBE_FALLBACK_TIMEOUT_MS = 1_500;
|
||||||
const PARTY_ANCHOR_STOPWORDS = new Set([
|
const PARTY_ANCHOR_STOPWORDS = new Set([
|
||||||
"ооо",
|
"ооо",
|
||||||
"ао",
|
"ао",
|
||||||
|
|
@ -156,6 +161,28 @@ interface VatMetadataObject {
|
||||||
synonym: string | null;
|
synonym: string | null;
|
||||||
objectType: "document" | "register";
|
objectType: "document" | "register";
|
||||||
}
|
}
|
||||||
|
const VAT_FALLBACK_METADATA_OBJECTS: VatMetadataObject[] = [
|
||||||
|
{
|
||||||
|
fullName: "РегистрНакопления.НДСЗаписиКнигиПродаж",
|
||||||
|
synonym: "НДС Продажи",
|
||||||
|
objectType: "register"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fullName: "РегистрНакопления.НДСЗаписиКнигиПокупок",
|
||||||
|
synonym: "НДС Покупки",
|
||||||
|
objectType: "register"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fullName: "РегистрНакопления.НДСПредъявленный",
|
||||||
|
synonym: "НДС предъявленный",
|
||||||
|
objectType: "register"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fullName: "РегистрНакопления.НДСВключенныйВСтоимость",
|
||||||
|
synonym: "НДС, включенный в стоимость",
|
||||||
|
objectType: "register"
|
||||||
|
}
|
||||||
|
];
|
||||||
const COUNTERPARTY_CATALOG_LOOKUP_QUERY_TEMPLATE = `
|
const COUNTERPARTY_CATALOG_LOOKUP_QUERY_TEMPLATE = `
|
||||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||||
ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период,
|
ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период,
|
||||||
|
|
@ -363,6 +390,14 @@ async function mapWithConcurrency<T, R>(
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sleepMs(ms: number): Promise<void> {
|
||||||
|
const delayMs = Number.isFinite(ms) ? Math.max(0, Math.trunc(ms)) : 0;
|
||||||
|
if (delayMs <= 0) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||||
|
}
|
||||||
|
|
||||||
async function executeVatMetadataProbeRequest(request: {
|
async function executeVatMetadataProbeRequest(request: {
|
||||||
meta_type: string;
|
meta_type: string;
|
||||||
name_mask: string;
|
name_mask: string;
|
||||||
|
|
@ -376,6 +411,7 @@ async function executeVatMetadataProbeRequest(request: {
|
||||||
return firstAttempt;
|
return firstAttempt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await sleepMs(VAT_METADATA_PROBE_RETRY_DELAY_MS);
|
||||||
const retryLimit = Math.max(20, Math.min(request.limit, Math.trunc(request.limit / 2)));
|
const retryLimit = Math.max(20, Math.min(request.limit, Math.trunc(request.limit / 2)));
|
||||||
const retryAttempt = await executeAddressMcpMetadata({
|
const retryAttempt = await executeAddressMcpMetadata({
|
||||||
...request,
|
...request,
|
||||||
|
|
@ -420,6 +456,11 @@ function scoreVatMetadataObject(item: VatMetadataObject): number {
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vatMetadataObjectPriority(item: VatMetadataObject): number {
|
||||||
|
const idx = VAT_FALLBACK_METADATA_OBJECTS.findIndex((fallback) => fallback.fullName === item.fullName);
|
||||||
|
return idx >= 0 ? idx : VAT_FALLBACK_METADATA_OBJECTS.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
type VatObjectProbeMode = "latest" | "exists";
|
type VatObjectProbeMode = "latest" | "exists";
|
||||||
|
|
||||||
function buildVatObjectProbeQuery(object: VatMetadataObject, asOfExpr: string, mode: VatObjectProbeMode = "latest"): string {
|
function buildVatObjectProbeQuery(object: VatMetadataObject, asOfExpr: string, mode: VatObjectProbeMode = "latest"): string {
|
||||||
|
|
@ -494,7 +535,12 @@ async function probeVatDirectSources(filters: AddressFilterSet): Promise<VatDire
|
||||||
const metadataResponses = await mapWithConcurrency(
|
const metadataResponses = await mapWithConcurrency(
|
||||||
metadataRequests,
|
metadataRequests,
|
||||||
VAT_METADATA_PROBE_CONCURRENCY,
|
VAT_METADATA_PROBE_CONCURRENCY,
|
||||||
(request) => executeVatMetadataProbeRequest(request)
|
async (request, index) => {
|
||||||
|
if (index > 0 && VAT_METADATA_PROBE_STAGGER_MS > 0) {
|
||||||
|
await sleepMs(index * VAT_METADATA_PROBE_STAGGER_MS);
|
||||||
|
}
|
||||||
|
return executeVatMetadataProbeRequest(request);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const metadataOutcomes = metadataResponses.map((response, index) => ({
|
const metadataOutcomes = metadataResponses.map((response, index) => ({
|
||||||
|
|
@ -539,14 +585,36 @@ async function probeVatDirectSources(filters: AddressFilterSet): Promise<VatDire
|
||||||
const discoveredMetadataObjects = Array.from(deduplicatedObjects.values())
|
const discoveredMetadataObjects = Array.from(deduplicatedObjects.values())
|
||||||
.filter((item) => isVatMetadataObject(item))
|
.filter((item) => isVatMetadataObject(item))
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) => scoreVatMetadataObject(b) - scoreVatMetadataObject(a) || a.fullName.localeCompare(b.fullName, "ru")
|
(a, b) =>
|
||||||
|
vatMetadataObjectPriority(a) - vatMetadataObjectPriority(b) ||
|
||||||
|
scoreVatMetadataObject(b) - scoreVatMetadataObject(a) ||
|
||||||
|
a.fullName.localeCompare(b.fullName, "ru")
|
||||||
);
|
);
|
||||||
const metadataObjects = discoveredMetadataObjects.slice(0, VAT_SOURCE_PROBE_MAX_OBJECTS);
|
const mergedMetadataObjectsMap = new Map<string, VatMetadataObject>();
|
||||||
|
for (const item of discoveredMetadataObjects) {
|
||||||
|
mergedMetadataObjectsMap.set(item.fullName, item);
|
||||||
|
}
|
||||||
|
for (const fallbackObject of VAT_FALLBACK_METADATA_OBJECTS) {
|
||||||
|
if (!mergedMetadataObjectsMap.has(fallbackObject.fullName)) {
|
||||||
|
mergedMetadataObjectsMap.set(fallbackObject.fullName, fallbackObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const metadataObjects = Array.from(mergedMetadataObjectsMap.values())
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
vatMetadataObjectPriority(a) - vatMetadataObjectPriority(b) ||
|
||||||
|
scoreVatMetadataObject(b) - scoreVatMetadataObject(a) ||
|
||||||
|
a.fullName.localeCompare(b.fullName, "ru")
|
||||||
|
)
|
||||||
|
.slice(0, VAT_SOURCE_PROBE_MAX_OBJECTS);
|
||||||
|
|
||||||
const probeRows = await mapWithConcurrency(
|
const probeRows = await mapWithConcurrency(
|
||||||
metadataObjects,
|
metadataObjects,
|
||||||
VAT_OBJECT_PROBE_CONCURRENCY,
|
VAT_OBJECT_PROBE_CONCURRENCY,
|
||||||
async (object): Promise<VatDirectSourceProbeItem> => {
|
async (object, index): Promise<VatDirectSourceProbeItem> => {
|
||||||
|
if (index > 0 && VAT_OBJECT_PROBE_STAGGER_MS > 0) {
|
||||||
|
await sleepMs(index * VAT_OBJECT_PROBE_STAGGER_MS);
|
||||||
|
}
|
||||||
let probeResult = await executeAddressMcpQuery({
|
let probeResult = await executeAddressMcpQuery({
|
||||||
query: buildVatObjectProbeQuery(object, asOfExpr, "latest"),
|
query: buildVatObjectProbeQuery(object, asOfExpr, "latest"),
|
||||||
limit: 1,
|
limit: 1,
|
||||||
|
|
@ -554,33 +622,41 @@ async function probeVatDirectSources(filters: AddressFilterSet): Promise<VatDire
|
||||||
});
|
});
|
||||||
let fallbackUsed = false;
|
let fallbackUsed = false;
|
||||||
if (probeResult.error) {
|
if (probeResult.error) {
|
||||||
|
let latestError: string | null = probeResult.error;
|
||||||
if (isAbortErrorMessage(probeResult.error)) {
|
if (isAbortErrorMessage(probeResult.error)) {
|
||||||
return {
|
await sleepMs(VAT_OBJECT_PROBE_ABORT_RETRY_DELAY_MS);
|
||||||
fullName: object.fullName,
|
const retryLatestResult = await executeAddressMcpQuery({
|
||||||
synonym: object.synonym,
|
query: buildVatObjectProbeQuery(object, asOfExpr, "latest"),
|
||||||
objectType: object.objectType,
|
limit: 1,
|
||||||
status: "error",
|
timeout_ms: VAT_OBJECT_PROBE_ABORT_RETRY_TIMEOUT_MS
|
||||||
rowsFetched: probeResult.fetched_rows,
|
});
|
||||||
error: probeResult.error
|
if (!retryLatestResult.error) {
|
||||||
};
|
probeResult = retryLatestResult;
|
||||||
|
latestError = null;
|
||||||
|
} else {
|
||||||
|
probeResult = retryLatestResult;
|
||||||
|
latestError = `${latestError}; retry_latest: ${retryLatestResult.error}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const fallbackResult = await executeAddressMcpQuery({
|
if (probeResult.error) {
|
||||||
query: buildVatObjectProbeQuery(object, asOfExpr, "exists"),
|
const fallbackResult = await executeAddressMcpQuery({
|
||||||
limit: 1,
|
query: buildVatObjectProbeQuery(object, asOfExpr, "exists"),
|
||||||
timeout_ms: VAT_OBJECT_PROBE_FALLBACK_TIMEOUT_MS
|
limit: 1,
|
||||||
});
|
timeout_ms: VAT_OBJECT_PROBE_FALLBACK_TIMEOUT_MS
|
||||||
if (!fallbackResult.error) {
|
});
|
||||||
probeResult = fallbackResult;
|
if (!fallbackResult.error) {
|
||||||
fallbackUsed = true;
|
probeResult = fallbackResult;
|
||||||
} else {
|
fallbackUsed = true;
|
||||||
return {
|
} else {
|
||||||
fullName: object.fullName,
|
return {
|
||||||
synonym: object.synonym,
|
fullName: object.fullName,
|
||||||
objectType: object.objectType,
|
synonym: object.synonym,
|
||||||
status: "error",
|
objectType: object.objectType,
|
||||||
rowsFetched: probeResult.fetched_rows,
|
status: "error",
|
||||||
error: `${probeResult.error}; fallback: ${fallbackResult.error}`
|
rowsFetched: probeResult.fetched_rows,
|
||||||
};
|
error: `${latestError ?? probeResult.error}; fallback: ${fallbackResult.error}`
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const firstRow = probeResult.raw_rows[0] ?? null;
|
const firstRow = probeResult.raw_rows[0] ?? null;
|
||||||
|
|
@ -610,7 +686,14 @@ async function probeVatDirectSources(filters: AddressFilterSet): Promise<VatDire
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const status: VatDirectSourceProbeSummary["status"] = metadataResponses.every((item) => item.error) ? "error" : "ok";
|
const hasProbeAttempts = probeRows.length > 0;
|
||||||
|
const hasNonErrorProbeResult = probeRows.some((item) => item.status !== "error");
|
||||||
|
const status: VatDirectSourceProbeSummary["status"] =
|
||||||
|
hasProbeAttempts && hasNonErrorProbeResult
|
||||||
|
? "ok"
|
||||||
|
: metadataResponses.every((item) => item.error) && !hasProbeAttempts
|
||||||
|
? "error"
|
||||||
|
: "ok";
|
||||||
const allErrors = [
|
const allErrors = [
|
||||||
...metadataErrors,
|
...metadataErrors,
|
||||||
...probeRows
|
...probeRows
|
||||||
|
|
|
||||||
|
|
@ -2718,7 +2718,14 @@ export function composeFactualReply(
|
||||||
}
|
}
|
||||||
lines.push("- Сумма расчета выше получена по книгам продаж/покупок; probe использован для контроля полноты VAT-источников.");
|
lines.push("- Сумма расчета выше получена по книгам продаж/покупок; probe использован для контроля полноты VAT-источников.");
|
||||||
} else if (vatProbe && vatProbe.status === "error") {
|
} else if (vatProbe && vatProbe.status === "error") {
|
||||||
lines.push("", "Покрытие VAT-источников через MCP: probe завершился ошибкой, проверьте доступность регистров книг продаж/покупок.");
|
lines.push(
|
||||||
|
"",
|
||||||
|
"Покрытие VAT-источников через MCP: дополнительный probe недоступен (например, timeout metadata).",
|
||||||
|
"Итоговая сумма НДС выше рассчитана по основному маршруту книг продаж/покупок; probe влияет только на диагностику покрытия."
|
||||||
|
);
|
||||||
|
if (vatProbe.errors.length > 0) {
|
||||||
|
lines.push(`- Детали probe: ${vatProbe.errors.slice(0, 2).join("; ")}.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue