refactor: удалены ссылки на knowledge/ (не относится к КЛГ АСУ ТК)

- Удалены API routes, rewrites и ссылки на knowledge/reglaments
- knowledge/ удалена из git-истории (6 ГБ PDF не относящихся к проекту)
- Нормативная база обслуживается через модуль legal (routes/legal/)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Yuriy 2026-02-15 14:02:14 +03:00
parent a30bf929dc
commit d47baa1782
11 changed files with 34 additions and 581 deletions

5
.gitignore vendored
View File

@ -32,4 +32,7 @@ build/
# OS # OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Нормативная база (регламенты) — вынесена в отдельный сервис, не в репозитории КЛГ АСУ ТК
knowledge/

View File

@ -119,138 +119,6 @@ const openApiSpec = {
}, },
}, },
}, },
'/api/knowledge/graph': {
get: {
summary: 'Получение Knowledge Graph',
description: 'Возвращает граф знаний или результаты поиска в графе',
tags: ['Knowledge Graph'],
parameters: [
{
name: 'query',
in: 'query',
description: 'Поисковый запрос (опционально)',
required: false,
schema: {
type: 'string',
},
},
{
name: 'format',
in: 'query',
description: 'Формат ответа',
required: false,
schema: {
type: 'string',
enum: ['json', 'visualization'],
default: 'json',
},
},
],
responses: {
'200': {
description: 'Успешный ответ',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
nodes: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
type: { type: 'string' },
label: { type: 'string' },
properties: { type: 'object' },
},
},
},
edges: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
source: { type: 'string' },
target: { type: 'string' },
type: { type: 'string' },
weight: { type: 'number' },
},
},
},
},
},
},
},
},
'429': {
description: 'Превышен лимит запросов',
},
'500': {
description: 'Внутренняя ошибка сервера',
},
},
},
},
'/api/knowledge/search': {
post: {
summary: 'Семантический поиск в базе знаний',
description: 'Выполняет семантический поиск по базе знаний',
tags: ['Knowledge Base'],
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['query'],
properties: {
query: {
type: 'string',
description: 'Поисковый запрос',
},
limit: {
type: 'number',
default: 10,
description: 'Максимальное количество результатов',
},
},
},
},
},
},
responses: {
'200': {
description: 'Успешный ответ',
},
},
},
},
'/api/knowledge/insights': {
get: {
summary: 'Получение инсайтов',
description: 'Генерирует инсайты на основе данных',
tags: ['Knowledge Base'],
responses: {
'200': {
description: 'Успешный ответ',
},
},
},
},
'/api/knowledge/recommendations': {
get: {
summary: 'Получение рекомендаций',
description: 'Генерирует рекомендации на основе данных',
tags: ['Knowledge Base'],
responses: {
'200': {
description: 'Успешный ответ',
},
},
},
},
}, },
components: { components: {
schemas: { schemas: {
@ -274,14 +142,6 @@ const openApiSpec = {
name: 'AI Agent', name: 'AI Agent',
description: 'Endpoints для взаимодействия с автономным агентом', description: 'Endpoints для взаимодействия с автономным агентом',
}, },
{
name: 'Knowledge Graph',
description: 'Endpoints для работы с графом знаний',
},
{
name: 'Knowledge Base',
description: 'Endpoints для работы с базой знаний',
},
], ],
}; };

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { logInfo, logError } from '@/lib/logger-client'; import { logError } from '@/lib/logger-client';
interface GraphNode { interface GraphNode {
id: string; id: string;
@ -33,180 +33,23 @@ export default function KnowledgeGraphVisualization({
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
let visNetwork: any = null;
const loadGraph = async () => { const loadGraph = async () => {
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
// Модуль knowledge вынесен в отдельный сервис (КЛГ АСУ ТК не использует локальную папку knowledge/)
const url = query setGraph({ nodes: [], edges: [] });
? `/api/knowledge/graph?query=${encodeURIComponent(query)}&format=visualization` setLoading(false);
: '/api/knowledge/graph?format=visualization'; return;
} catch (err) {
const response = await fetch(url); logError('Knowledge graph unavailable', err);
if (!response.ok) { setError('Модуль графа знаний вынесен в отдельный сервис');
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
setGraph(data);
// Динамически загружаем vis-network и vis-data
let Network: any = null;
let DataSet: any = null;
try {
// Импортируем vis-network для Network
const visNetwork = await import('vis-network');
Network = visNetwork.Network || visNetwork.default?.Network || visNetwork.default || visNetwork;
// Импортируем vis-data для DataSet
const visData = await import('vis-data');
DataSet = visData.DataSet || visData.default?.DataSet || visData.default || visData;
// Если не получилось через ESM, пробуем через require (для SSR)
if (!Network || !DataSet) {
if (typeof window === 'undefined') {
const visNetworkReq = require('vis-network');
Network = visNetworkReq.Network || visNetworkReq.default?.Network || visNetworkReq;
const visDataReq = require('vis-data');
DataSet = visDataReq.DataSet || visDataReq.default?.DataSet || visDataReq;
}
}
} catch (err) {
logError('vis-network/vis-data import error', err);
setError(`Ошибка загрузки библиотек визуализации: ${err instanceof Error ? err.message : 'Неизвестная ошибка'}`);
setLoading(false);
return;
}
if (!Network) {
logError('vis-network Network not found');
setError('vis-network Network не найден');
setLoading(false);
return;
}
if (!DataSet) {
logError('vis-data DataSet not found');
setError('vis-data DataSet не найден. Установите: npm install vis-data');
setLoading(false);
return;
}
if (!containerRef.current) {
return;
}
// Подготовка данных для vis-network
const nodes = new DataSet(
data.nodes.map((node: GraphNode) => {
// Создаем новый объект без id, чтобы избежать дублирования
const { id, label, type, ...restNode } = node;
return {
id,
label: label || id,
group: type,
title: `${type}: ${label}\n${JSON.stringify(node, null, 2)}`,
...restNode,
};
})
);
const edges = new DataSet(
data.edges.map((edge: GraphEdge) => ({
id: edge.id,
from: edge.source,
to: edge.target,
label: edge.type,
value: edge.weight,
title: `${edge.type} (weight: ${edge.weight})`,
}))
);
const networkData = { nodes, edges };
const options = {
nodes: {
shape: 'dot',
size: 16,
font: {
size: 12,
color: '#333',
},
borderWidth: 2,
shadow: true,
},
edges: {
width: 2,
color: { color: '#848484' },
smooth: {
type: 'continuous',
},
arrows: {
to: {
enabled: true,
scaleFactor: 0.5,
},
},
font: {
size: 10,
align: 'middle',
},
},
physics: {
enabled: true,
stabilization: {
iterations: 200,
},
},
interaction: {
hover: true,
tooltipDelay: 100,
zoomView: true,
dragView: true,
},
groups: {
aircraft: { color: { background: '#2196f3', border: '#1976d2' } },
audit: { color: { background: '#ff9800', border: '#f57c00' } },
risk: { color: { background: '#f44336', border: '#d32f2f' } },
operator: { color: { background: '#4caf50', border: '#388e3c' } },
regulation: { color: { background: '#9c27b0', border: '#7b1fa2' } },
document: { color: { background: '#00bcd4', border: '#0097a7' } },
},
};
visNetwork = new Network(containerRef.current, networkData, options);
// Обработка клика на узел
visNetwork.on('click', (params: any) => {
if (params.nodes.length > 0 && onNodeClick) {
onNodeClick(params.nodes[0]);
}
});
logInfo('Knowledge graph visualization loaded', {
nodes: data.nodes.length,
edges: data.edges.length,
});
} catch (err: any) {
logError('Failed to load knowledge graph visualization', err);
setError(err.message || 'Ошибка загрузки графа знаний');
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
loadGraph(); loadGraph();
}, [query]);
return () => {
if (visNetwork) {
visNetwork.destroy();
}
};
}, [query, onNodeClick]);
if (loading) { if (loading) {
return ( return (

View File

@ -47,30 +47,11 @@ export default function KnowledgePanel({
const loadKnowledge = async () => { const loadKnowledge = async () => {
setLoading(true); setLoading(true);
try { try {
// Загружаем инсайты // Модуль нормативной базы (knowledge) вынесен в отдельный сервис
const insightsRes = await fetch('/api/knowledge/insights', { setInsights([]);
method: 'POST', setRecommendations([]);
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
data: [{ id: entityId, type: entityType }],
context: `${entityType} ${entityId}`,
}),
});
const insightsData = await insightsRes.json();
setInsights(insightsData.insights || []);
// Загружаем рекомендации
const recRes = await fetch('/api/knowledge/recommendations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
[String(entityType)]: [{ id: entityId }],
}),
});
const recData = await recRes.json();
setRecommendations(recData.recommendations || []);
} catch (error) { } catch (error) {
console.error('Failed to load knowledge:', error); console.error('Knowledge module in separate service', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }

View File

@ -31,21 +31,9 @@ export default function SemanticSearch({
setLoading(true); setLoading(true);
try { try {
const response = await fetch('/api/knowledge/search', { // Модуль нормативной базы (knowledge) вынесен в отдельный сервис
method: 'POST', setResults([]);
headers: { 'Content-Type': 'application/json' }, } catch {
body: JSON.stringify({
query,
type: type === 'all' ? undefined : type,
limit: 10,
threshold: 0.7,
}),
});
const data = await response.json();
setResults(data.results || []);
} catch (error) {
// Ошибка уже обработана в API
setResults([]); setResults([]);
} finally { } finally {
setLoading(false); setLoading(false);

View File

@ -1,5 +1,7 @@
# AI-Powered Knowledge System # AI-Powered Knowledge System
> **Примечание.** Модуль нормативной базы (knowledge) вынесен в отдельный сервис. Описание ниже сохранено как справочное; эндпоинты `/api/knowledge/*` в данном репозитории (КЛГ АСУ ТК) не реализованы.
## Обзор ## Обзор
Система знаний на основе ИИ для автоматического извлечения, хранения и поиска информации из документов и данных. Система знаний на основе ИИ для автоматического извлечения, хранения и поиска информации из документов и данных.

View File

@ -42,14 +42,7 @@
- **Рёбра**: Связи между сущностями - **Рёбра**: Связи между сущностями
- **Embeddings**: Векторные представления для семантического поиска - **Embeddings**: Векторные представления для семантического поиска
**Пример:** **Пример:** Модуль нормативной базы (knowledge) вынесен в отдельный сервис; граф знаний доступен через внешний API.
```typescript
import { buildKnowledgeGraph } from '@/lib/ai/knowledge-graph';
const graph = await buildKnowledgeGraph();
// graph.nodes - узлы графа
// graph.edges - связи между узлами
```
### 2. LLM Reasoning ### 2. LLM Reasoning
@ -159,11 +152,11 @@ const result = await agent.execute(plan);
} }
``` ```
#### GET `/api/knowledge/graph` #### Модуль knowledge
Получение Knowledge Graph. Модуль нормативной базы (knowledge) вынесен в отдельный сервис. Эндпоинт `GET /api/knowledge/graph` в данном репозитории не реализован.
**Query Parameters:** **Query Parameters (внешний сервис):**
- `query` - поисковый запрос (опционально) - `query` - поисковый запрос (опционально)
- `format` - формат ответа: `json` или `visualization` - `format` - формат ответа: `json` или `visualization`

View File

@ -44,7 +44,7 @@ const result = await bulkheads.ai.execute(async () => {
**Глобальные bulkheads:** **Глобальные bulkheads:**
- `ai` - 5 одновременных запросов - `ai` - 5 одновременных запросов
- `database` - 20 одновременных запросов - `database` - 20 одновременных запросов
- `knowledgeGraph` - 2 построения графа одновременно - Модуль нормативной базы (knowledge) вынесен в отдельный сервис; лимит `knowledgeGraph` не используется в этом репозитории.
- `fileProcessing` - 3 файла одновременно - `fileProcessing` - 3 файла одновременно
### 3. Retry с Exponential Backoff ### 3. Retry с Exponential Backoff

View File

@ -2,6 +2,6 @@
Домен: Учет ТМЦ (TMC Registry) Домен: Учет ТМЦ (TMC Registry)
- Использовать только утверждённые шаблоны и регламенты из `knowledge/`. - Модуль нормативной базы (knowledge) вынесен в отдельный сервис; использовать только утверждённые шаблоны и регламенты, доступные через него.
- Все поля должны соответствовать структурам, описанным в словарях и спецификациях проекта. - Все поля должны соответствовать структурам, описанным в словарях и спецификациях проекта.
- При работе с ТМЦ приоритет имеют документы с `domain = tmc` в `manifest.json`. - При работе с ТМЦ приоритет имеют документы с `domain = tmc` в `manifest.json`.

View File

@ -1,170 +1,9 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Скрипт для добавления всех файлов из knowledge/ в manifest.json * Раньше: добавление файлов из knowledge/ в manifest.json.
* Приводит все записи к единому формату с обязательными полями * Модуль нормативной базы (knowledge) вынесен в отдельный сервис.
* Скрипт сохранён как заглушка для совместимости с CI/скриптами.
*/ */
import fs from 'fs'; console.log('Модуль нормативной базы (knowledge) вынесен в отдельный сервис. Обновление manifest по knowledge/ пропущено.');
import path from 'path'; process.exit(0);
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const MANIFEST_PATH = path.join(__dirname, '../index/manifest.json');
const KNOWLEDGE_DIR = path.join(__dirname, '../knowledge');
// Ключевые документы для статуса approved (3-5 документов)
const KEY_DOCUMENTS_PATTERNS = [
/papa_project_bootstrap/i,
/145\.1в/i,
/рц-ап-145/i,
/tv3_117_kniga_1.*turboval/i,
/tech_cards_registry/i
];
function getDocumentType(filePath) {
const dir = path.dirname(filePath).split(path.sep).pop();
if (dir === 'reglaments') return 'reglament';
if (dir === 'guides') return 'template';
if (dir === 'samples') return 'sample';
return 'reglament';
}
function getDomain(filePath, fileName) {
const lowerName = fileName.toLowerCase();
if (lowerName.includes('tmc') || lowerName.includes('tv3') || lowerName.includes('двигател') || lowerName.includes('byulleten')) return 'tmc';
if (lowerName.includes('145') || lowerName.includes('183') || lowerName.includes('регламент') || lowerName.includes('руководство')) return 'regulations';
if (lowerName.includes('анкет') || lowerName.includes('hr')) return 'hr';
if (lowerName.includes('инспекц')) return 'inspection';
return 'core';
}
function generateId(type, index) {
const prefix = type === 'reglament' ? 'reg' : type === 'template' ? 'guide' : 'sample';
return `${prefix}-${String(index).padStart(3, '0')}`;
}
function getTitle(fileName) {
// Убираем расширение
let title = fileName.replace(/\.[^.]+$/, '');
// Убираем "— копия" и подобное
title = title.replace(/\s*—\s*копия\s*/gi, '').trim();
// Если название слишком длинное, обрезаем
if (title.length > 100) {
title = title.substring(0, 97) + '...';
}
return title || fileName;
}
function isKeyDocument(fileName) {
return KEY_DOCUMENTS_PATTERNS.some(pattern => pattern.test(fileName));
}
function getAllFiles() {
const files = [];
['reglaments', 'guides', 'samples'].forEach(dir => {
const dirPath = path.join(KNOWLEDGE_DIR, dir);
if (!fs.existsSync(dirPath)) return;
const dirFiles = fs.readdirSync(dirPath)
.filter(file => {
const filePath = path.join(dirPath, file);
const stat = fs.statSync(filePath);
return stat.isFile() && file !== '.DS_Store' && file !== 'README.md';
})
.map(file => ({
fileName: file,
filePath: path.join(dir, file),
fullPath: path.join(dirPath, file)
}));
files.push(...dirFiles);
});
return files;
}
function updateManifest() {
// console.log('🔍 Сканирование файлов в knowledge/...\n');
// Получаем все файлы
const allFiles = getAllFiles();
console.log(`Найдено файлов: ${allFiles.length}\n`);
// Генерируем документы для всех файлов
const documents = [];
let regIndex = 1;
let guideIndex = 1;
let sampleIndex = 1;
// Сортируем файлы для консистентности
allFiles.sort((a, b) => {
const typeA = getDocumentType(a.filePath);
const typeB = getDocumentType(b.filePath);
const typeOrder = { reglament: 0, template: 1, sample: 2 };
if (typeOrder[typeA] !== typeOrder[typeB]) {
return typeOrder[typeA] - typeOrder[typeB];
}
return a.fileName.localeCompare(b.fileName);
});
allFiles.forEach(file => {
const relativePath = `knowledge/${file.filePath}`;
const type = getDocumentType(relativePath);
// Генерируем ID
let id;
if (type === 'reglament') {
id = generateId('reglament', regIndex++);
} else if (type === 'template') {
id = generateId('template', guideIndex++);
} else {
id = generateId('sample', sampleIndex++);
}
const doc = {
id,
title: getTitle(file.fileName),
path: relativePath,
type,
domain: getDomain(relativePath, file.fileName),
version: '1.0.0',
status: isKeyDocument(file.fileName) ? 'approved' : 'draft'
};
documents.push(doc);
});
// Читаем текущий manifest
const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
// Обновляем manifest
manifest.documents = documents;
manifest.manifest.lastUpdated = new Date().toISOString();
// Обновляем метаданные
const statusBreakdown = { approved: 0, draft: 0, deprecated: 0 };
const fileTypes = {};
documents.forEach(doc => {
statusBreakdown[doc.status] = (statusBreakdown[doc.status] || 0) + 1;
const ext = path.extname(doc.path).slice(1).toLowerCase();
fileTypes[ext] = (fileTypes[ext] || 0) + 1;
});
manifest.metadata.documentCount = documents.length;
manifest.metadata.statusBreakdown = statusBreakdown;
manifest.metadata.fileTypes = fileTypes;
// Сохраняем
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
console.log(`✅ Обновлено документов: ${documents.length}`);
console.log(` - Approved: ${statusBreakdown.approved}`);
console.log(` - Draft: ${statusBreakdown.draft}`);
console.log(` - Deprecated: ${statusBreakdown.deprecated}\n`);
}
updateManifest();

View File

@ -20,7 +20,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const MANIFEST_PATH = path.join(__dirname, '../index/manifest.json'); const MANIFEST_PATH = path.join(__dirname, '../index/manifest.json');
const KNOWLEDGE_DIR = path.join(__dirname, '../knowledge'); // Модуль knowledge вынесен в отдельный сервис — проверка файлов в knowledge/ отключена
// Обязательные поля документа // Обязательные поля документа
const REQUIRED_FIELDS = ['id', 'title', 'path', 'type', 'domain', 'version', 'status']; const REQUIRED_FIELDS = ['id', 'title', 'path', 'type', 'domain', 'version', 'status'];
@ -154,9 +154,6 @@ function validateManifest() {
} }
}); });
// Проверка: все файлы из knowledge/ должны быть в manifest
checkKnowledgeFilesInManifest(manifest, documentPaths);
printResults(); printResults();
if (errors.length > 0) { if (errors.length > 0) {
@ -164,59 +161,6 @@ function validateManifest() {
} }
} }
function getAllKnowledgeFiles() {
const files = [];
if (!fs.existsSync(KNOWLEDGE_DIR)) {
return files;
}
['reglaments', 'guides', 'samples'].forEach(dir => {
const dirPath = path.join(KNOWLEDGE_DIR, dir);
if (!fs.existsSync(dirPath)) return;
const dirFiles = fs.readdirSync(dirPath)
.filter(file => {
const filePath = path.join(dirPath, file);
try {
const stat = fs.statSync(filePath);
return stat.isFile() && file !== '.DS_Store' && file !== 'README.md';
} catch (e) {
return false;
}
})
.map(file => {
const relativePath = `knowledge/${dir}/${file}`;
return {
fileName: file,
relativePath: relativePath,
fullPath: path.join(dirPath, file)
};
});
files.push(...dirFiles);
});
return files;
}
function checkKnowledgeFilesInManifest(manifest, manifestPaths) {
console.log('🔍 Проверка соответствия файлов knowledge/ и manifest...\n');
const knowledgeFiles = getAllKnowledgeFiles();
const manifestPathsSet = new Set(manifestPaths);
// Проверяем, что каждый файл из knowledge/ есть в manifest
knowledgeFiles.forEach(file => {
if (!manifestPathsSet.has(file.relativePath)) {
errors.push(`Файл из knowledge/ отсутствует в manifest: ${file.relativePath}`);
}
});
console.log(` Проверено файлов в knowledge/: ${knowledgeFiles.length}`);
console.log(` Записей в manifest: ${manifestPaths.size}\n`);
}
function printResults() { function printResults() {
if (errors.length > 0) { if (errors.length > 0) {
console.log('❌ Ошибки:\n'); console.log('❌ Ошибки:\n');