Нет — в фаза 2 я имел в виду более скромный вариант: бэкенд остаётся на Python, а на Scala пишется только ETL-приложение, которое валидирует партии движком и отправляет их в существующий API. Но ваш вопрос — правильная эскалация этой идеи, и решить его нужно именно сейчас, до фазы 0: иначе мы вложим тесты и наведение порядка в Python-стек, который потом выбросим.

Давайте честно взвесим.

Аргументы за переписывание на Scala 3

1. Ворота в базу должны валидировать сами. Это главный архитектурный аргумент. Если POST /api/games написан на Python, движок не может работать внутри процесса API — валидация остаётся на совести клиентов (ETL, боты). Получается «источник истины», которому верят на слово. Со Scala-бэкендом каждая партия, входящая в базу, проверяется движком как библиотекой: легальность ходов, правило максимума микро-ходов, корректность FEN. Граница доверия встаёт на правильное место.

2. Сейчас — самый дешёвый момент. Живого кода в Python-бэкенде мало: два read-роутера, модели, схемы — порядка 600 строк (importer.py вы сами списали в утиль). Тестов нет, то есть терять нечего. Через полгода развития аналитики это будет уже не «переписать за несколько дней», а «миграция на месяц». Тот же аргумент, что и со SvelteKit.

3. Схлопывание стеков. Сейчас у вас три языка: Scala (движок), Python (analytics), TypeScript (UI). После переписывания — два. Причём Scala-стек бэкенда естественно собирается из того, что движок уже использует: Circe (JSON), Cats, MUnit. Одна культура тестирования, один sbt-дисциплинарный набор (-Werror, порог покрытия), который у вас отлажен лучше всего.

4. Будущие фичи этого захотят. Матожидание по позициям, серверный анализ партий, гибрид «эмпирика из базы + оценка движка» — всё это работа движка на сервере. С Python пришлось бы городить мост (subprocess/HTTP к JVM); со Scala это просто вызов функции.

Аргументы против (честно)

  • FastAPI даёт OpenAPI-документацию и скорость итераций бесплатно. В Scala это закрывается Tapir (типизированные эндпоинты → автогенерация OpenAPI/Swagger UI), но церемоний больше.
  • Работа на парность: перенос двух роутеров, Alembic → Flyway, Dockerfile, CI — реалистично несколько дней до паритета, плюс POST /api/games. Это дни, не съеденные позиционной аналитикой.
  • Классический риск «переписывание вместо продукта» — но здесь он смягчён крошечным объёмом и тем, что UI полностью развязан: REST-контракт и схема PostgreSQL не меняются, фаза 1 (SvelteKit, эксплорер) идёт параллельно и ничего не ждёт.

Моя рекомендация

Для вашей ситуации — да, переписывать, и именно сейчас. Конкретный стек, максимально совпадающий с движком:

СлойИнструментЗамечание
HTTP + контрактыhttp4s + TapirTapir = типобезопасный аналог FastAPI с автогенерацией OpenAPI
БДDoobie + Flywayсхема PostgreSQL остаётся как есть, Flyway берёт baseline от текущей
JSONCirceуже в движке
ТестыMUnit + testcontainers-scalaнастоящий PostgreSQL в тестах с первого дня
ДвижокJVM-артефакт dicechess-engineон уже кросс-собирается; опубликовать в GitHub Packages как maven-зависимость

Поправка к дорожной карте: фаза 0 превращается в «бутстрап Scala-бэкенда с тестами с первого дня», баг с rating_classic умирает естественной смертью при переносе (а если /players нужен UI раньше — в Python это однострочный фикс, можно сделать не дожидаясь). Фаза 2 упрощается: ETL становится ещё одним модулем того же Scala-проекта, а не отдельным решением.

Переписываем ли бэкенд dicechess-analytics на Scala 3?


Отлично, решение зафиксировано — обновлю память проекта.

Решение принято: бэкенд переписываем на Scala 3. Да, вы правильно поняли направление мысли — и довели его до логического конца: движок как библиотека прямо внутри API, а не только в ETL.

Итоговая дорожная карта с учётом решения:

Фаза 0 — Scala-бэкенд до паритета. В том же репозитории dicechess-analytics (схема БД, docs, docker-compose остаются): sbt-проект по образцу движка (-Werror, порог покрытия), http4s + Tapir + Doobie + Flyway (baseline от текущей схемы), перенос GET /api/games, GET /api/games/{id}, GET /api/players — с тестами на testcontainers с первого коммита. Баг rating_classic решаем правильно сразу: рейтинг игрока как производная от его последней партии. Параллельно — опубликовать движок как JVM-артефакт в GitHub Packages (sbt-publish у вас наверняка почти готов, npm-пакет уже выходит оттуда же).

Фаза 1 — UI. Миграция на SvelteKit (adapter-static), @rabestro/dicechess-engine вместо fenHelper.ts, тесты на реконструкцию состояний, поиск/фильтры по 140k партий. Идёт параллельно с фазой 0 — контракт не меняется.

Фаза 2 — приёмный тракт. POST /api/games с валидацией движком in-process, ETL с dicechess.com как модуль того же Scala-проекта, боты пишут в тот же эндпоинт.

Фаза 3 — позиционная аналитика. Агрегаты по позициям (матожидание с учётом ставок), позиционный эксплорер с разрезом по броскам костей.

Когда будете готовы начинать — могу стартовать фазу 0: каркас Scala-проекта с CI и первым тестируемым эндпоинтом, либо сперва публикацию движка как maven-артефакта (она на критическом пути). Скажите, с чего заходим, и работаем.