АДРЕСНЫЙ РЕЖИМ - локальная подель на декомпозе
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
ASSISTANT_MCP_PROXY_URL,
|
||||
ASSISTANT_MCP_TIMEOUT_MS
|
||||
} from "../config";
|
||||
import iconv from "iconv-lite";
|
||||
|
||||
interface McpExecuteQueryResponse {
|
||||
success?: unknown;
|
||||
@@ -36,8 +37,81 @@ function parseFiniteNumber(value: unknown): number | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function textMojibakeScore(value: string): number {
|
||||
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: string): boolean {
|
||||
const source = String(value ?? "");
|
||||
if (!source.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ‘’“”•–—™љ›њќћџ]/.test(source)) {
|
||||
return true;
|
||||
}
|
||||
return (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2;
|
||||
}
|
||||
|
||||
function decodeUtf8FromWin1251Mojibake(value: string): string {
|
||||
if (!looksLikeMojibake(value)) {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
const bytes = iconv.encode(value, "win1251");
|
||||
const decoded = bytes.toString("utf8");
|
||||
return textMojibakeScore(decoded) > textMojibakeScore(value) ? decoded : value;
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function decodeUtf8FromLatin1Mojibake(value: string): string {
|
||||
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: string): string {
|
||||
const fromWin1251 = decodeUtf8FromWin1251Mojibake(value);
|
||||
return decodeUtf8FromLatin1Mojibake(fromWin1251);
|
||||
}
|
||||
|
||||
function normalizeMojibakeValue(value: unknown): unknown {
|
||||
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 as Record<string, unknown>;
|
||||
const normalized: Record<string, unknown> = {};
|
||||
for (const [key, raw] of Object.entries(source)) {
|
||||
const repairedKey = normalizeMojibakeString(key);
|
||||
normalized[repairedKey] = normalizeMojibakeValue(raw);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeMojibakeRows(rows: Array<Record<string, unknown>>): Array<Record<string, unknown>> {
|
||||
return rows.map((row) => normalizeMojibakeValue(row) as Record<string, unknown>);
|
||||
}
|
||||
|
||||
function parseRowsFromTextTable(source: string): Array<Record<string, unknown>> {
|
||||
const normalized = String(source ?? "").replace(/\r/g, "").trim();
|
||||
const normalized = normalizeMojibakeString(String(source ?? "")).replace(/\r/g, "").trim();
|
||||
if (!normalized) {
|
||||
return [];
|
||||
}
|
||||
@@ -111,7 +185,7 @@ function parseRowsFromTextTable(source: string): Array<Record<string, unknown>>
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
return rows;
|
||||
return normalizeMojibakeRows(rows);
|
||||
}
|
||||
|
||||
function parseExecutePayload(payload: unknown): AddressMcpQueryResult {
|
||||
@@ -133,9 +207,11 @@ function parseExecutePayload(payload: unknown): AddressMcpQueryResult {
|
||||
}
|
||||
|
||||
if (Array.isArray(source.data)) {
|
||||
const rows = source.data
|
||||
.map((item) => (item && typeof item === "object" ? (item as Record<string, unknown>) : null))
|
||||
.filter((item): item is Record<string, unknown> => item !== null);
|
||||
const rows = normalizeMojibakeRows(
|
||||
source.data
|
||||
.map((item) => (item && typeof item === "object" ? (item as Record<string, unknown>) : null))
|
||||
.filter((item): item is Record<string, unknown> => item !== null)
|
||||
);
|
||||
return {
|
||||
ok: true,
|
||||
rows,
|
||||
@@ -152,9 +228,11 @@ function parseExecutePayload(payload: unknown): AddressMcpQueryResult {
|
||||
}
|
||||
|
||||
if (source.data && typeof source.data === "object" && Array.isArray((source.data as { rows?: unknown }).rows)) {
|
||||
const rows = ((source.data as { rows: unknown[] }).rows ?? [])
|
||||
.map((item) => (item && typeof item === "object" ? (item as Record<string, unknown>) : null))
|
||||
.filter((item): item is Record<string, unknown> => item !== null);
|
||||
const rows = normalizeMojibakeRows(
|
||||
((source.data as { rows: unknown[] }).rows ?? [])
|
||||
.map((item) => (item && typeof item === "object" ? (item as Record<string, unknown>) : null))
|
||||
.filter((item): item is Record<string, unknown> => item !== null)
|
||||
);
|
||||
return {
|
||||
ok: true,
|
||||
rows,
|
||||
|
||||
Reference in New Issue
Block a user