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:
parent
a30bf929dc
commit
d47baa1782
5
.gitignore
vendored
5
.gitignore
vendored
@ -32,4 +32,7 @@ build/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Нормативная база (регламенты) — вынесена в отдельный сервис, не в репозитории КЛГ АСУ ТК
|
||||
knowledge/
|
||||
@ -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: {
|
||||
schemas: {
|
||||
@ -274,14 +142,6 @@ const openApiSpec = {
|
||||
name: 'AI Agent',
|
||||
description: 'Endpoints для взаимодействия с автономным агентом',
|
||||
},
|
||||
{
|
||||
name: 'Knowledge Graph',
|
||||
description: 'Endpoints для работы с графом знаний',
|
||||
},
|
||||
{
|
||||
name: 'Knowledge Base',
|
||||
description: 'Endpoints для работы с базой знаний',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { logInfo, logError } from '@/lib/logger-client';
|
||||
import { logError } from '@/lib/logger-client';
|
||||
|
||||
interface GraphNode {
|
||||
id: string;
|
||||
@ -33,180 +33,23 @@ export default function KnowledgeGraphVisualization({
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let visNetwork: any = null;
|
||||
|
||||
const loadGraph = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const url = query
|
||||
? `/api/knowledge/graph?query=${encodeURIComponent(query)}&format=visualization`
|
||||
: '/api/knowledge/graph?format=visualization';
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
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 || 'Ошибка загрузки графа знаний');
|
||||
// Модуль knowledge вынесен в отдельный сервис (КЛГ АСУ ТК не использует локальную папку knowledge/)
|
||||
setGraph({ nodes: [], edges: [] });
|
||||
setLoading(false);
|
||||
return;
|
||||
} catch (err) {
|
||||
logError('Knowledge graph unavailable', err);
|
||||
setError('Модуль графа знаний вынесен в отдельный сервис');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadGraph();
|
||||
|
||||
return () => {
|
||||
if (visNetwork) {
|
||||
visNetwork.destroy();
|
||||
}
|
||||
};
|
||||
}, [query, onNodeClick]);
|
||||
}, [query]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
||||
@ -47,30 +47,11 @@ export default function KnowledgePanel({
|
||||
const loadKnowledge = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// Загружаем инсайты
|
||||
const insightsRes = await fetch('/api/knowledge/insights', {
|
||||
method: 'POST',
|
||||
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 || []);
|
||||
// Модуль нормативной базы (knowledge) вынесен в отдельный сервис
|
||||
setInsights([]);
|
||||
setRecommendations([]);
|
||||
} catch (error) {
|
||||
console.error('Failed to load knowledge:', error);
|
||||
console.error('Knowledge module in separate service', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@ -31,21 +31,9 @@ export default function SemanticSearch({
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch('/api/knowledge/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
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
|
||||
// Модуль нормативной базы (knowledge) вынесен в отдельный сервис
|
||||
setResults([]);
|
||||
} catch {
|
||||
setResults([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
# AI-Powered Knowledge System
|
||||
|
||||
> **Примечание.** Модуль нормативной базы (knowledge) вынесен в отдельный сервис. Описание ниже сохранено как справочное; эндпоинты `/api/knowledge/*` в данном репозитории (КЛГ АСУ ТК) не реализованы.
|
||||
|
||||
## Обзор
|
||||
|
||||
Система знаний на основе ИИ для автоматического извлечения, хранения и поиска информации из документов и данных.
|
||||
|
||||
@ -42,14 +42,7 @@
|
||||
- **Рёбра**: Связи между сущностями
|
||||
- **Embeddings**: Векторные представления для семантического поиска
|
||||
|
||||
**Пример:**
|
||||
```typescript
|
||||
import { buildKnowledgeGraph } from '@/lib/ai/knowledge-graph';
|
||||
|
||||
const graph = await buildKnowledgeGraph();
|
||||
// graph.nodes - узлы графа
|
||||
// graph.edges - связи между узлами
|
||||
```
|
||||
**Пример:** Модуль нормативной базы (knowledge) вынесен в отдельный сервис; граф знаний доступен через внешний API.
|
||||
|
||||
### 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` - поисковый запрос (опционально)
|
||||
- `format` - формат ответа: `json` или `visualization`
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ const result = await bulkheads.ai.execute(async () => {
|
||||
**Глобальные bulkheads:**
|
||||
- `ai` - 5 одновременных запросов
|
||||
- `database` - 20 одновременных запросов
|
||||
- `knowledgeGraph` - 2 построения графа одновременно
|
||||
- Модуль нормативной базы (knowledge) вынесен в отдельный сервис; лимит `knowledgeGraph` не используется в этом репозитории.
|
||||
- `fileProcessing` - 3 файла одновременно
|
||||
|
||||
### 3. Retry с Exponential Backoff
|
||||
|
||||
@ -2,6 +2,6 @@
|
||||
|
||||
Домен: Учет ТМЦ (TMC Registry)
|
||||
|
||||
- Использовать только утверждённые шаблоны и регламенты из `knowledge/`.
|
||||
- Модуль нормативной базы (knowledge) вынесен в отдельный сервис; использовать только утверждённые шаблоны и регламенты, доступные через него.
|
||||
- Все поля должны соответствовать структурам, описанным в словарях и спецификациях проекта.
|
||||
- При работе с ТМЦ приоритет имеют документы с `domain = tmc` в `manifest.json`.
|
||||
|
||||
@ -1,170 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Скрипт для добавления всех файлов из knowledge/ в manifest.json
|
||||
* Приводит все записи к единому формату с обязательными полями
|
||||
* Раньше: добавление файлов из knowledge/ в manifest.json.
|
||||
* Модуль нормативной базы (knowledge) вынесен в отдельный сервис.
|
||||
* Скрипт сохранён как заглушка для совместимости с CI/скриптами.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
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();
|
||||
console.log('Модуль нормативной базы (knowledge) вынесен в отдельный сервис. Обновление manifest по knowledge/ пропущено.');
|
||||
process.exit(0);
|
||||
|
||||
@ -20,7 +20,7 @@ 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');
|
||||
// Модуль knowledge вынесен в отдельный сервис — проверка файлов в knowledge/ отключена
|
||||
|
||||
// Обязательные поля документа
|
||||
const REQUIRED_FIELDS = ['id', 'title', 'path', 'type', 'domain', 'version', 'status'];
|
||||
@ -154,9 +154,6 @@ function validateManifest() {
|
||||
}
|
||||
});
|
||||
|
||||
// Проверка: все файлы из knowledge/ должны быть в manifest
|
||||
checkKnowledgeFilesInManifest(manifest, documentPaths);
|
||||
|
||||
printResults();
|
||||
|
||||
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() {
|
||||
if (errors.length > 0) {
|
||||
console.log('❌ Ошибки:\n');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user