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

444 lines
20 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";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAssistantDataScopePolicy = createAssistantDataScopePolicy;
// @ts-nocheck
const assistantOrganizationMatcher_1 = require("./assistantOrganizationMatcher");
const assistantContinuityPolicy_1 = require("./assistantContinuityPolicy");
const DATA_SCOPE_CACHE_TTL_MS = 60_000;
function normalizeScopeLabel(value) {
return String(value ?? "")
.replace(/[“”«»]/g, '"')
.replace(/\s+/g, " ")
.trim();
}
function normalizeScopeKey(value) {
return normalizeScopeLabel(value).toLowerCase().replace(/ё/g, "е");
}
function normalizeGuidValue(value) {
const source = normalizeScopeLabel(value);
if (!source) {
return null;
}
const match = source.match(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i);
return match ? String(match[0]).toLowerCase() : null;
}
function extractGuidValuesFromText(value) {
const source = normalizeScopeLabel(value);
if (!source) {
return [];
}
const matches = source.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi);
if (!matches || matches.length === 0) {
return [];
}
return Array.from(new Set(matches.map((item) => String(item).toLowerCase())));
}
function hasOrganizationKeyHint(key) {
return /(?:организац|organization|company|контор|org)\b/i.test(String(key ?? ""));
}
function hasNameKeyHint(key) {
return /(?:представ|наимен|name|title|display|presentation|description)\b/i.test(String(key ?? ""));
}
function hasGuidKeyHint(key) {
return /(?:идентифик|guid|uuid|key|ref|ссылк|\bid\b)\b/i.test(String(key ?? ""));
}
function looksLikeOrganizationTypeMarker(value) {
const normalized = normalizeScopeKey(value);
return /(?:справочникссылка\.\s*организац|catalogref\.\s*organization|organization|company|организац)/i.test(normalized);
}
function isPlausibleOrganizationName(value) {
const candidate = normalizeScopeLabel(value);
if (!candidate) {
return false;
}
if (/^(?:период|регистратор|счетдт|счеткт|amount|period|registrator|accountdt|accountkt)$/i.test(candidate)) {
return false;
}
if (/^[0-9._:/\\-]+$/i.test(candidate)) {
return false;
}
if (/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i.test(candidate)) {
return false;
}
if (/(?:справочникссылка|документссылка|плансчетовссылка|standardodata|recordtype|cmp:)/i.test(candidate)) {
return false;
}
return /[A-Za-z\u0400-\u04FF]/u.test(candidate);
}
function appendOrganizationFactsFromValue(value, hints, bucket, depth = 0) {
if (depth > 4 || value === null || value === undefined) {
return;
}
if (typeof value === "string") {
for (const guid of extractGuidValuesFromText(value)) {
if (hints.guidHint || hints.organizationHint || hints.nameHint) {
bucket.refs.push(guid);
}
}
if ((hints.organizationHint || hints.nameHint) && isPlausibleOrganizationName(value)) {
const normalized = normalizeScopeLabel(value);
if (normalized) {
bucket.names.push(normalized);
}
}
return;
}
if (Array.isArray(value)) {
for (const item of value) {
appendOrganizationFactsFromValue(item, hints, bucket, depth + 1);
}
return;
}
if (typeof value !== "object") {
return;
}
const entries = Object.entries(value);
let objectIsOrganization = false;
let hasObjectRefMarker = false;
let hasGuidLikeField = false;
let hasTypeMarker = false;
for (const [rawKey, rawVal] of entries) {
const key = normalizeScopeKey(rawKey);
if ((key.includes("objectref") || key.includes("_objectref")) && rawVal === true) {
hasObjectRefMarker = true;
}
if (typeof rawVal === "string" && normalizeGuidValue(rawVal)) {
hasGuidLikeField = true;
}
if (hasOrganizationKeyHint(key)) {
objectIsOrganization = true;
break;
}
if ((key.includes("типобъекта") || key.includes("type")) && typeof rawVal === "string" && looksLikeOrganizationTypeMarker(rawVal)) {
objectIsOrganization = true;
hasTypeMarker = true;
break;
}
}
if (!objectIsOrganization && hasObjectRefMarker && hasGuidLikeField) {
const hasNameLikeValue = entries.some(([rawKey, rawVal]) => {
if (typeof rawVal !== "string") {
return false;
}
const key = normalizeScopeKey(rawKey);
return hasNameKeyHint(key) || isPlausibleOrganizationName(rawVal);
});
if (hasTypeMarker || hasNameLikeValue) {
objectIsOrganization = true;
}
}
for (const [rawKey, rawVal] of entries) {
if (String(rawKey ?? "").startsWith("__")) {
continue;
}
const key = normalizeScopeKey(rawKey);
const childHints = {
organizationHint: hints.organizationHint || objectIsOrganization || hasOrganizationKeyHint(key),
nameHint: hints.nameHint || objectIsOrganization || hasNameKeyHint(key),
guidHint: hints.guidHint || objectIsOrganization || hasGuidKeyHint(key)
};
if (typeof rawVal === "string") {
const guid = normalizeGuidValue(rawVal);
if (guid && childHints.guidHint) {
bucket.refs.push(guid);
}
}
appendOrganizationFactsFromValue(rawVal, childHints, bucket, depth + 1);
}
}
function buildResolvedDataScopeProbe(status, activeMcpChannel, organizations) {
return {
status,
channel: activeMcpChannel,
organizations: Array.from(new Set(Array.isArray(organizations) ? organizations : [])).slice(0, 20),
error: null
};
}
function createAssistantDataScopePolicy(deps) {
const dataScopeProbeCache = new Map();
function parseOrganizationsFromDataScopeAssistantText(text) {
const source = deps.repairAddressMojibake(String(text ?? ""));
if (!source) {
return [];
}
const extracted = [];
const singleMatch = source.match(/доступна\s+организация:\s*([^.\n]+)/iu);
if (singleMatch) {
const value = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(singleMatch[1]);
if (value) {
extracted.push(value);
}
}
const multiMatch = source.match(/доступны\s+организац(?:ии|ия)\s*(?:\(\d+\))?:\s*([^.\n]+)/iu);
if (multiMatch) {
const parts = String(multiMatch[1] ?? "")
.split(",")
.map((item) => (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(item))
.filter((item) => Boolean(item));
extracted.push(...parts);
}
return Array.from(new Set(extracted));
}
function extractKnownOrganizationsFromHistory(items) {
const collected = [];
for (let index = (Array.isArray(items) ? items.length : 0) - 1; index >= 0; index -= 1) {
const item = Array.isArray(items) ? items[index] : null;
if (!item || typeof item !== "object" || item.role !== "assistant") {
continue;
}
const debug = item.debug;
if (debug && typeof debug === "object") {
const directFromProbe = Array.isArray(debug.living_chat_data_scope_probe_organizations)
? debug.living_chat_data_scope_probe_organizations
: [];
const knownFromDebug = Array.isArray(debug.assistant_known_organizations)
? debug.assistant_known_organizations
: [];
const directFromCandidates = Array.isArray(debug.organization_candidates) ? debug.organization_candidates : [];
const directFromResolved = [
(0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(debug.assistant_active_organization),
(0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(debug.living_chat_selected_organization),
(0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(debug.extracted_filters?.organization),
(0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(debug.address_root_frame_context?.organization)
].filter((value) => Boolean(value));
if (directFromProbe.length > 0 ||
knownFromDebug.length > 0 ||
directFromCandidates.length > 0 ||
directFromResolved.length > 0) {
collected.push(...directFromProbe, ...knownFromDebug, ...directFromCandidates, ...directFromResolved);
}
}
const parsedFromText = parseOrganizationsFromDataScopeAssistantText(item.text);
if (parsedFromText.length > 0) {
collected.push(...parsedFromText);
}
if (collected.length >= 20) {
break;
}
}
return (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)(collected, 20);
}
function findLastAssistantActiveOrganization(items) {
for (let index = (Array.isArray(items) ? items.length : 0) - 1; index >= 0; index -= 1) {
const item = Array.isArray(items) ? items[index] : null;
if (!item || typeof item !== "object" || item.role !== "assistant") {
continue;
}
const debug = item.debug;
if (!debug || typeof debug !== "object") {
continue;
}
const direct = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(debug.assistant_active_organization);
if (direct) {
return direct;
}
const selected = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(debug.living_chat_selected_organization);
if (selected) {
return selected;
}
if ((0, assistantContinuityPolicy_1.isGroundedAddressDebug)(debug)) {
const groundedOrganization = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)((0, assistantContinuityPolicy_1.readAddressDebugOrganization)(debug));
if (groundedOrganization) {
return groundedOrganization;
}
}
}
return null;
}
function extractOrganizationFactsFromRows(rows) {
const names = [];
const refs = [];
const pairs = [];
for (const row of Array.isArray(rows) ? rows : []) {
if (!row || typeof row !== "object") {
continue;
}
const rowNames = [];
const rowRefs = [];
for (const [rawKey, rawValue] of Object.entries(row)) {
if (String(rawKey ?? "").startsWith("__")) {
continue;
}
const key = normalizeScopeKey(rawKey);
appendOrganizationFactsFromValue(rawValue, {
organizationHint: hasOrganizationKeyHint(key),
nameHint: hasNameKeyHint(key),
guidHint: hasGuidKeyHint(key)
}, { names: rowNames, refs: rowRefs });
}
const dedupRowNames = Array.from(new Set(rowNames))
.filter((item) => isPlausibleOrganizationName(item))
.slice(0, 20);
const dedupRowRefs = Array.from(new Set(rowRefs))
.map((item) => String(item ?? "").toLowerCase())
.filter((item) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(item))
.slice(0, 20);
if (dedupRowNames.length === 0 && dedupRowRefs.length === 0) {
const fallbackBucket = { names: [], refs: [] };
appendOrganizationFactsFromValue(row, {
organizationHint: true,
nameHint: true,
guidHint: true
}, fallbackBucket);
for (const value of fallbackBucket.names) {
if (isPlausibleOrganizationName(value)) {
dedupRowNames.push(value);
}
}
for (const value of fallbackBucket.refs) {
const normalized = String(value ?? "").toLowerCase();
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(normalized)) {
dedupRowRefs.push(normalized);
}
}
}
names.push(...dedupRowNames);
refs.push(...dedupRowRefs);
if (dedupRowRefs.length > 0 && dedupRowNames.length > 0) {
for (const ref of dedupRowRefs) {
for (const name of dedupRowNames) {
pairs.push({ ref, name });
}
}
}
}
return {
names: Array.from(new Set(names)).slice(0, 20),
refs: Array.from(new Set(refs)).slice(0, 20),
pairs: Array.from(new Set(pairs.map((item) => `${item.ref}||${item.name}`)))
.map((token) => {
const [ref, name] = token.split("||");
return { ref, name };
})
.slice(0, 100)
};
}
function resolveOrganizationNamesByRefs(refs, facts) {
const refSet = new Set((Array.isArray(refs) ? refs : [])
.map((item) => String(item ?? "").toLowerCase())
.filter((item) => item.length > 0));
if (refSet.size === 0) {
return [];
}
const names = [];
for (const pair of Array.isArray(facts?.pairs) ? facts?.pairs : []) {
const ref = String(pair?.ref ?? "").toLowerCase();
const name = normalizeScopeLabel(pair?.name ?? "");
if (!ref || !name || !refSet.has(ref)) {
continue;
}
names.push(name);
}
return Array.from(new Set(names)).slice(0, 20);
}
async function resolveAssistantDataScopeProbe() {
const cacheKey = `${deps.mcpProxyUrl}|${deps.activeMcpChannel}`;
const now = Date.now();
const cached = dataScopeProbeCache.get(cacheKey);
if (cached && Number(cached.expiresAt ?? 0) > now) {
return cached.value;
}
if (String(process.env.NODE_ENV ?? "").toLowerCase() === "test") {
return {
status: "skipped_test_env",
channel: deps.activeMcpChannel,
organizations: [],
error: null
};
}
const catalogQueryCandidates = [
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.Наименование КАК Организация ИЗ Справочник.Организации КАК Организации",
"ВЫБРАТЬ ПЕРВЫЕ 20 Организации.НаименованиеПолное КАК Организация ИЗ Справочник.Организации КАК Организации",
"ВЫБРАТЬ ПЕРВЫЕ 100 Организации.Ссылка КАК Организация, ПРЕДСТАВЛЕНИЕ(Организации.Ссылка) КАК ОрганизацияПредставление ИЗ Справочник.Организации КАК Организации"
];
const movementProbeCandidates = [
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация ИЗ РегистрБухгалтерии.Хозрасчетный КАК Движения УПОРЯДОЧИТЬ ПО Движения.Период УБЫВ",
"ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация ИЗ РегистрБухгалтерии.Хозрасчетный КАК Движения"
];
let lastError = null;
const catalogFacts = { names: [], refs: [], pairs: [] };
for (const queryText of catalogQueryCandidates) {
const probe = await deps.executeAddressMcpQuery({
query: queryText,
limit: 100
});
if (probe.error) {
lastError = String(probe.error);
continue;
}
const facts = extractOrganizationFactsFromRows(Array.isArray(probe.rows) ? probe.rows : []);
catalogFacts.names.push(...facts.names);
catalogFacts.refs.push(...facts.refs);
catalogFacts.pairs.push(...facts.pairs);
if (facts.names.length > 0) {
const resolved = buildResolvedDataScopeProbe("resolved", deps.activeMcpChannel, facts.names);
dataScopeProbeCache.set(cacheKey, {
expiresAt: now + DATA_SCOPE_CACHE_TTL_MS,
value: resolved
});
return resolved;
}
}
const movementFacts = { names: [], refs: [], pairs: [] };
for (const queryText of movementProbeCandidates) {
const probe = await deps.executeAddressMcpQuery({
query: queryText,
limit: 60
});
if (probe.error) {
lastError = String(probe.error);
continue;
}
const facts = extractOrganizationFactsFromRows(Array.isArray(probe.rows) ? probe.rows : []);
movementFacts.names.push(...facts.names);
movementFacts.refs.push(...facts.refs);
movementFacts.pairs.push(...facts.pairs);
if (facts.names.length > 0) {
const resolved = buildResolvedDataScopeProbe("resolved_from_activity", deps.activeMcpChannel, facts.names);
dataScopeProbeCache.set(cacheKey, {
expiresAt: now + DATA_SCOPE_CACHE_TTL_MS,
value: resolved
});
return resolved;
}
}
const movementRefs = Array.from(new Set(movementFacts.refs))
.map((item) => String(item ?? "").toLowerCase())
.filter((item) => item.length > 0);
if (movementRefs.length > 0) {
const namesFromCatalogPairs = resolveOrganizationNamesByRefs(movementRefs, {
names: Array.from(new Set(catalogFacts.names)),
refs: Array.from(new Set(catalogFacts.refs)),
pairs: catalogFacts.pairs
});
if (namesFromCatalogPairs.length > 0) {
const resolved = buildResolvedDataScopeProbe("resolved_from_ref_lookup", deps.activeMcpChannel, namesFromCatalogPairs);
dataScopeProbeCache.set(cacheKey, {
expiresAt: now + DATA_SCOPE_CACHE_TTL_MS,
value: resolved
});
return resolved;
}
}
const fallback = {
status: lastError ? "unresolved_with_error" : "unresolved",
channel: deps.activeMcpChannel,
organizations: [],
error: lastError
};
dataScopeProbeCache.set(cacheKey, {
expiresAt: now + DATA_SCOPE_CACHE_TTL_MS,
value: fallback
});
return fallback;
}
return {
parseOrganizationsFromDataScopeAssistantText,
extractKnownOrganizationsFromHistory,
findLastAssistantActiveOrganization,
extractOrganizationFactsFromRows,
resolveOrganizationNamesByRefs,
resolveAssistantDataScopeProbe
};
}