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
|
# OS
|
||||||
.DS_Store
|
.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: {
|
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 для работы с базой знаний',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
# AI-Powered Knowledge System
|
# AI-Powered Knowledge System
|
||||||
|
|
||||||
|
> **Примечание.** Модуль нормативной базы (knowledge) вынесен в отдельный сервис. Описание ниже сохранено как справочное; эндпоинты `/api/knowledge/*` в данном репозитории (КЛГ АСУ ТК) не реализованы.
|
||||||
|
|
||||||
## Обзор
|
## Обзор
|
||||||
|
|
||||||
Система знаний на основе ИИ для автоматического извлечения, хранения и поиска информации из документов и данных.
|
Система знаний на основе ИИ для автоматического извлечения, хранения и поиска информации из документов и данных.
|
||||||
|
|||||||
@ -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`
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
Домен: Учет ТМЦ (TMC Registry)
|
Домен: Учет ТМЦ (TMC Registry)
|
||||||
|
|
||||||
- Использовать только утверждённые шаблоны и регламенты из `knowledge/`.
|
- Модуль нормативной базы (knowledge) вынесен в отдельный сервис; использовать только утверждённые шаблоны и регламенты, доступные через него.
|
||||||
- Все поля должны соответствовать структурам, описанным в словарях и спецификациях проекта.
|
- Все поля должны соответствовать структурам, описанным в словарях и спецификациях проекта.
|
||||||
- При работе с ТМЦ приоритет имеют документы с `domain = tmc` в `manifest.json`.
|
- При работе с ТМЦ приоритет имеют документы с `domain = tmc` в `manifest.json`.
|
||||||
|
|||||||
@ -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();
|
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user