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>
44 lines
1.6 KiB
TypeScript
44 lines
1.6 KiB
TypeScript
// Палитра дашборда. Бренд-цвета — из 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];
|
|
}
|