О проекте
Команда: Ultima
Кейс HackAlem.ai: Спецтрек — Astana Innovations
Стадия: Доведённый продукт
Задача кейса: «Аким на 5 часов»: городской симулятор, распределяющий бюджет по 5 направлениям и 5 районам
Суть
Интерактивный симулятор городского бюджета с анимированной SVG-картой Астаны: выбор 5 мер из 14 по районам, расчёт Score, AI-разбор, предложения замен, сравнение «План А / План Б», общая таблица команд и автопрезентация.
Как устроено
FastAPI отдаёт статический фронтенд на vanilla JS и API. engine.py считает показатели с лагами, синергиями и несовместимостями по simulation.json, recommender.py проверяет варианты замены одной меры или переноса в другой район. Для AI-разбора сервер строит каталог фактов из расчёта, LLM (OpenAI Chat Completions, gpt-4.1-mini, Structured Outputs) возвращает только ID фактов и их порядок, а формулировки и числа подставляет сервер. Неизвестный ID ведёт к локальному разбору по правилам. Результаты команд хранятся в SQLite с кодом редактирования.
Ключевые решения
- deterministic scoring model
- single-swap improvement search
- fact-ID grounded explanation
- Structured Outputs
- SQLite leaderboard
Метрики
Контрольный пример: расход 95, Score 56,54; замена M5 Сарыарка → M3 Нура даёт 57,21 (+0,67)
Особенности
Произвольный текст LLM в интерфейс не попадает: модель выбирает только ID фактов из серверного каталога, у каждого вывода раскрывается «Основание расчёта». Анимированная карта Астаны с объектами всех 14 инициатив и проверкой коллизий, лидерборд команд.
Стек
Python · FastAPI · JavaScript · SVG · SQLite · pytest · Ruff · GitHub Actions
Модели
gpt-4.1-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-5b2fd688-ultima.
README
README.md«Аким на 5 часов» - симулятор городских решений
Интерактивный AI-симулятор распределения городского бюджета. Выберите пять инициатив для города и его пяти районов, проверьте бюджет и увидьте, как меняются показатели и Astana Quality of Life Score.
Все районы, показатели и эффекты мероприятий в проекте синтетические и используются только для демонстрации модели.
Возможности
- одинаковые для всех исходные данные и бюджет в 100 условных единиц;
- обзор города с карточками районов: по клику доступны профиль и все десять исходных показателей;
- интерактивная схема Астаны: выбор района и мер прямо на карте, появление ЛРТ, деревьев, школ и других проектов;
- каталог из 14 городских инициатив по пяти направлениям;
- выбор ровно пяти мер, районов для районных проектов и автоматическая проверка ограничений;
- учёт стоимости, лага, синергий, несовместимостей и пределов показателей 0–100;
- раскрываемая формула Score: вклады среднего уровня, слабейшего района и критических показателей до/после;
- сравнение баллов пяти районов, раскрытие изменений показателей и AI-разбор;
- проверенные моделью варианты улучшения плана: замена одной меры или перенос её в другой район;
- личное сравнение «План А / План Б» с бюджетом, решениями, районными баллами и потерянными эффектами;
- загрузка контрольного примера одной кнопкой с объяснением выбора всех пяти мер;
- общая таблица результатов нескольких команд с деталями решений и баллами районов;
- автономная презентация из трёх слайдов с возможностью печати в PDF;
- список оставшихся показателей ниже критического порога и эффекты мер с учётом лага;
- локальное объяснение продолжает работать, если AI API-ключ не задан или сервис недоступен;
- выбранные решения сохраняются в браузере на время следующего визита.
Пользователь выбирает ровно 5 разных мероприятий из каталога пяти направлений. По правилам предоставленного датасета допускается максимум 2 мероприятия одного направления, поэтому план охватывает минимум 3 направления; по одному из каждого выбирать не обязательно.
Запуск
Требуются Python 3.10+ и доступ к установке пакетов из requirements.txt. У фронтенда нет отдельного шага сборки: FastAPI раздаёт клиентские файлы и API с одного адреса.
Linux / macOS
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python -m uvicorn backend.app.main:app --reload
Windows PowerShell
py -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
python -m uvicorn backend.app.main:app --reload
Откройте http://127.0.0.1:8000. Интерактивная документация API доступна по адресу http://127.0.0.1:8000/docs.
AI-разбор для демонстрации
AI-анализ - обязательная часть задания. Для демонстрации живого LLM-анализа нужен действующий серверный ключ OpenAI Chat Completions. Без него приложение рассчитывает тот же Score, но показывает явно помеченное объяснение по правилам (source="rules"); оно не является ответом LLM. Чтобы включить настоящий AI-разбор:
cp .env.example .env
В .env задайте OPENAI_API_KEY; при необходимости измените OPENAI_MODEL (по умолчанию gpt-4.1-mini). Перезапустите сервер после изменения переменных окружения. Файл .env исключён из Git. Ключ используется только сервером и не передаётся браузеру. На машине демонстрации рассчитайте контрольный сценарий и проверьте метку «ИИ-РАЗБОР» (explanation.source="openai") и содержательность текста. При ошибке провайдера будет метка «ПО ДАННЫМ МОДЕЛИ» (source="rules"); Score не изменится. Проверки с имитацией ответов не заменяют живой вызов.
AI получает каталог утверждений, построенных сервером из рассчитанного сценария: улучшения показателей, слабые места, лаги, синергии и компромиссы допустимых альтернатив. LLM выбирает приоритеты и порядок выводов для резюме, сильных сторон, рисков, последствий и рекомендаций. Формулировки и числа подставляет сервер из этого каталога.
Ответ провайдера содержит только ID фактов. Structured Outputs ограничивает ответ JSON-схемой с разрешёнными ID и количеством пунктов; сервер дополнительно проверяет структуру, допустимость раздела и повторы. Неизвестный факт или произвольный текст приводит к локальному разбору. У каждого вывода можно раскрыть «Основание расчёта»: значения и источники из модели. В /api/analyze эти основания доступны в explanation.evidence, а режим — в explanation.grounding. Это проверяемая сборка анализа: произвольные формулировки LLM не попадают в интерфейс. Релевантность выбранных AI акцентов всё равно оценивает пользователь.
Карта решений
В разделе «Карта решений» выберите район на схеме или в списке, затем направление и проект в палитре. Перед добавлением сервер проверяет бюджет и совместимость, а на карте появляется полупрозрачный эскиз будущего проекта. Кнопка «Добавить за … ед.» включает его в общий план.
ЛРТ и автобусы движутся по протяжённым условным маршрутам внутри района. Парки, школы, медцентры и другие объекты встроены в городскую сцену с кварталами, деревьями, набережными и ориентирами. В районе Алматы размещён Дворец мира и согласия. Все 14 инициатив имеют свою визуализацию.
Для зданий, подписей, ориентиров и транспортных маршрутов предусмотрены отдельные зоны. Транспорт идёт между конечными остановками и возвращается обратно; река и декорации набережной рисуются под проектами. Проверка плотных планов контролирует пересечения площадок, подписей, реки и движущихся машин во всех пяти районах на компьютере и телефоне.
- Клик по объекту открывает его меру и фактическое место в плане; кнопка «Убрать из плана» удаляет проект. «Показать проект на карте» возвращает к нему. Выбор синхронизирован с каталогом, бюджетом, примером, альтернативами и сохранёнными планами.
- Городская мера отображается во всех пяти районах, но занимает один слот и оплачивается один раз.
- Баллы на карте остаются исходными до нажатия «Рассчитать сценарий». После расчёта показаны итоговые районные баллы; при изменении решений карта возвращается к исходным значениям.
- «Исходный город / Ваш план» позволяет сравнивать состояние без изменения решений. В исходном виде проекты скрыты, а баллы берутся из базового отчёта.
- «Проекты / Баллы районов» переключает обычную сцену и цветовой слой районных баллов с легендой. Цвет района не равен количеству критических показателей.
- Кнопки масштаба, перетаскивание мышью, приближение района и «Развернуть карту» помогают рассмотреть город. Развёрнутый режим закрывается по Escape и удерживает клавиатурный фокус внутри карты.
- «Пауза» останавливает анимации. Системная настройка уменьшения движения также поддерживается.
- На телефоне панель выбора находится сразу после сцены; добавленный проект автоматически показывается на карте. Список выбранных проектов свёрнут, эффекты и сроки раскрываются отдельно.
- Несовместимая мера блокируется до добавления с объяснением причины. Бюджетная полоска показывает текущие расходы и стоимость эскиза. Если допустимый промежуточный выбор оставит слишком мало денег даже на самые дешёвые свободные меры, интерфейс пояснит необходимость замены решений.
- Сброс и загрузка другого плана отменяют ожидающее добавление. Устаревшая проверка не меняет новый выбор; при сетевом сбое предварительную проверку можно повторить.
Это автономная SVG-схема пяти районов синтетического датасета с рекой Есиль и ориентирами Астаны. Границы, маршруты и места строительства условны. Схема загружается с приложением без внешних картографических сервисов; анимация иллюстрирует выбранный проект, а численный эффект рассчитывается сервером с учётом лага.
Сцена и камера находятся в web/city-map.js / web/city-map.css, оформление рабочей панели — в web/map-workspace.css, синхронизация с планом и API — в web/app.js. Компонент сцены получает готовые значения и сообщает о кликах через callbacks; собственных расчётов Score в нём нет.
Быстрый пример и сравнение своих планов
- В блоке «Быстрый старт» нажмите «Загрузить пример». Он заменит решения контрольным набором из
backend/data/example.json: M7, M8 и M10 в Нуре, M12 для города, M5 в Сарыарке. Загрузка не запускает платный AI-запрос. - Нажмите «Рассчитать сценарий»: расход 95, Score 56,54. В разделе «Как получился Score?» показаны все составляющие формулы и их изменение.
- Нажмите «Сохранить для сравнения» — результат станет планом А. Измените решения или выберите предложенную замену и рассчитайте план Б.
- В блоке «План А / План Б» сравните бюджет, меры, районные баллы и выигранные/потерянные эффекты. Замена M5 Сарыарка на M3 Нура даёт 57,21 и +0,67 относительно показанного Score плана А, но теряет прирост воздуха и ЖКХ Сарыарки.
План А сохраняется локально, отдельно от таблицы команд. В браузерное хранилище записываются только решения и версия модели; после обновления страницы сервер пересчитывает их через /api/simulate без вызова LLM. План другой версии не сравнивается с текущей моделью. Кнопка «Вернуть решения плана А» загружает его для дальнейшего редактирования; «Убрать сравнение» удаляет локальный снимок.
Сравнение команд и презентация
После расчёта введите название команды и сохраните результат. Сервер заново проверяет пять решений и вычисляет Score, прежде чем добавить запись в общую таблицу. Результаты команд доступны посетителям одного запущенного сервера: можно сравнить Score, расходы и баллы районов или загрузить чужой план для собственного эксперимента.
Первое сохранение выдаёт код команды для редактирования своей записи; браузер сохраняет его автоматически. Чтобы обновить запись с другого устройства, раскройте «Код команды для обновления результата» и вставьте код. Занятое название без верного кода изменить нельзя. Код не показывается в публичной таблице. Локальная база SQLite создаётся автоматически в backend/data/scenarios.sqlite3; путь можно изменить через SIMULATOR_DB_PATH. Файлы базы игнорируются Git. Таблица показывает только результаты текущей версии синтетической модели; это инструмент сравнения сценариев, а не официальный рейтинг хакатона.
Кнопка «Презентация / PDF» скачивает автономный HTML с тремя слайдами по текущему рассчитанному сценарию. Откройте файл в браузере и нажмите «Печать / PDF», чтобы сохранить PDF. Файл не требует доступа к серверу или интернету. На слайд с выводами попадают краткое резюме и до двух сокращённых пунктов каждого раздела, включая последствия. Полный текст остаётся в симуляторе.
Визуальный стиль вдохновлён Astana Innovations: тёмный первый экран, плавающее верхнее меню, мятные акценты, голубо-зелёные градиенты и крупные заголовки. Собственная SVG-иллюстрация Астаны хранится в web/images/astana-future.svg. Светлые рабочие блоки объединяют обзор районов, карту, каталог инициатив и закреплённый бюджет.
На телефоне навигация раскрывается по кнопке меню и закрывается по Escape или выбору раздела; бюджет, число решений и расчёт доступны в нижней панели. Все шрифты — Montserrat, Manrope и JetBrains Mono — хранятся локально в web/fonts/ с лицензиями OFL. Оформление работает без запросов к внешнему сайту. Анимации учитывают системную настройку уменьшения движения.
Проверки
GitHub Actions запускает python -m pytest -q и python -m ruff check backend scripts при каждом push и pull request. Workflow CI использует Python 3.13, устанавливает зависимости из requirements-dev.txt и выполняет проверки без ключа OpenAI. Его также можно запустить вручную во вкладке Actions.
Для линтера и браузерного теста сначала установите инструменты разработчика из requirements-dev.txt (Chromium нужен только для E2E):
python -m pip install -r requirements-dev.txt
python -m pytest -q
ruff check backend scripts
python -m playwright install chromium
python scripts/e2e_smoke.py
Тесты проверяют контрольные примеры датасета, формулу Score, распределение эффектов по районам, бюджет, повторы, направления, ограничения районов, синергии, несовместимости, подбор альтернатив, таблицу команд, AI-факты и API.
Скрипт запускает временный сервер с отключённым платным AI-запросом и отдельной временной SQLite-базой. Он проверяет переключение районов, фильтры, загрузку примера, формулу Score, основания выводов, бюджет, варианты улучшения, сравнение А/Б, восстановление снимка и сравнение команд с двух браузерных сессий. Проверки карты в scripts/map_checks.py охватывают все 14 проектов, движение ЛРТ, отключение анимаций, городские меры, клавиатуру, приближение и синхронизацию с планом. Запоздавшие ответы игнорируются после изменения, сброса и загрузки примера. Экспорт проверяется как трёхстраничный PDF, в том числе с длинным текстом; макет — на ширинах 320–1440 px с заполненными сравнениями.
Проверенное окружение: Linux, Python 3.13.12; FastAPI 0.141.1, Pydantic 2.13.5, Uvicorn 0.53.0, HTTPX 0.28.1, pytest 9.1.1, Playwright 1.63.0, pypdf 6.19.0, Ruff 0.16.8. Диапазоны версий заданы в requirements.txt и requirements-dev.txt.
Модель
- Горизонт симуляции — 8 кварталов. Для меры с лагом
Lприменяется доля эффекта(8 − L) / 8. - Городская мера действует во всех пяти районах; районная — только в выбранном.
- Синергия добавляется один раз без масштабирования лагом.
- После применения эффектов показатели ограничиваются диапазоном 0–100.
- Районный балл — взвешенная сумма десяти показателей; сумма их весов равна 1.
- Городской средний балл взвешивается по доле населения районов.
- К итоговому Score применяется формула
0.7 × D_avg + 0.3 × min(D_d) − N_crit, гдеN_crit— количество значений строго ниже 40. - Для симуляции требуются ровно пять уникальных мер, не более двух на направление, без превышения бюджета и нарушений несовместимости.
- Остаток бюджета не увеличивает Score. Порядок выбора мер не влияет на расчёт.
Подробности формулы и контрольные примеры: docs/model.md. Демонстрационный сценарий: docs/demo.md.
Путь данных: браузер отправляет ID мер и районов → сервер проверяет правила → движок считает показатели и Score → рекомендатель проверяет одиночные замены → сервер составляет каталог фактов → LLM выбирает акценты → сервер проверяет выбранные ID и собирает текст с основаниями. Синтетический датасет перенесён из предоставленного «Датасета районов» в backend/data/simulation.json; он един для всех запросов.
Структура
backend/app/ API, валидатор, scoring engine и AI-разбор
backend/data/ общая версия синтетических данных и правил
backend/tests/ проверки движка и API
scripts/ E2E-проверка в Chromium
web/ интерфейс симулятора
docs/ заметки о модели и сценарий демонстрации
Соответствие пунктам задания и границы дополнительных функций: docs/requirements.md. Итоги повторной сверки и исправления: docs/review.md.
Авторы коммитов
История коммитов
- 2026-09-23 17:39ci: add pytest and Ruff checks and fix web serving testsonidev7
- 2026-09-23 17:28feat: refresh Astana design and fix map object collisionssonidev7
- 2026-09-23 17:18Update README.mdsonidev7
- 2026-09-23 16:28feat: add interactive Astana map with animated projects and planning controlssonidev7
- 2026-09-23 15:33feat: redesign simulator and add verified AI analysis and plan comparisonsonidev7
- 2026-09-23 14:24fix: expose baseline districts and address compliance reviewAlimzhan
- 2026-09-23 14:16style: refine simulator layout and mobile planning flowAlimzhan
- 2026-09-23 14:04feat: compare teams and export scenario presentationsAlimzhan
Все коммиты (11)
- 2026-09-23 13:47test: add chromium end-to-end smoke coverageAlimzhan
- 2026-09-23 13:42feat: suggest validated scenario improvementsAlimzhan
- 2026-09-23 13:39feat: add city budget simulator MVPAlimzhan
Структура репозитория (45 файлов)
.env.example .github/workflows/ci.yml .gitignore README.md backend/__init__.py backend/app/__init__.py backend/app/ai.py backend/app/engine.py backend/app/evidence.py backend/app/leaderboard.py backend/app/main.py backend/app/recommender.py backend/data/example.json backend/data/simulation.json backend/tests/test_ai.py backend/tests/test_api.py backend/tests/test_engine.py backend/tests/test_leaderboard.py backend/tests/test_recommender.py backend/tests/test_score_breakdown.py docs/demo.md docs/model.md docs/requirements.md docs/review.md requirements-dev.txt requirements.txt scripts/e2e_smoke.py scripts/map_checks.py web/app.js web/city-map.css web/city-map.js web/fonts/JETBRAINS-MONO-OFL.txt web/fonts/MANROPE-OFL.txt web/fonts/jetbrains-cyrillic.woff2 web/fonts/jetbrains-latin.woff2 web/fonts/manrope-cyrillic.woff2 web/fonts/manrope-latin.woff2 web/fonts/montserrat-OFL.txt web/fonts/montserrat-cyrillic.woff2 web/fonts/montserrat-latin.woff2 web/images/astana-future.svg web/index.html web/map-workspace.css web/presentation.js web/styles.css
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.