Понял расстановку приоритетов, сохранил её в память. Сначала отвечу на вопрос про SvelteKit, потом план.

SvelteKit: да, и переходить стоит именно сейчас

Моя рекомендация — мигрировать на SvelteKit с adapter-static и отключённым SSR (режим SPA). Аргументы:

  1. Вам неизбежно нужен роутинг с глубокими ссылками. Программа для работы с базой партий без URL вида /games/{id}, /positions/{hash}, /players/{id} — неполноценна: нельзя поделиться позицией, нельзя открыть партию в новой вкладке, кнопка «назад» не работает. Сейчас переключение видов живёт в App.svelte руками — это первое, что перестанет масштабироваться при добавлении Openings/Players/Positions.
  2. SvelteKit ≠ SSR. С adapter-static + ssr = false вы получаете file-based роутинг, layout-композицию, load-функции для загрузки данных по маршрутам и соглашения по структуре — а на выходе те же статические файлы для вашего nginx. В nginx.conf почти ничего не меняется (SPA-fallback у вас уже настроен). FastAPI остаётся единственным бэкендом.
  3. Цена миграции сейчас — около дня (проекту день от роду, ~1.9k строк, три стора и восемь компонентов переносятся почти как есть). Через полгода это будут недели.
  4. Это официально рекомендуемый способ строить Svelte-приложения — голый шаблон Vite сама команда Svelte позиционирует для особых случаев. Раз цель «лучшие практики» — это и есть лучшая практика.

Единственный случай, когда я бы сказал «не надо» — если бы UI навсегда остался одноэкранной смотрелкой. Ваша цель противоположная.

Дорожная карта

Фаза 0 — починить и подвести фундамент качества (analytics)

Это короткая фаза, но она разблокирует всё остальное.

  • Починить GET /api/players: у Player нет рейтинга — он есть только как снимок в games. Минимальный фикс — убрать сортировку по rating_classic и Optional-поле из схемы; правильное решение чуть позже — view/materialized view «текущий рейтинг игрока = рейтинг из его последней партии».
  • Поднять pytest-инфраструктуру на настоящем PostgreSQL (testcontainers): схема использует ARRAY, JSONB и GIN trigram — на SQLite это не тестируется честно. Базовый набор: smoke-тест на каждый эндпоинт (он один поймал бы текущий баг), тесты репозиторных запросов.
  • Один движок БД вместо трёх: централизовать создание engine в database.py.
  • CI-гейт: тесты + ruff + mypy обязательны для merge (по образцу того, что у вас уже отлажено в lab и engine).

Фаза 1 — UI: миграция на SvelteKit + тесты на ядро

  • Перенос на SvelteKit (adapter-static): маршруты /games, /games/[id], дальше /players, /positions.
  • Заменить самописный fenHelper.ts на @rabestro/dicechess-engine — пакет уже существует и используется в lab, так что это прямое воплощение принципа «движок — источник истины». Псевдолегальное применение ходов уходит, появляется настоящая валидация.
  • Вынести и покрыть тестами reconstructStates() из activeGameStore (493 строки — самое рискованное место UI). Инвариант для проверки: каждое следующее состояние получается применением хода к предыдущему через движок.
  • Обработка ошибок API (retry, понятные состояния загрузки) и поиск/фильтры в списке партий — это первая «продуктовая» ценность поверх 140k партий.

Фаза 2 — приёмный тракт данных (вместо одноразового импортёра)

  • POST /api/games в analytics: транзакционный, идемпотентный (игра + ходы + события атомарно), с валидацией. Это единые ворота в базу для всего.
  • Новый ETL с dicechess.com. Здесь развилка по языку — единственное серьёзное архитектурное решение фазы:
    • Вариант A (мой фаворит при вашем принципе): Scala-модуль/приложение, использующее движок как библиотеку напрямую — каждая партия перед отправкой в API прогоняется через настоящую валидацию (легальность ходов, правило максимума микро-ходов, корректные FEN). Знание API сайта переносится из game_sync.py лаборатории.
    • Вариант B: Python-ETL рядом с analytics, валидация через JS/CLI-обёртку движка. Быстрее стартовать, но логика опять размазывается.
  • Боты пишут в ту же трубу: dicechess-bots отправляют сыгранные партии в тот же POST /api/games. Один тракт — одни правила валидации.

Фаза 3 — позиционная аналитика (ядро всей затеи)

То, ради чего база и существует, и где Dice Chess принципиально отличается от шахмат:

  • Агрегаты по позициям: для position_id — число партий, распределение результатов, эмпирическое матожидание (с учётом ставок/x2 — данные о деньгах в схеме уже есть). При 140k партий × ~40 ходов это миллионы строк в turns — нужны materialized views или таблицы-агрегаты с инкрементальным обновлением при ингесте.
  • Позиционный эксплорер в UI: доска + таблица продолжений а-ля opening explorer Lichess/ChessBase, но с разрезом по броску костей — «при броске NNB из этой позиции играли так-то с таким-то матожиданием». Это фича, которой нет ни у кого.
  • Дальше сюда же ложится гибрид: эмпирическая статистика из базы + оценка движка (когда дозреет expectiminimax) для позиций с малой выборкой.

Сквозное правило

Каждая фаза заканчивается тестами и CI-гейтом, а не «допишем потом» — иначе повторится история analytics, где самый важный компонент оказался единственным без тестов.

Что предлагаю решить сейчас

  1. SvelteKit — согласны с миграцией в фазе 1?
  2. Язык ETL — Scala с движком как библиотекой (вариант A) или Python (вариант B)?
  3. Порядок фаз 1 и 2 — я бы сделал UI раньше ETL: 140k партий уже лежат в базе, и эксплорер по ним даст ценность немедленно, а свежие партии никуда не убегут (или убегут? если API сайта отдаёт только недавнюю историю — тогда ETL поднимается в приоритете).

Скажите, что думаете по этим трём пунктам — и можно начинать с фазы 0: баг с rating_classic и тестовый каркас в analytics я могу сделать прямо в этой сессии.