Техническая документация
Архитектура
Приложение построено на Next.js 16 (App Router) с использованием серверных и клиентских компонентов. API реализован через Route Handlers. Тёмная тема реализована через CSS-переменные и @custom-variant dark (Tailwind 4).
app/
├── api/ # API Route Handlers
│ ├── segments/ # CRUD сегментов + overview отчёт
│ ├── programs/ # CRUD программ + overview отчёт
│ ├── initiatives/ # CRUD инициатив
│ ├── tasks/ # CRUD задач
│ ├── relations/ # EntityRelation CRUD
│ ├── history/ # EntityHistory (GET)
│ ├── decision-forks/ # DecisionFork CRUD
│ ├── milestones/ # Milestone CRUD (с toggle completed)
│ ├── qa/ # QAItem CRUD
│ ├── health/ # Расчёт здоровья (инициативы, программы, сегменты)
│ ├── health-trend/ # Снимки здоровья для графика тренда
│ ├── attention/ # Элементы, требующие внимания
│ ├── stats/ # Агрегированная статистика для дашборда
│ ├── settings/ # Настройки (GET/PUT)
│ └── excel/ # Импорт/экспорт Excel
├── segments/ # Страницы сегментов
├── programs/ # Страницы программ
├── initiatives/ # Страницы инициатив
├── tasks/ # Страницы задач
├── graph/ # Страница графа
├── roadmap/ # Страница роадмапа
├── settings/ # Настройки (тема, Jira, типы связей, блоки отчёта)
└── docs/ # Документация
components/
├── graph/ # GraphView, CustomNode, CustomEdge, FilterPanel, Legend
├── roadmap/ # RoadmapTimeline
├── layout/ # Sidebar, Header, SidebarContext, LayoutShell
├── providers/ # ThemeProvider
└── ui/ # Card, Badge, Modal, RelationsSection, QABlock,
# HealthBadge, HealthTrendChart и др.
lib/
├── db.ts # Prisma-клиент (PrismaPg adapter)
├── health.ts # Расчёт здоровья (инициативы, программы, сегменты, каскад)
├── progress.ts # Расчёт прогресса (инициативы, программы)
├── constants.ts # Метки, цвета, статусы, матрица влияния
└── relations.ts # Навигация по иерархии (getTaskInitiativeId и др.)Модель данных (Prisma)
ORM: Prisma 7 с адаптером @prisma/adapter-pg. Подключение через PrismaPg в lib/db.ts.
InvestmentSegment
├── id, name, description, status (LifecycleStatus), owner, label (EntityLabel)
├── startDate, endDate
├── asIs, limitations, targetState, coreDescription
├── impact (Json), attributes (String[])
├── jiraKey (String?)
├── healthStatus, healthIssues (Json), healthUpdatedAt
├── manualStatus, manualStatusDescription
├── manualStatusProblems (Json), manualStatusUpdatedAt
└── programs: Program[]
Program
├── segmentId → InvestmentSegment
├── (те же поля: status, owner, label, dates, narrative, health, manual...)
├── jiraKey (String?)
└── initiatives: Initiative[]
Initiative
├── programId → Program
├── healthStatus, healthIssues (Json), healthUpdatedAt
├── manualStatus, manualStatusDescription
├── manualStatusProblems (Json), manualStatusUpdatedAt
├── risks (Json) — массив {id, description, severity, mitigationDate, status}
├── jiraKey (String?)
├── tasks: Task[]
└── milestones: Milestone[]
Task
├── initiativeId → Initiative
├── id, name, description, status, owner, startDate, endDate
└── jiraKey (String?)
Milestone
├── description, date, completed (Boolean)
└── initiativeId → Initiative (cascade delete)
EntityRelation (полиморфная)
├── sourceType, sourceId → targetType, targetId
├── type: PART_OF | PREREQUISITE | ENABLES
├── sourceAnchor, targetAnchor (default "FINISH"/"START")
└── decisionForkId, decisionForkOptionIdx (условные связи)
EntityHistory (полиморфная)
├── entityType, entityId, entityName
├── field, oldValue, newValue
└── changedAt, changedBy
DecisionFork (полиморфная)
├── entityType, entityId
├── question, status: OPEN | RESOLVED | DEFERRED
├── date (DateTime?)
└── options (Json) — массив {title, approved}
QAItem (полиморфная)
├── entityType, entityId
├── question, answer
└── createdAt
HealthSnapshot (тренды здоровья)
├── entityType, entityId
├── healthStatus, manualStatus
└── snapshotDate (с кулдауном 1 час)
Settings (key-value)
├── key (unique)
└── value (Json)Enum LifecycleStatus: DRAFT, PLANNED, IN_PROGRESS, ON_HOLD, COMPLETED, CANCELLED
Enum EntityLabel: IT_STRATEGY, IT_INITIATIVE, BUSINESS_INITIATIVE, EXTERNAL_FACTOR
Enum RelationType: PART_OF, PREREQUISITE, ENABLES
Enum DecisionForkStatus: OPEN, RESOLVED, DEFERRED
API Endpoints
| Метод | Путь | Описание |
|---|---|---|
| GET | /api/segments | Список сегментов |
| POST | /api/segments | Создать сегмент |
| GET | /api/segments/[id] | Получить сегмент |
| PUT | /api/segments/[id] | Обновить сегмент |
| DELETE | /api/segments/[id] | Удалить сегмент |
| GET | /api/segments/[id]/overview | HTML-отчёт по сегменту |
| GET/POST | /api/programs | Список / создание программ |
| GET/PUT/DEL | /api/programs/[id] | CRUD программы |
| GET | /api/programs/[id]/overview | HTML-отчёт по программе |
| GET/POST | /api/initiatives | Список / создание инициатив |
| GET/PUT/DEL | /api/initiatives/[id] | CRUD инициативы |
| GET/POST | /api/tasks | Список / создание задач |
| GET/PUT/DEL | /api/tasks/[id] | CRUD задачи |
| GET/POST | /api/relations | Связи (фильтр entityType/entityId) |
| PUT/DEL | /api/relations/[id] | Обновить/удалить связь |
| GET/POST | /api/milestones | Вехи (фильтр initiativeId) |
| PUT/DEL | /api/milestones/[id] | Обновить (toggle completed) / удалить веху |
| GET | /api/history/[type]/[id] | История изменений сущности |
| GET/POST | /api/decision-forks | Развилки (фильтр entityType/entityId) |
| GET/PUT/DEL | /api/decision-forks/[id] | CRUD развилки |
| GET/POST | /api/qa | Q&A (фильтр entityType/entityId) |
| POST | /api/health | Расчёт здоровья (инициативы + программы + сегменты) |
| GET | /api/health-trend | Снимки здоровья (entityType, entityId, days) |
| GET | /api/attention | Элементы, требующие внимания (severity фильтр) |
| GET | /api/stats | Статистика для дашборда |
| GET/PUT | /api/settings | Настройки приложения |
| GET | /api/excel/export | Экспорт в Excel |
| POST | /api/excel/import | Импорт из Excel (multipart) |
Система здоровья (health.ts)
Расчёт здоровья реализован в lib/health.ts. Endpoint: POST /api/health.
// Расчёт одной инициативы
POST /api/health { initiativeId: "..." }
// Расчёт всех (инициативы → программы → сегменты)
POST /api/health {}
Инициативы
Функция evaluateHealth() выполняет 14 проверок RED + 12 проверок YELLOW. Проверяются: даты, вехи, задачи, зависимости (PREREQUISITE/ENABLES), развилки, риски, актуальность ручного статуса. Результат записывается в поля:
- healthStatus — GREEN | YELLOW | RED
- healthIssues — JSON-массив {code, severity, message}
- healthUpdatedAt — дата/время расчёта
Программы и сегменты
Функция evaluateEntityHealth() агрегирует здоровье дочерних элементов по принципу worst-case (наихудший статус) + собственные проверки (даты, ответственный, ручной статус). Программы агрегируют инициативы, сегменты агрегируют программы.
Каскад и снимки
recalculateHealthCascade() — неблокирующий фоновый пересчёт вверх по иерархии при изменении сущности. При каждом пересчёте создаётся HealthSnapshot с кулдауном 1 час (recordSnapshot()).
Оптимизация
getEntityInfoBatch() — пакетная загрузка статусов и имён сущностей. Группирует по типу и выполняет 4 параллельных запроса вместо N отдельных, устраняя N+1 проблему.
Расчёт прогресса (progress.ts)
Реализован в lib/progress.ts.
Инициатива
Веса: задачи = 0.7, вехи = 0.3
progress = round((completedTasks/totalTasks × 0.7 + passedMilestones/totalMilestones × 0.3) × 100)
// Нет задач → 100% вехи; нет вех → 100% задачи; нет ничего → 0%
Программа
Система внимания (attention)
Endpoint: GET /api/attention. Собирает элементы, требующие действий.
| Тип | Severity | Триггер |
|---|---|---|
| RED_HEALTH | RED | effective health = RED |
| OVERDUE | RED | endDate < now, status ≠ COMPLETED/CANCELLED |
| MANUAL_PROBLEM | RED | manual problem severity=RED, resolved=false |
| HIGH_RISK | RED | risk severity=HIGH, status=OPEN |
| OPEN_FORK | YELLOW | fork OPEN > 14 days |
Сортировка: RED → YELLOW. Каждый элемент содержит ID, имя инициативы, программу, тип и описание.
Тренд здоровья (health-trend)
Endpoint: GET /api/health-trend?entityType=...&entityId=...&days=90.
Возвращает массив снимков HealthSnapshot за указанный период (по умолчанию 90 дней). Каждый снимок содержит: date, healthStatus, manualStatus. Отображается компонентом HealthTrendChart (Recharts).
Запуск и развёртывание
Требования
- Node.js 18+
- Docker (для PostgreSQL)
- npm
Переменные окружения
Команды
docker compose up -d postgres # Запуск БД (порт 5433)
npm install # Установка зависимостей
npm run db:migrate # Миграции Prisma
npm run dev # Dev-сервер (http://localhost:3000)
Конфигурация Prisma 7
prisma.config.ts— URL для миграцийlib/db.ts—PrismaPg({ connectionString })для runtimenext.config.ts—serverExternalPackages: ["@prisma/client", "@prisma/adapter-pg", "pg"]
Ключевые зависимости
@xyflow/react — граф (React Flow v12)dagre — автоматическая раскладка графаrecharts — диаграммы (дашборд, тренд здоровья)exceljs — генерация и парсинг Exceldate-fns — форматирование дат@prisma/client — ORM (Prisma 7)@prisma/adapter-pg — PostgreSQL адаптерlucide-react — иконкиclsx / tailwind-merge — утилиты CSS-классовТёмная тема
Тема управляется через ThemeProvider, который переключает класс .dark на document.documentElement.
- CSS-переменные в
globals.css: --background, --foreground, --card-bg, --border, --muted, --accent и др. - Директива
@custom-variant dark (&:where(.dark, .dark *))— необходима для Tailwind 4, чтобыdark:утилиты работали с классом .dark - Специальные стили для Recharts (fill), ReactFlow (CSS-переменные --xy-*) и скроллбаров
Полиморфный паттерн
Несколько моделей используют полиморфный паттерн entityType + entityId вместо отдельных FK для каждого типа. Это позволяет привязывать записи к любому типу сущности:
- EntityRelation — связи между любыми парами объектов
- EntityHistory — аудит-лог изменений всех сущностей
- DecisionFork — развилки решений на инициативах и задачах
- QAItem — блоки вопрос-ответ на любых объектах
- HealthSnapshot — снимки здоровья всех типов