AAYMA
RU
Войти

Аким на 5 часов

Городской симулятор Астаны: игрок выбирает 5 инициатив из 14 в пределах бюджета 100 для 5 районов, видит изменения показателей и итоговый Score на…

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

О проекте

Команда: mittens
Кейс HackAlem.ai: Спецтрек — Astana Innovations
Стадия: Доведённый продукт

Задача кейса: «Аким на 5 часов»: городской симулятор, распределяющий бюджет по 5 направлениям и 5 районам

Суть

Городской симулятор Астаны: игрок выбирает 5 инициатив из 14 в пределах бюджета 100 для 5 районов, видит изменения показателей и итоговый Score на карте 2ГИС, AI поясняет результат.

Как устроено

У каждого района 10 показателей в 5 направлениях. Инициативы из каталога проходят серверную валидацию: бюджет, не больше 2 мер одного направления, несовместимые пары. Детерминированный движок считает I' = clip(I + Σэффект×(8−лаг)/8 + синергии) и Score = 0.7×средневзвешенный D + 0.3×минимальный D − N. OpenAI gpt-4.1-mini только объясняет готовый расчёт (до 50 слов) и на результат не влияет.

Ключевые решения

  • детерминированная модель эффектов с лагами и синергиями
  • взвешенный Score с учётом слабейшего района
  • исчерпывающий оптимизатор кампании
  • 2GIS MapGL и Places API
  • polygon-clipping для границ районов

Особенности

В Score входят средневзвешенный индекс, слабейший район и штраф за критичные показатели. Сервер пересчитывает весь набор с исходного состояния, поэтому повторная отправка не удваивает эффект. Есть жюри-сценарий с ожидаемыми числами (Score 52.56 → 56.54).

Стек

TypeScript · React 19 · TanStack Start · Vite · 2GIS MapGL · Cloudflare Workers · Node.js test runner

Модели

gpt-4.1-mini


Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-9f94a26d-mittens.

README

README.md

Аким на 5 часов

Интерактивный симулятор управления Астаной: пять решений, ограниченный бюджет и наглядные последствия для районов.

Проект помогает разобраться, как выбор городских инициатив влияет на транспорт, экологию, социальную инфраструктуру, безопасность и городские сервисы. Игрок выступает в роли акима: распределяет ресурсы и сравнивает результат со стартовым состоянием города. Решение предназначено для демонстрации жюри хакатона, знакомства с городским управлением и обсуждения решений с городскими командами и аналитиками.

Это игровой прототип на синтетических данных, а не прогноз развития Астаны. Карта и границы загружаются из 2ГИС; показатели районов, доли населения, стоимость и эффекты инициатив заданы в коде. AI объясняет готовые расчёты, но не определяет результат.

Демонстрация

Демонстрация работы приложения «Аким на 5 часов»

Смотреть исходное видео в формате MP4

Что реализовано

  • Интерактивная карта с выбором пяти игровых районов: Есиль, Алматы, Сарыарка, Байконур и Нура; перемещение, масштабирование, подписи и отображение индекса района.
  • Переключение дневной и тёмной темы карты и интерфейса. Выбор темы сохраняется в браузере и не сбрасывает раунд.
  • По 10 показателей на район в пяти направлениях. Значения находятся в диапазоне 0–100, где больше — лучше. Панель статистики показывает исходное состояние и изменения.
  • Каталог из 14 инициатив: 10 районных и 4 общегородские. Среди них — автобусные полосы, школа и детский сад, чистое топливо, освещение, обновление сетей и цифровая платформа обращений.
  • Раунд из ровно 5 уникальных инициатив при бюджете 100 условных единиц. Допускается не более двух инициатив одного направления; проверяются несовместимые сочетания.
  • Применение решений по одному или несколькими за отправку, редактирование черновика и начало нового раунда. Уже применённые решения учитываются в следующих ходах.
  • Детерминированный расчёт с задержкой эффекта, синергиями, отрицательными последствиями отдельных мер и ограничением значений показателей.
  • Результаты каждого хода и итоговый отчёт: Score до и после, изменения направлений по городу и критичные показатели.
  • Необязательное объяснение на русском через OpenAI после хода и завершения раунда, до 50 слов на объяснение. Повторный запрос текста не меняет бюджет и расчёт.
  • Обработка ошибок карты, сервера и AI; автоматические проверки модели и вспомогательной логики интерфейса.

Как работает решение

  1. Игрок открывает приложение и выбирает район на карте, по подписи или кнопке района.
  2. Изучает показатели и выбирает инициативы в категориях. Районная мера относится к выбранному району; инициативы вкладки «Весь город» затрагивают все пять районов.
  3. Выбранные меры попадают в черновик и резервируют бюджет. До отправки их можно удалить в разделе «План».
  4. Нажатие «Применить» отправляет на сервер весь накопленный набор решений. Сервер проверяет ограничения и пересчитывает результат от исходного состояния — повторная отправка не удваивает расходы или эффекты.
  5. Карта и статистика обновляются. Результат хода показывает изменения последней отправки, а статистика района — изменения от начала раунда. AI-текст запрашивается отдельно и не задерживает численный результат.
  6. После пяти применённых инициатив доступен итоговый Score и отчёт раунда. Если все пять отправлены одним пакетом, отчёт открывается кнопкой «Итоги раунда»; при поэтапном завершении — автоматически.

Правила расчёта

Для каждого показателя суммируются эффекты выбранных мер с учётом лага на фиксированном горизонте 8, затем добавляются предусмотренные синергии:

I' = clip(I + Σ(эффект × (8 − лаг) / 8) + синергии, 0, 100)
D = Σ(вес показателя × значение показателя)
Score = 0.7 × средневзвешенный D + 0.3 × минимальный D − N

Здесь D — индекс района; средневзвешенный индекс использует игровые доли населения; N — количество показателей ниже 40 во всех районах. Веса показателей заданы в src/simulator/scoring.ts. Таким образом, оценка учитывает средний результат, положение слабейшего района и критичные значения. Итоговый Score выдаётся только для полного набора из пяти мер.

Лимит двух мер относится к исходному направлению, даже если мера отображается во вкладке «Весь город». Одну и ту же меру нельзя повторить, в том числе в другом районе. Несовместимы M1 и M3 в одном раунде, а также M4 и M7 либо M5 и M13 в одном районе.

Технологии

СлойТехнологии и назначение
ЯзыкиTypeScript, JavaScript, CSS; JSON для каталога
ИнтерфейсReact 19, Lucide React для иконок
Приложение и серверTanStack Start, TanStack Router; серверные функции в одном приложении
СборкаVite 8, плагин React, TypeScript 6
Карта@2gis/mapgl, MapGL JS API и Places API 2ГИС
Геометрияwellknown для WKT, polygon-clipping для объединения границ
AIOpenAI SDK, Responses API; по умолчанию gpt-4.1-mini, модель можно изменить через OPENAI_MODEL
ПроверкиВстроенный тестовый раннер Node.js, tsx, проверка типов TypeScript
РазвёртываниеCloudflare Workers, @cloudflare/vite-plugin, Wrangler

Версии зависимостей зафиксированы в package-lock.json. База данных и отдельный сервис расчётов не используются.

Архитектура проекта

Браузер: React + карта 2ГИС
  ├─ MapGL / Places API → карта и границы районов
  ├─ simulateCity → валидация → движок симуляции → показатели и Score
  └─ explainCityActions → рассчитанные изменения → OpenAI → пояснение

Фронтенд и серверные функции собираются как одно приложение TanStack Start. Сервер проверяет идентификаторы, районы, бюджет, повторы и конфликты, не принимая клиентские числа за источник истины. Расчётный модуль не зависит от доступности карты или AI.

ПутьСодержимое
src/routes/index.tsxОсновной экран, состояние раунда, черновик и отправка решений
src/components/Карта, карточки инициатив, статистика, результаты хода, итоговый отчёт и AI-текст
src/lib/Загрузка и объединение границ, представление показателей, проверки выбора и форматирование
src/server/simulateCity.tsСерверные функции исходных данных, расчёта и запроса пояснения
src/server/analysis-context.tsКонтекст последнего хода и полного раунда для AI
src/server/analysis.server.tsСерверный вызов OpenAI и обработка ошибок
src/simulator/Исходные показатели, меры, валидация, формулы, синергии и разбивка результата
data/campaigns.jsonРусские названия, стоимость, категории и правила выбора для интерфейса
tests/, src/**/*.test.tsАвтоматические проверки
vite.config.ts, wrangler.jsoncСборка и конфигурация Cloudflare Workers

Подробнее о расчётном модуле — в документации симулятора. Описание продукта и интерфейса — в PRODUCT.md и DESIGN.md.

Установка и локальный запуск

Требования

  • Node.js 22.12+ и npm. В .nvmrc указана ветка 20; если используете её, Vite 8 требует минимум 20.19.0.
  • Ключ 2ГИС с доступом к MapGL и Places API, включая поля items.geometry.centroid и items.geometry.selection, для полноценной карты.
  • Интернет для карты и необязательного AI-анализа.
  • Серверный ключ OpenAI — только если нужны текстовые объяснения.

Локальная разработка

В корне полученной копии репозитория выполните:

npm ci
cp .env.example .env.local

Если .env.local уже существует, отредактируйте его, сохранив нужные значения. Заполните переменные:

VITE_2GIS_MAP_KEY=[скрыто]
VITE_2GIS_MAP_STYLE_ID=
OPENAI_API_KEY= sk-svc…[ключ скрыт]
# Необязательно; значение по умолчанию показано ниже:
OPENAI_MODEL=gpt-4.1-mini
ПеременнаяНазначение
VITE_2GIS_MAP_KEYБраузерный ключ карты и границ; ограничьте разрешённые домены в настройках 2ГИС, включая локальный адрес http://localhost:3000
VITE_2GIS_MAP_STYLE_IDНеобязательный UUID опубликованного стиля 2ГИС; без него используются встроенные дневной и ночной стили, по умолчанию — ночной
OPENAI_API_KEYНеобязательный серверный ключ; без него расчёт работает, интерфейс сообщает о недоступности AI
OPENAI_MODELНеобязательное имя модели OpenAI

VITE_-переменные доступны браузеру и встраиваются при сборке. Ключ OpenAI должен оставаться серверным, без префикса VITE_. Локальные файлы окружения исключены из Git. После изменения настроек перезапустите сервер.

npm run dev

Откройте http://localhost:3000. Фронтенд и backend работают вместе; отдельный сервер на порту 8000 не нужен.

Сборка и локальный просмотр

npm run build
npm run preview

preview запускает собранное приложение с локальной средой Workers; адрес выводится в терминале.

Как проверить решение

Сценарий для жюри

Начните новый раунд и последовательно примените следующие меры. После каждого незавершающего хода нажимайте «Следующее действие».

ШагРайон / вкладкаИнициативаСтоимость
1Нура → социальная сфераM7: Школа и детский сад24
2Нура → социальная сфераM8: Центр семейного здоровья20
3Нура → безопасностьM10: Освещение и камеры12
4Весь городM12: Цифровая платформа обращений14
5Сарыарка → экологияM5: Чистое топливо для частного сектора25

Ожидаемый результат по модели, до округления интерфейсом:

  • Применено 5 из 5 решений; потрачено 95, осталось 5.
  • Исходный Score: 52.55768; итоговый: 56.54307; прирост: 3.98539.
  • Количество показателей ниже 40 снижается с 2 до 0.
  • M10 и M12 дают предусмотренную синергию безопасности в Нуре.
  • При настроенном OpenAI появляется пояснение; отсутствие ключа не мешает завершить раунд и увидеть числа.

Этот набор хранится в src/simulator/example.ts. Его можно воспроизвести без карты и внешних API:

node --import tsx --input-type=module -e 'import { simulate } from "./src/simulator/simulate.ts"; import { EXAMPLE_SCENARIO } from "./src/simulator/example.ts"; const r = simulate(EXAMPLE_SCENARIO); console.log({ baseline: r.baselineScore, final: r.finalScore, delta: r.scoreDelta, budget: r.budget });'

Для проверки ограничений начните новый раунд, выберите M1 и попробуйте добавить M3: сочетание должно быть недоступно. Можно также проверить, что третья мера одного направления заблокирована, а «Обновить отчёт» повторяет только AI-запрос, сохраняя расчёт.

Автоматические проверки

npm test
npx tsc --noEmit
npm run build

Тесты проверяют исходные показатели, каталог, веса, эффекты, лаги, синергии, ограничения, частичные отправки, точные результаты сценариев, геометрию объединения районов и вспомогательную логику интерфейса. Проверки AI используют подмену клиента; успешные тесты не подтверждают доступность внешних API.

В среде, где запрещено создание IPC-сокета CLI tsx, те же тесты можно запустить напрямую:

node --import tsx --test tests/*.test.mjs src/server/*.test.ts src/simulator/*.test.ts

Данные и интеграции

Игровые данные. Источник стартовых значений и долей населения — src/simulator/districts.ts; эффектов, лагов, синергий и несовместимостей — src/simulator/measures.ts; весов — src/simulator/scoring.ts. Названия и определения показателей находятся в src/simulator/presentation.ts. Это локальные заданные таблицы, без загрузки актуальной городской статистики.

2ГИС. MapGL отображает географическую основу. Places API (https://catalog.api.2gis.com/3.0/items) возвращает геометрию районов для города с ID 9570771978420226. WKT преобразуется в полигоны. Территории Алматы и Сарайшыка геометрически объединяются в один игровой район Алматы с одной долей населения и набором показателей. Это условность сценария, а не утверждение об административном делении.

OpenAI. Сервер передаёт рассчитанные результаты и контекст изменений в Responses API. Модель получает инструкцию объяснять существующие числа, не придумывая прогнозов; ответ ограничивается 50 словами. Запрос выполняется с store: false, тайм-аутом 20 секунд и без автоматических повторов. Ошибка возвращается как статус недоступного пояснения.

Ограничения текущей версии

  • MVP ориентирован прежде всего на настольный экран; интерфейс русскоязычный.
  • Доступен один сценарий Астаны, пять игровых районов, 14 фиксированных мер и раунд из пяти решений. Редактора сценариев в интерфейсе нет.
  • Модель использует заданные эффекты и фиксированный горизонт, не учитывает реальные динамические данные и не валидирована как инструмент городского прогнозирования. Название проекта не означает наличие пятичасового таймера.
  • Нет авторизации, базы данных, сохранения раундов между перезагрузками и многопользовательского режима. Состояние раунда хранится в памяти вкладки; в localStorage сохраняется только выбранная тема.
  • Применённые меры нельзя отменить отдельно: можно удалить черновик или начать новый раунд.
  • Полноценная карта зависит от сети, доступности 2ГИС и разрешений ключа. Геометрия не заменяет синтетические показатели.
  • AI необязателен и зависит от доступности API и выбранной модели. Текст не является источником числового результата.

Deployed-версия

Подтверждённая ссылка на работающую публичную версию в репозитории не указана. Для демонстрации жюри используйте локальный запуск по инструкции выше: http://localhost:3000.

Звёзды
⭐ 0
Форки
0
Язык
TypeScript
Последний push
в прошлом месяце
Коммиты
24
Авторы
3
Файлы
64
Ветки
3

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

Degr1s 14cloudlybtw 7Nocs782 3

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

  • 2026-09-23 17:58Update README.mdDegr1s
  • 2026-09-23 17:56Merge latest theme changes and finalize local setup documentationcloudlybtw
  • 2026-09-23 17:54Keep setup instructions focused on local developmentcloudlybtw
  • 2026-09-23 17:53Configure Cloudflare Workers deploymentcloudlybtw
  • 2026-09-23 17:53Document project in Russian and add demo recordingcloudlybtw
  • 2026-09-23 17:53Add dark city map and persistent top-right day-night theme toggleNurssk
  • 2026-09-23 17:44Add construction loading animation, report retry and simpler round summaryNurssk
  • 2026-09-23 17:35Merge origin/main and reconcile concise GPT analysis instructionsNurssk
Все коммиты (24)
  • 2026-09-23 17:34Polish city HUD and add step feedback, round summary and concise GPT explanationsNurssk
  • 2026-09-23 17:27rechecked the prompt for the open aiNocs782
  • 2026-09-23 16:47Add bottom action dock, incremental rounds and district indicator trendsNurssk
  • 2026-09-23 16:26Add deterministic exhaustive campaign optimizercloudlybtw
  • 2026-09-23 16:04Add district dropdowns to campaign selectioncloudlybtw
  • 2026-09-23 15:55some backend logic extendedNocs782
  • 2026-09-23 15:47Redesign city HUD with blue accents and a light fullscreen mapNurssk
  • 2026-09-23 15:26Install backend SDK dependency and include server testsNurssk
  • 2026-09-23 15:25Merge remote-tracking branch 'origin/main'Nurssk
  • 2026-09-23 15:25Group initiative selection by development categoryNurssk
  • 2026-09-23 15:23some backend logicNocs782
  • 2026-09-23 15:12Merge origin/main with real 2GIS map and six-district selectionNurssk
  • 2026-09-23 15:08Preserve local Astana simulator with 2GIS district mapNurssk
  • 2026-09-23 14:58feat: list selectioncloudlybtw
  • 2026-09-23 14:40Add 2GIS district command map UINurssk
  • 2026-09-23 14:31Add TanStack simulation frontend skeletonNurssk
Структура репозитория (64 файлов)
.cta.json
.env.example
.gitignore
.nvmrc
DESIGN.md
PRODUCT.md
README.md
data/campaigns.json
hack-alem.gif
hack-alem.mp4
package-lock.json
package.json
src/components/AIExplanation.tsx
src/components/ActionResult.tsx
src/components/AstanaMap.tsx
src/components/IndicatorStatistics.tsx
src/components/InitiativeCard.tsx
src/components/RoundSummary.tsx
src/components/ThemeToggle.tsx
src/lib/action-feedback.ts
src/lib/analysis-text.ts
src/lib/astana-districts.ts
src/lib/indicator-stats.ts
src/lib/initiative-tabs.ts
src/lib/map-style.ts
src/lib/round-feedback.ts
src/lib/scenario-districts.ts
src/lib/selection.mjs
src/routeTree.gen.ts
src/router.tsx
src/routes/__root.tsx
src/routes/index.tsx
src/server/analysis-context.test.ts
src/server/analysis-context.ts
src/server/analysis.server.ts
src/server/analysis.test.ts
src/server/simulateCity.ts
src/simulator/README.md
src/simulator/breakdown.test.ts
src/simulator/breakdown.ts
src/simulator/districts.ts
src/simulator/example.ts
src/simulator/measures.ts
src/simulator/presentation.ts
src/simulator/scoring.ts
src/simulator/simulate.test.ts
src/simulator/simulate.ts
src/simulator/types.ts
src/simulator/validator.ts
src/styles.css
tests/action-feedback.test.mjs
tests/analysis-text.test.mjs
tests/indicator-stats.test.mjs
tests/initiative-card.test.mjs
tests/map-style.test.mjs
tests/progress.test.mjs
tests/reference-dataset.test.mjs
tests/round-feedback.test.mjs
tests/scenario-districts.test.mjs
tests/selection.test.mjs
tsconfig.json
tsr.config.json
vite.config.ts
wrangler.jsonc
Ветки (3)

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

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

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