АРЧ АП11 - Архитектура после регресса: Архитектура: сохранить свежую дату при inventory root restore и очистить data-scope ответы от грязных лейблов

This commit is contained in:
2026-04-18 09:12:39 +03:00
parent a5ea9adf53
commit 0431595542
24 changed files with 1030 additions and 58 deletions
@@ -47,7 +47,9 @@ const ORGANIZATION_SCOPE_STOPWORDS = new Set([
function normalizeScopeLabel(value: unknown): string {
return String(value ?? "")
.replace(/\\/g, " ")
.replace(/[“”«»]/g, '"')
.replace(/([\p{L}])"(?=[\p{L}])/gu, "$1в")
.replace(/\s+/g, " ")
.trim();
}
@@ -109,6 +111,54 @@ function organizationTokenVariants(token: string): string[] {
return Array.from(variants);
}
function isSingleInsertionOrDeletionAway(left: string, right: string): boolean {
const longer = left.length >= right.length ? left : right;
const shorter = left.length >= right.length ? right : left;
if (longer.length - shorter.length !== 1) {
return false;
}
let longIndex = 0;
let shortIndex = 0;
let mismatchUsed = false;
while (longIndex < longer.length && shortIndex < shorter.length) {
if (longer[longIndex] === shorter[shortIndex]) {
longIndex += 1;
shortIndex += 1;
continue;
}
if (mismatchUsed) {
return false;
}
mismatchUsed = true;
longIndex += 1;
}
return true;
}
function organizationTokensLookEquivalent(left: string, right: string): boolean {
if (!left || !right) {
return false;
}
if (left === right) {
return true;
}
if (left.length >= 5 && right.length >= 5 && (left.startsWith(right) || right.startsWith(left))) {
return true;
}
const leftCompact = left.replace(/\s+/g, "");
const rightCompact = right.replace(/\s+/g, "");
if (!leftCompact || !rightCompact) {
return false;
}
if (leftCompact === rightCompact) {
return true;
}
if (leftCompact.length >= 6 && rightCompact.length >= 6 && isSingleInsertionOrDeletionAway(leftCompact, rightCompact)) {
return true;
}
return false;
}
export function scoreOrganizationMentionInMessage(message: unknown, organization: unknown): number {
const messageNorm = normalizeOrganizationScopeSearchText(message);
const organizationNorm = normalizeOrganizationScopeSearchText(organization);
@@ -170,20 +220,73 @@ export function scoreOrganizationMentionInMessage(message: unknown, organization
return score;
}
export function organizationsLikelySameEntity(left: unknown, right: unknown): boolean {
const leftNorm = normalizeOrganizationScopeSearchText(left);
const rightNorm = normalizeOrganizationScopeSearchText(right);
if (!leftNorm || !rightNorm) {
return false;
}
if (leftNorm === rightNorm) {
return true;
}
const leftTokens = tokenizeOrganizationScope(leftNorm);
const rightTokens = tokenizeOrganizationScope(rightNorm);
if (leftTokens.length === 0 || rightTokens.length === 0) {
return false;
}
const leftCompact = leftTokens.join("");
const rightCompact = rightTokens.join("");
if (leftCompact && rightCompact) {
if (leftCompact === rightCompact) {
return true;
}
if (
leftCompact.length >= 8 &&
rightCompact.length >= 8 &&
isSingleInsertionOrDeletionAway(leftCompact, rightCompact)
) {
return true;
}
}
const leftCovered = leftTokens.every((leftToken) =>
rightTokens.some((rightToken) => organizationTokensLookEquivalent(leftToken, rightToken))
);
if (!leftCovered) {
return false;
}
const rightCovered = rightTokens.every((rightToken) =>
leftTokens.some((leftToken) => organizationTokensLookEquivalent(leftToken, rightToken))
);
return rightCovered;
}
export function mergeKnownOrganizations(values: unknown[], limit = 50): string[] {
const dedup = new Map<string, string>();
const dedup: string[] = [];
for (const raw of Array.isArray(values) ? values : []) {
const normalized = normalizeOrganizationScopeValue(raw);
if (!normalized) {
continue;
}
const key = normalizeOrganizationScopeSearchText(normalized);
if (!key || dedup.has(key)) {
if (!key) {
continue;
}
dedup.set(key, normalized);
const existingIndex = dedup.findIndex((item) => organizationsLikelySameEntity(item, normalized));
if (existingIndex >= 0) {
const existing = dedup[existingIndex];
const existingKey = normalizeOrganizationScopeSearchText(existing);
if (key.length > existingKey.length || normalized.length > existing.length) {
dedup[existingIndex] = normalized;
}
continue;
}
dedup.push(normalized);
}
return Array.from(dedup.values()).slice(0, limit);
return dedup.slice(0, limit);
}
export function resolveOrganizationSelectionFromMessage(