NODEDC_1C/llm_normalizer/backend/dist/services/addressMcpClient.js

297 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeAddressMcpQuery = executeAddressMcpQuery;
const config_1 = require("../config");
const iconv_lite_1 = __importDefault(require("iconv-lite"));
function toStringValue(value) {
if (value === null || value === undefined) {
return "";
}
return String(value);
}
function parseFiniteNumber(value) {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string") {
const parsed = Number(value.replace(",", ".").trim());
if (Number.isFinite(parsed)) {
return parsed;
}
}
return null;
}
function textMojibakeScore(value) {
const source = String(value ?? "");
const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length;
const latin = (source.match(/[A-Za-z]/g) ?? []).length;
const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ‘’“”•–—™љ›њќћџ]/g) ?? []).length;
const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length;
return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2;
}
function looksLikeMojibake(value) {
const source = String(value ?? "");
if (!source.trim()) {
return false;
}
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ‘’“”•–—™љ›њќћџ]/.test(source)) {
return true;
}
return (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2;
}
function decodeUtf8FromWin1251Mojibake(value) {
if (!looksLikeMojibake(value)) {
return value;
}
try {
const bytes = iconv_lite_1.default.encode(value, "win1251");
const decoded = bytes.toString("utf8");
return textMojibakeScore(decoded) > textMojibakeScore(value) ? decoded : value;
}
catch {
return value;
}
}
function decodeUtf8FromLatin1Mojibake(value) {
if (!looksLikeMojibake(value)) {
return value;
}
try {
const decoded = Buffer.from(value, "latin1").toString("utf8");
return textMojibakeScore(decoded) > textMojibakeScore(value) ? decoded : value;
}
catch {
return value;
}
}
function normalizeMojibakeString(value) {
const fromWin1251 = decodeUtf8FromWin1251Mojibake(value);
return decodeUtf8FromLatin1Mojibake(fromWin1251);
}
function normalizeMojibakeValue(value) {
if (typeof value === "string") {
return normalizeMojibakeString(value);
}
if (Array.isArray(value)) {
return value.map((item) => normalizeMojibakeValue(item));
}
if (value && typeof value === "object") {
const source = value;
const normalized = {};
for (const [key, raw] of Object.entries(source)) {
const repairedKey = normalizeMojibakeString(key);
normalized[repairedKey] = normalizeMojibakeValue(raw);
}
return normalized;
}
return value;
}
function normalizeMojibakeRows(rows) {
return rows.map((row) => normalizeMojibakeValue(row));
}
function parseRowsFromTextTable(source) {
const normalized = normalizeMojibakeString(String(source ?? "")).replace(/\r/g, "").trim();
if (!normalized) {
return [];
}
const headerMatch = normalized.match(/\{([^}]*)\}:/);
if (!headerMatch) {
return [];
}
const columns = String(headerMatch[1] ?? "")
.split(",")
.map((item) => item.replace(/^"+|"+$/g, "").trim())
.filter(Boolean);
const body = normalized.slice((headerMatch.index ?? 0) + headerMatch[0].length).trim();
if (!body) {
return [];
}
const rows = [];
const parseCsvLine = (line) => {
const values = [];
let current = "";
let inQuotes = false;
for (let index = 0; index < line.length; index += 1) {
const char = line[index];
if (char === '"') {
if (inQuotes && line[index + 1] === '"') {
current += '"';
index += 1;
continue;
}
inQuotes = !inQuotes;
continue;
}
if (char === "," && !inQuotes) {
values.push(current.trim());
current = "";
continue;
}
current += char;
}
values.push(current.trim());
return values;
};
const lines = body
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
for (const line of lines) {
const values = parseCsvLine(line);
if (values.length === 0) {
continue;
}
const row = {};
for (let index = 0; index < columns.length; index += 1) {
const key = columns[index] ?? `column_${index + 1}`;
const raw = values[index] ?? "";
const parsed = parseFiniteNumber(raw);
row[key] = parsed ?? raw;
}
if (values[0])
row.Period = values[0];
if (values[1])
row.Registrator = values[1];
if (values[2])
row.AccountDt = values[2];
if (values[3])
row.AccountKt = values[3];
if (values[4])
row.Amount = parseFiniteNumber(values[4]) ?? values[4];
rows.push(row);
}
return normalizeMojibakeRows(rows);
}
function parseExecutePayload(payload) {
if (!payload || typeof payload !== "object") {
return {
ok: false,
rows: [],
error: "MCP payload is empty or malformed"
};
}
const source = payload;
if (source.success !== true) {
return {
ok: false,
rows: [],
error: toStringValue(source.error).trim() || "MCP execute_query returned success=false"
};
}
if (Array.isArray(source.data)) {
const rows = normalizeMojibakeRows(source.data
.map((item) => (item && typeof item === "object" ? item : null))
.filter((item) => item !== null));
return {
ok: true,
rows,
error: null
};
}
if (typeof source.data === "string") {
return {
ok: true,
rows: parseRowsFromTextTable(source.data),
error: null
};
}
if (source.data && typeof source.data === "object" && Array.isArray(source.data.rows)) {
const rows = normalizeMojibakeRows((source.data.rows ?? [])
.map((item) => (item && typeof item === "object" ? item : null))
.filter((item) => item !== null));
return {
ok: true,
rows,
error: null
};
}
return {
ok: true,
rows: [],
error: null
};
}
function buildMcpUrl(endpoint) {
const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
const separator = normalizedEndpoint.includes("?") ? "&" : "?";
return `${config_1.ASSISTANT_MCP_PROXY_URL}${normalizedEndpoint}${separator}channel=${encodeURIComponent(config_1.ASSISTANT_MCP_CHANNEL)}`;
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function filterRowsByAccountScope(rows, accountScope) {
if (accountScope.length === 0) {
return rows;
}
const matchers = accountScope.map((account) => new RegExp(`\\b${escapeRegExp(account)}(?:\\.\\d{1,2})?\\b`, "i"));
return rows.filter((row) => {
const searchable = Object.values(row)
.map((item) => String(item ?? ""))
.join(" ");
return matchers.some((matcher) => matcher.test(searchable));
});
}
async function executeAddressMcpQuery(input) {
const endpoint = buildMcpUrl("/api/execute_query");
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), Math.max(300, config_1.ASSISTANT_MCP_TIMEOUT_MS));
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"content-type": "application/json; charset=utf-8"
},
body: JSON.stringify({
query: input.query,
limit: input.limit
}),
signal: controller.signal
});
const responseText = await response.text();
if (!response.ok) {
return {
fetched_rows: 0,
matched_rows: 0,
raw_rows: [],
rows: [],
error: `MCP HTTP ${response.status}: ${responseText.slice(0, 240)}`
};
}
const payload = responseText.trim() ? JSON.parse(responseText) : {};
const parsed = parseExecutePayload(payload);
if (!parsed.ok) {
return {
fetched_rows: 0,
matched_rows: 0,
raw_rows: [],
rows: [],
error: parsed.error
};
}
const filtered = filterRowsByAccountScope(parsed.rows, Array.isArray(input.account_scope) ? input.account_scope : []);
return {
fetched_rows: parsed.rows.length,
matched_rows: filtered.length,
raw_rows: parsed.rows,
rows: filtered,
error: null
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
fetched_rows: 0,
matched_rows: 0,
raw_rows: [],
rows: [],
error: `MCP fetch failed: ${message}`
};
}
finally {
clearTimeout(timeout);
}
}