О проекте
Команда: Temirqazyq
Кейс HackAlem.ai: Спецтрек — Astana Innovations
Стадия: Доведённый продукт
Задача кейса: «Аким на 5 часов»: городской симулятор, распределяющий бюджет по 5 направлениям и 5 районам
Суть
Городской симулятор с детерминированным движком, полным перебором всех 694 395 допустимых планов (глобальный максимум, Парето, перцентиль), тремя AI-агентами (Аналитик, Критик, Советник с инструментами движка), кризисами «Чёрный лебедь», советом депутатов и лидербордом.
Как устроено
Датасет data/*.json (districts, measures, events) загружает движок backend/app/engine: validator (8 правил), scorer (трасса расчёта и Шепли), optimizer (векторизованный перебор на NumPy примерно за 0,3 с), events (применение кризиса). LLM-слой получает компактный контекст трассы, агенты работают параллельно. Советник вызывает инструменты score_scenario, best_swaps и oracle_best, каждую его рекомендацию сервер пересчитывает и помечает verified. Свободный текст кризиса LLM переводит в параметры, последствия считает движок. Frontend на React 19, Vite, Ant Design и Recharts, лидерборд в SQLite.
Ключевые решения
- полный перебор NumPy
- фронт Парето
- значения Шепли
- multi-agent (Analyst/Critic/Advisor)
- function calling
- стресс-тест кризисами
- независимый selfcheck формулы
Метрики
Перебор 694 395 наборов за ~0,3 с; база 52,56, глобальный максимум 57,24
Особенности
«Оракул» даёт точный глобальный оптимум и перцентиль плана команды, а не только поиск одной замены. У каждой кнопки района заранее показан прирост баллов. Рекомендации агента пересчитываются сервером и маркируются verified. Математика дублирована независимым tools/selfcheck.py без импортов из движка.
Стек
Python · FastAPI · NumPy · Pydantic · SQLite · React 19 · TypeScript · Vite · Ant Design · Recharts · Docker
Модели
OpenAI-совместимый API (OpenAI, NVIDIA NIM, vLLM, Ollama, LM Studio)
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-91da6326-temirqazyq.
README
README.mdQurultay — AI-симулятор управления городом
Кейс HackAlem AI. Команда получает виртуальный бюджет и данные по пяти условным районам Астаны, принимает ровно пять управленческих решений и видит, что станет с городом через два года — с оценкой, объяснением, критикой и стресс-тестом «Чёрный лебедь».
Главный принцип проекта: все числа считает детерминированный движок, языковая модель их только объясняет. Модель получает готовую трассу расчёта и работает поверх неё. Каждая её рекомендация повторно пересчитывается движком на сервере, поэтому выдуманное число не может попасть на экран.
Какую проблему решает
Городское управление — это выбор в условиях жёстких ограничений: денег хватает не на всё, эффект приходит с задержкой, а улучшение одного района не заменяет провал в другом. Этому трудно научить на словах: пока не попробуешь распределить бюджет сам, не почувствуешь, почему «построить парк» и «построить школу» конкурируют за один участок.
Qurultay даёт это попробовать. Пользователь распоряжается бюджетом 100 условных единиц, выбирает пять мероприятий из четырнадцати и сразу видит последствия: как изменился каждый из 50 показателей города, какой район остался слабым, сколько баллов дала каждая мера и насколько план далёк от математически лучшего.
Для кого: участники и жюри хакатона; шире — студенты и слушатели управленческих программ, которым нужен безопасный тренажёр бюджетных компромиссов.
Что реализовано
Обязательная часть кейса
| Требование | Как сделано |
|---|---|
| Единый бюджет и датасет для всех | data/*.json — единственный источник правды, загружается при старте |
| Пять решений по пяти направлениям | Каталог из 14 мер, пошаговый выбор по направлениям, слоты плана |
| Система не даёт превысить бюджет | Живая проверка после каждого клика; кнопка расчёта заблокирована для невалидного набора; сервер отвергает такой набор кодом 422 |
| AI-анализ решений | Три агента: Аналитик, Критик и Советник с вызовом инструментов движка |
| Расчёт итоговой оценки | Формула из датасета, полная трасса расчёта, контрольные числа покрыты тестами |
| Сильные стороны, риски, последствия | Карточки Аналитика и Критика плюс «голос жителя» самого слабого района |
Дополнительно
- Оракул — полный перебор всех 694 395 допустимых наборов решений (NumPy, около 0.3 секунды). Даёт точный глобальный максимум, перцентиль плана команды, фронт Парето «стоимость → лучший достижимый результат» и гистограмму распределения.
- Подсказчик планирования (
POST /api/guide) — для каждого возможного следующего хода заранее посчитано, на сколько он сдвинет оценку. Плюс слабые места каждого района и четыре готовых набора под разные цели: максимум оценки, подтянуть слабейший район, закрыть провалы дёшево, лучшее за 70% бюджета. - «Чёрный лебедь» — шесть готовых кризисов и, отдельно, своя проблема произвольным текстом: модель переводит описание в параметры (какие районы и показатели просядут, нужно ли резать бюджет), а последствия считает движок. Если план после кризиса развалился, движок подбирает лучший возможный набор уже в новом мире.
- Совет депутатов — четыре персоны с противоположными ценностями оценивают план, модератор подводит итог.
- Лидерборд с фильтрами и сортировкой, сравнение любых двух сценариев, HTML-отчёт по сценарию.
- Разложение вклада мер по Шепли — сумма вкладов равна общему приросту с машинной точностью.
- Независимая перепроверка математики (
tools/selfcheck.py) — формула и правила реализованы заново, без единого импорта из движка, результаты сверяются построчно. - Обучение первого пользователя: вступительный экран с целью и ориентирами, пошаговый тур с подсветкой, расшифровка любого числа по клику.
Как работает решение
Сценарий от входа до результата:
1. Вход. Пользователь видит вступление: цель — поднять оценку качества жизни, бездействие даёт 52.56, лучший возможный план — 57.24. Вся борьба идёт внутри этих пяти баллов.
2. Выбор решений. Каталог разбит на пять направлений, пользователь проходит их по шагам. У каждой меры показаны цена, лаг (за сколько кварталов она разворачивается) и на какие показатели влияет. Для мер типа «район» рядом стоят пять кнопок с районами, и на каждой заранее написано, сколько баллов она даст — выбор перестаёт быть вслепую. Правила (бюджет, не более двух мер на направление, несовместимые пары) проверяются после каждого клика.
3. Расчёт. Сервер валидирует набор и считает трассу: для каждой пары «район × показатель» — что было, что стало, какая мера сколько дала, сработали ли синергии, упёрлось ли значение в потолок шкалы.
4. Результат. Пользователь видит итоговую оценку, прогресс к максимуму в процентах, фразу-вывод обычным
языком, графики «до → после» по районам и направлениям, вклад каждой меры и тепловую карту 5×10. Любую ячейку
можно нажать и увидеть арифметику: 55.00 + 10.50 (M10: +12 × 0.875) + 2.00 (синергия) = 67.50.
5. Разбор AI. Параллельно работают три агента. Аналитик объясняет результат, Критик ищет слабые места,
Советник вызывает инструменты движка и предлагает замены с посчитанным результатом — каждая помечена
verified, потому что сервер пересчитал её сам.
6. Стресс-тест и сравнение. Совет депутатов, кризис (готовый или свой текстом), отправка в лидерборд, сравнение с другими командами, отчёт.
Технологии
Бэкенд
| Пакет | Версия | Роль |
|---|---|---|
| FastAPI | 0.141.1 | HTTP-слой, 19 эндпоинтов, Swagger |
| Pydantic | 2.13.5 | схемы запросов и ответов |
| NumPy | 2.5.3 | векторизованный перебор всех наборов решений |
| OpenAI SDK | 3.19.0 | клиент к любому OpenAI-совместимому API |
| python-dotenv | 1.2.3 | чтение .env |
| uvicorn | 0.53.0 | сервер |
| pytest, httpx | 9.1.1, 0.28.1 | тесты |
Версии зафиксированы точными номерами — сборка повторяется.
Фронтенд: React 19, TypeScript 6, Vite 8, Ant Design 6 (таблицы, подсказки, поповеры), Recharts 3 (гистограмма), oxlint. Остальные графики — dumbbell, диверегирующие бары, тепловая карта — собраны вручную на HTML и SVG.
Хранилище: SQLite через стандартную библиотеку, без ORM.
AI: любой OpenAI-совместимый провайдер, переключается одной переменной окружения — openai, nvidia
(NVIDIA NIM), custom (vLLM, Ollama, LM Studio) или none. Конкретная модель задаётся в .env;
в коде не зашита. Клиент подстраивается под требования модели: если сервер сообщает, что не принимает
какой-то параметр, клиент переименовывает или убирает его и повторяет запрос.
Дизайн: akim-sim/DESIGN.md — файл дизайн-системы stripe из коллекции
voltagent/awesome-design-md плюс приложение с правилами
проекта. Палитра графиков провалидирована скриптом на различимость при дальтонизме.
Архитектура
akim-sim/
├─ data/ ДАТАСЕТ — единственный источник правды
│ ├─ districts.json 5 районов × 10 показателей, веса, доли населения, правила
│ ├─ measures.json 14 мер, 3 синергии, 3 несовместимости
│ └─ events.json 6 кризисов «Чёрный лебедь»
│
├─ backend/app/engine/ ДЕТЕРМИНИРОВАННЫЙ ДВИЖОК — ни одной строки LLM
│ ├─ data.py загрузка датасета
│ ├─ validator.py 8 правил → список нарушений
│ ├─ scorer.py формула, полная трасса, вклад мер по Шепли
│ ├─ optimizer.py оракул: перебор всех наборов, Парето, перцентиль, замены
│ └─ events.py применение кризиса → новое состояние мира
│
├─ backend/app/ai/ LLM-СЛОЙ — поверх готовой трассы
│ ├─ llm.py клиент, JSON-режим, агентный цикл с инструментами
│ ├─ context.py трасса + оракул → компактный контекст для промпта
│ ├─ agents.py промпты и схемы пяти ролей
│ ├─ fallback.py те же ответы без модели, шаблонами по трассе
│ └─ pipeline.py параллельный запуск, деградация при сбое
│
├─ backend/app/main.py FastAPI, раздача собранного фронтенда
├─ backend/app/db.py SQLite-лидерборд
├─ backend/app/report.py HTML-отчёт по сценарию
├─ tools/selfcheck.py независимая перепроверка математики
└─ frontend/src/ React: Planner, Results, AIPanel, Guide, EventPanel, Leaderboard
Поток данных
UI ──решения──▶ validator ──ок──▶ scorer ──трасса──▶ context ──▶ [Аналитик │ Критик │ Советник]
│ │ ▲
└──▶ optimizer (оракул, замены) ──────────┘ │
│
инструменты движка: score_scenario · best_swaps · oracle_best ──┘
Ключевая граница: движок ничего не знает о языковой модели, а модель не умеет считать — она только получает
числа и вызывает инструменты. Ответ Советника проходит через повторную валидацию и пересчёт на сервере,
и каждая рекомендация помечается verified: true/false.
Установка и запуск
Вариант A — Docker, одна команда
git clone https://github.com/BAITC-Hacks/hack-91da6326-temirqazyq.git
cd hack-91da6326-temirqazyq/akim-sim
cp .env.example .env # необязательно: впишите ключ модели
docker compose up --build # → http://localhost:8000
Образ собирает фронтенд и кладёт его рядом с FastAPI — один процесс, один порт.
Вариант B — локально (Python 3.11+, Node 20+)
cd akim-sim
# бэкенд
pip install -r backend/requirements.txt
cd backend && uvicorn app.main:app --reload --port 8000
# фронтенд, в другом терминале
cd frontend && npm install && npm run dev # → http://localhost:5173
Либо собрать фронтенд один раз (cd frontend && npm run build) — тогда FastAPI сам отдаёт его на
http://localhost:8000, и второй процесс не нужен. То же делает make run.
Подключение модели
Без ключа приложение полностью работоспособно — объяснения собираются детерминированно из той же трассы
расчёта, режим помечен на каждой карточке. Чтобы включить живой язык, задайте в .env две строки:
# OpenAI
LLM_PROVIDER=openai LLM_API_KEY=sk-... LLM_MODEL=gpt-4o-mini
# NVIDIA NIM — адрес и модель подставит пресет
LLM_PROVIDER=nvidia LLM_API_KEY=[скрыто]
Если запрос к модели не удался, агент автоматически возвращается к шаблонному режиму и помечает ответ
_mode: fallback. Движок при этом работает без изменений.
Как проверить решение
1. Пройти сценарий руками (2 минуты)
Откройте http://localhost:8000. Во вступлении нажмите «Показать на готовом примере» — загрузится набор
из технического задания: M7 Нура · M8 Нура · M10 Нура · M12 город · M5 Сарыарка, стоимость 95.
Нажмите «Рассчитать результат». Ожидаемые числа:
| Показатель | Значение |
|---|---|
| Оценка города | 56.54 |
| База без решений | 52.56 |
| Прирост | +3.98 |
| Провальных показателей | 0 (было 2) |
| Лучше других планов | 99.9% из 694 395 |
Разверните «Подробности расчёта» и нажмите на ячейку B1 в строке Нуры — раскроется арифметика:
55.00 + 10.50 (M10: +12 × 0.875) + 2.00 (синергия M10+M12) = 67.50.
2. Прогнать тесты
cd akim-sim/backend && python -m pytest -q
24 теста: контрольные числа датасета, все восемь правил валидатора по отдельности, аддитивность вкладов по Шепли, санитайзер произвольного кризиса и проверка того, что Советник не может протащить выдуманную оценку (подставная модель возвращает 99.9, движок обязан перезаписать это настоящим числом).
3. Перепроверить математику независимо
cd akim-sim && make verify
Запускает tools/selfcheck.py, где формула и правила реализованы заново, без единого импорта из движка,
и сверяет результаты построчно:
проверка независимо движок
База без решений 52.557680 52.557680 ✓
Пример из датасета 56.543070 56.543070 ✓
Сумма вкладов по Шепли = прирост 3.985390 3.985390 ✓
Показанная дельта = score − база 3.980000 3.980000 ✓
Число допустимых наборов 694395 694395 ✓
Глобальный максимум 57.236735 57.236735 ✓
Полный перебор занимает около 10 секунд на чистом Python. Самая ценная строка — число допустимых наборов: оно сходится только если обе независимые реализации одинаково поняли все восемь правил.
4. Проверить через API
curl -s localhost:8000/api/score -H 'content-type: application/json' -d '{
"decisions":[{"measure_id":"M7","district_id":"nura"},{"measure_id":"M8","district_id":"nura"},
{"measure_id":"M10","district_id":"nura"},{"measure_id":"M12"},
{"measure_id":"M5","district_id":"saryarka"}]}'
Swagger со всеми эндпоинтами: http://localhost:8000/docs
Данные и интеграции
Данные
Датасет синтетический — он составлен для кейса, а не выгружен из открытых источников. Три файла в data/:
| Файл | Содержимое |
|---|---|
districts.json | 5 районов (Есиль, Алматы, Сарыарка, Байконур, Нура), 10 показателей качества жизни со шкалой 0–100, веса показателей, доли населения, правила игры |
measures.json | 14 мероприятий: направление, тип (район или весь город), стоимость, лаг, эффекты; 3 синергии; 3 несовместимости |
events.json | 6 кризисов: шоки показателей, блокировки мер, секвестр бюджета |
Изменить модель города можно, отредактировав JSON — код подхватит, а тесты покажут, если контрольные числа разошлись. Структура готова к замене синтетики на реальные открытые данные.
Внешние сервисы
Один — провайдер языковой модели, и он необязателен. Используется OpenAI-совместимый HTTP API; конкретный
провайдер выбирается переменной LLM_PROVIDER:
| Значение | Куда идут запросы |
|---|---|
openai | api.openai.com |
nvidia | integrate.api.nvidia.com (NVIDIA NIM) |
custom | ваш адрес в LLM_BASE_URL — vLLM, Ollama, LM Studio |
none | никуда, работает шаблонный режим |
Других внешних зависимостей нет: ни аналитики, ни телеметрии, ни сторонних API. Шрифт Inter подключается с Google Fonts. Данные пользователя никуда не уходят, лидерборд хранится локально в SQLite.
Ограничения
Честный список того, чего в текущей версии нет.
Модель города
- Датасет синтетический, а не реальные данные Астаны.
- Эффекты аддитивны и линейны: две меры на один показатель складываются без убывающей отдачи.
- Нет межрайонных переливов: ЛРТ в Есиле никак не разгружает Алматы, хотя транспорт — сетевая система.
- Нет эксплуатационных расходов: бюджет разовый, содержание построенного не учитывается.
- Лаг реализован как доля эффекта
(8 − лаг) / 8, а не как траектория по кварталам. Промежуточных состояний модель не считает — только итог на горизонте.
Инфраструктура
- SQLite рассчитан на один экземпляр приложения; горизонтального масштабирования нет.
- Аутентификации нет — название команды вводится свободным текстом.
- Оракул держит результат перебора в памяти процесса, поэтому бессерверный хостинг не подходит.
AI-слой
- Агенты не хранят историю диалога — каждый запрос независим.
- Советник работает через вызов инструментов, а его поддерживают не все модели. Если выбранная модель этого не умеет, Советник перейдёт в шаблонный режим; остальные три агента и движок продолжат работать.
- Рекомендации Советника ограничены заменой одного решения; полную перестройку плана он не предлагает.
Интерфейс
- Отчёт по сценарию — HTML-страница, свёрстанная под печать. Готовый PDF-файл сервер не генерирует: сохранение в PDF делается печатью из браузера.
- Фронт Парето выводится простой строкой текста и читается плохо.
- Интерфейс только на русском языке.
Прочее
README.mdв корне иakim-sim/README.mdсейчас дублируют друг друга и могут разойтись.
Потенциал развития
- Подключить реальные открытые данные Астаны вместо синтетики; калибровать веса и эффекты на истории.
- Добавить убывающую отдачу, межрайонные переливы и стоимость содержания.
- Турнирный режим: серия кризисов по кварталам, общий экран для команд.
- Экспорт отчёта в PPTX и генерация дорожной карты по кварталам.
Авторы коммитов
История коммитов
- 2026-09-23 18:04README для жюри в корне, технический разбор — в akim-simMeirambekov Nursultan
- 2026-09-23 18:00Согласовать название проекта и ссылку на дизайн-системуMeirambekov Nursultan
- 2026-09-23 17:59Слить правки README из mainMeirambekov Nursultan
- 2026-09-23 17:58Update README.mdMeirambekov Nursultan
- 2026-09-23 17:56Починить работу с LLM, переименовать проект в Qurultay, подкрасить плиткиMeirambekov Nursultan
- 2026-09-23 17:42Убрать разнобой в добавлении мер и дать иконку кнопке примераMeirambekov Nursultan
- 2026-09-23 17:41test v:2Your Name
- 2026-09-23 17:38Очистить шапку и сделать районы явными кнопками добавленияMeirambekov Nursultan
Все коммиты (18)
- 2026-09-23 17:33Разрешить раздельный деплой фронтенда и описать варианты хостингаMeirambekov Nursultan
- 2026-09-23 17:30Выделить действия на карточках и описать подключение NVIDIAMeirambekov Nursultan
- 2026-09-23 17:22Упростить планировщик: пошаговый выбор, районы с очками, меньше текстаMeirambekov Nursultan
- 2026-09-23 17:01Переработать интерфейс и добавить подсказчик планированияMeirambekov Nursultan
- 2026-09-23 16:55chore: continue work on mainMeirambekov Nursultan
- 2026-09-23 14:50test v:1Your Name
- 2026-09-23 14:42Make planner easier to useMeirambekov Nursultan
- 2026-09-23 14:39Improve planner onboardingMeirambekov Nursultan
- 2026-09-23 14:14Add akim simulator projectMeirambekov Nursultan
- 2026-09-23 11:43testMeirambekov Nursultan
Структура репозитория (65 файлов)
README.md akim-sim/.env.example akim-sim/.gitignore akim-sim/DESIGN.md akim-sim/Dockerfile akim-sim/Makefile akim-sim/README.md akim-sim/backend/app/__init__.py akim-sim/backend/app/ai/__init__.py akim-sim/backend/app/ai/agents.py akim-sim/backend/app/ai/context.py akim-sim/backend/app/ai/fallback.py akim-sim/backend/app/ai/llm.py akim-sim/backend/app/ai/pipeline.py akim-sim/backend/app/db.py akim-sim/backend/app/engine/__init__.py akim-sim/backend/app/engine/data.py akim-sim/backend/app/engine/events.py akim-sim/backend/app/engine/models.py akim-sim/backend/app/engine/optimizer.py akim-sim/backend/app/engine/scorer.py akim-sim/backend/app/engine/validator.py akim-sim/backend/app/main.py akim-sim/backend/app/report.py akim-sim/backend/requirements.txt akim-sim/backend/tests/test_api.py akim-sim/backend/tests/test_engine.py akim-sim/data/districts.json akim-sim/data/events.json akim-sim/data/measures.json akim-sim/docker-compose.yml akim-sim/frontend/.gitignore akim-sim/frontend/.oxlintrc.json akim-sim/frontend/README.md akim-sim/frontend/index.html akim-sim/frontend/netlify.toml akim-sim/frontend/package-lock.json akim-sim/frontend/package.json akim-sim/frontend/public/favicon.svg akim-sim/frontend/public/icons.svg akim-sim/frontend/src/App.tsx akim-sim/frontend/src/api.ts akim-sim/frontend/src/assets/hero.png akim-sim/frontend/src/assets/vite.svg akim-sim/frontend/src/components/AIPanel.tsx akim-sim/frontend/src/components/CouncilPanel.tsx akim-sim/frontend/src/components/EventPanel.tsx akim-sim/frontend/src/components/Guide.tsx akim-sim/frontend/src/components/Leaderboard.tsx akim-sim/frontend/src/components/Onboarding.tsx akim-sim/frontend/src/components/Planner.tsx akim-sim/frontend/src/components/Results.tsx akim-sim/frontend/src/components/SideNav.tsx akim-sim/frontend/src/components/Tour.tsx akim-sim/frontend/src/components/Viz.tsx akim-sim/frontend/src/components/tourSteps.ts akim-sim/frontend/src/index.css akim-sim/frontend/src/lib.ts akim-sim/frontend/src/main.tsx akim-sim/frontend/src/theme.ts akim-sim/frontend/tsconfig.app.json akim-sim/frontend/tsconfig.json akim-sim/frontend/tsconfig.node.json akim-sim/frontend/vite.config.ts akim-sim/tools/selfcheck.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.