feat(stats): переписать дашборд статистики с Flet на React

stats/ (Flet) генерировал графики через matplotlib в PNG и отдавал их
через Flutter web с WebSocket — та же архитектура, что подводила
основной фронтенд (см. известные ошибки в README). Плавающая версия
flet>=0.82.2 недавно подтянула 0.86.0 с несовместимым протоколом
фреймирования, и дашборд перестал открываться в браузере.

stats-react/ — SPA по тому же паттерну, что и web-react (Vite+React+TS,
свой Dockerfile+nginx, same-origin прокси /api вместо WebSocket).
Функциональность 1:1: 4 summary-карточки, общие фильтры (тест/
организация/группа/год), два таба (прохождения / результаты тестов),
8 графиков, топ-10 таблица, фильтр по оси + сравнение по организациям/
группам. Круговая диаграмма заменена на 100%-stacked bar (dataviz:
"part-to-whole rides on the stacked bar chart; donut stays
deprioritized") с фолдингом длинного хвоста категорий в "Другое" —
у оригинала было 32 категории на 8 цветов, из-за чего сегменты
становились неразличимы.

Категориальная палитра графиков провалидирована на CVD-безопасность
(scripts/validate_palette.js, light+dark) — 8-цветная палитра из
исходного designer.py не проходила проверку (hard fail по
normal-vision floor). Бренд-оранжевый сохранён в палитре, остальные
7 слотов — из референсного набора skill'а.

stats/ и Dockerfile.stats удалены, docker-compose.yml переключён на
stats-react. Проверено сборкой всех трёх образов, полным compose up
и визуально (headless Chrome, light+dark, обе вкладки, реальные
данные с прод-сервера).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-07-16 14:11:06 +05:00
parent 1728450778
commit eb110f677c
38 changed files with 3583 additions and 1239 deletions

View File

@@ -0,0 +1,43 @@
// Палитра дашборда. Бренд-цвета — из web-react/src/theme/themes.ts (designer.py).
// Категориальная 8-цветная палитра для графиков (pie/multi-bar) провалидирована
// dataviz-скиллом (scripts/validate_palette.js) на CVD-безопасность в light и
// dark режимах — порядок слотов и hex не менять без повторной валидации.
// Единственная замена относительно референсного набора: слот "orange" — бренд-оранжевый.
export const BRAND = {
primary: "#F85A40",
primaryDark: "#D94530",
primaryLight: "#FFEAB7",
accent: "#FFC845",
info: "#007FBD",
success: "#85C446",
};
// [PASS] все проверки, light: surface #FFFFFF (WARN contrast на magenta/yellow/aqua — компенсируется прямыми подписями значений)
export const CATEGORICAL_LIGHT = [
"#2a78d6", // blue
"#008300", // green
"#e87ba4", // magenta
"#eda100", // yellow
"#1baf7a", // aqua
"#F85A40", // orange (бренд)
"#4a3aa7", // violet
"#e34948", // red
];
// [PASS] все проверки, dark: surface #1a1a19
export const CATEGORICAL_DARK = [
"#3987e5",
"#008300",
"#d55181",
"#c98500",
"#199e70",
"#D94530", // orange (бренд, тёмный шаг)
"#9085e9",
"#e66767",
];
export function categoricalColor(index: number, dark: boolean): string {
const arr = dark ? CATEGORICAL_DARK : CATEGORICAL_LIGHT;
return arr[index % arr.length];
}