AAYMA
RU
Войти

Qurultay

Городской симулятор с детерминированным движком, полным перебором всех 694 395 допустимых планов (глобальный максимум, Парето, перцентиль), тремя…

Доведённый продукт
Команда «Temirqazyq» 4💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: 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.md

Qurultay — 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. Стресс-тест и сравнение. Совет депутатов, кризис (готовый или свой текстом), отправка в лидерборд, сравнение с другими командами, отчёт.


Технологии

Бэкенд

ПакетВерсияРоль
FastAPI0.141.1HTTP-слой, 19 эндпоинтов, Swagger
Pydantic2.13.5схемы запросов и ответов
NumPy2.5.3векторизованный перебор всех наборов решений
OpenAI SDK3.19.0клиент к любому OpenAI-совместимому API
python-dotenv1.2.3чтение .env
uvicorn0.53.0сервер
pytest, httpx9.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.json5 районов (Есиль, Алматы, Сарыарка, Байконур, Нура), 10 показателей качества жизни со шкалой 0–100, веса показателей, доли населения, правила игры
measures.json14 мероприятий: направление, тип (район или весь город), стоимость, лаг, эффекты; 3 синергии; 3 несовместимости
events.json6 кризисов: шоки показателей, блокировки мер, секвестр бюджета

Изменить модель города можно, отредактировав JSON — код подхватит, а тесты покажут, если контрольные числа разошлись. Структура готова к замене синтетики на реальные открытые данные.

Внешние сервисы

Один — провайдер языковой модели, и он необязателен. Используется OpenAI-совместимый HTTP API; конкретный провайдер выбирается переменной LLM_PROVIDER:

ЗначениеКуда идут запросы
openaiapi.openai.com
nvidiaintegrate.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 и генерация дорожной карты по кварталам.
Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
18
Авторы
3
Файлы
65
Ветки
4

Авторы коммитов

Meirambekov Nursultan 15Meirambekov Nursultan 2Your Name 2

История коммитов

  • 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
Ветки (4)

Обсуждение 0 комментариев

Войдите, чтобы комментировать

Пока нет комментариев — будьте первым.