410 lines
18 KiB
JavaScript
410 lines
18 KiB
JavaScript
"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 authority = (0, assistantContinuityPolicy_1.resolveAssistantOrganizationAuthority)({
|
||
sessionItems: items,
|
||
toNonEmptyString: assistantOrganizationMatcher_1.normalizeOrganizationScopeValue,
|
||
normalizeOrganizationScopeValue: assistantOrganizationMatcher_1.normalizeOrganizationScopeValue,
|
||
mergeKnownOrganizations: assistantOrganizationMatcher_1.mergeKnownOrganizations
|
||
});
|
||
const collected = [...authority.knownOrganizations];
|
||
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 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) {
|
||
const authority = (0, assistantContinuityPolicy_1.resolveAssistantOrganizationAuthority)({
|
||
sessionItems: items,
|
||
toNonEmptyString: assistantOrganizationMatcher_1.normalizeOrganizationScopeValue,
|
||
normalizeOrganizationScopeValue: assistantOrganizationMatcher_1.normalizeOrganizationScopeValue,
|
||
mergeKnownOrganizations: assistantOrganizationMatcher_1.mergeKnownOrganizations
|
||
});
|
||
return (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(authority.activeOrganization);
|
||
}
|
||
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
|
||
};
|
||
}
|