О проекте
Команда: STS Работаем за токены
Кейс HackAlem.ai: Спецтрек — Astana Innovations
Стадия: Доведённый продукт
Задача кейса: «Аким на 5 часов»: городской симулятор, распределяющий бюджет по 5 направлениям и 5 районам
Суть
Городской симулятор для Астаны: пользователь выбирает 5 инициатив по районам в рамках бюджета 100, получает Astana Quality of Life Score, карту районов, фронт Парето и записку акиму. Есть AI-советник с вызовом инструментов движка.
Как устроено
Данные (data/*.json: районы, меры, конфиг) проверяются Zod и передаются в детерминированный TypeScript-движок. Он считает лаги, синергии, районные баллы и Score = 0.7·D_avg + 0.3·D_min − N_crit. Оптимизатор полным перебором проходит 694 395 допустимых сценариев (лучший и худший набор, Парето, ранг). Три API-маршрута (analyze/advice/agent) используют LLM только для текста и вызова 8 инструментов (evaluate_scenario, find_best, suggest_swaps, pareto, timeline и др.), ограничение 6 раундов. Без ключа работает шаблонный fallback.
Ключевые решения
- детерминированный симулятор
- полный перебор сценариев
- фронт Парето
- локальный поиск одиночной заменой (swap)
- function calling
- Zod-валидация аргументов инструментов
- квартальный timeline с лагами
- SVG-карта по GeoJSON OSM
Метрики
Score: baseline 52.56, эталон 56.54 (расход 95), лучший 57.24 (98), худший 52.04 (80); совет 56.54→56.99; перебор 694 395 сценариев
Особенности
Строгое разделение: все числа считает детерминированный движок, LLM только объясняет и вызывает проверяемые инструменты. Числа в тексте LLM заменяются шаблоном, совет перед применением повторно валидируется. Журнал вызовов инструментов виден в UI.
Стек
TypeScript · Next.js · React · Tailwind CSS · Zod · OpenAI SDK · Vitest · Docker
Модели
gpt-5.4-mini · gpt-4o-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-ee17ec59-sts.
README
README.mdАким на 5 часов
AI-симулятор управления городом для HackAlem AI. Задача Astana Innovations: распределить бюджет 100 условных единиц между пятью инициативами и увидеть последствия для районов Астаны.
Пользователь собирает план, получает Astana Quality of Life Score, сравнивает его с другими допустимыми наборами и может применить проверенный совет AI. Все числовые результаты рассчитывает детерминированный TypeScript-движок; языковая модель объясняет результаты и вызывает инструменты анализа.
Быстрый запуск
Нужны Node.js 20.9+ и npm. Для локальной разработки проверен Node.js 22.
npm ci
npm run dev
Откройте localhost:3000. Без API-ключа работают расчёты, карта, оптимизатор, Парето и шаблонные объяснения.
Для включения AI скопируйте .env.example в .env.local, если этого файла ещё нет:
# macOS / Linux
cp .env.example .env.local
# Windows PowerShell
Copy-Item .env.example .env.local
Вставьте ключ в LLM_API_KEY и перезапустите приложение. Пример уже связывает OPENAI_API_KEY и OPENAI_MODEL с LLM_*, чтобы включить все три AI-маршрута. Существующий файл с ключами не перезаписывайте.
Docker Compose
Нужны Docker и Docker Compose 2.24+. Запуск без ключа и файла .env:
docker compose up --build
Для AI создайте .env из .env.example, заполните ключ и запустите ту же команду. Compose читает секреты при запуске контейнера; они не включаются в образ. Приложение доступно на localhost:3000.
Dockerfile собирает Next.js standalone и запускает его от непривилегированного пользователя. Контейнерный запуск на машине подготовки проекта не проверен: Docker CLI отсутствовал.
Возможности
| Возможность | Что доступно в приложении |
|---|---|
| Пять решений | Выбор инициатив и районов, стоимость, остаток бюджета и ошибки валидации |
| Расчёт последствий | Городской Score, баллы районов до/после, критические показатели и синергии |
| Карта | Интерактивная SVG-карта пяти районов по локальному GeoJSON; выбор района, слои «D после» и «Изменение D» |
| Лучший и худший набор | Загрузка сценариев из полного перебора допустимых решений |
| Сравнение с другими наборами | Место и процентиль текущего сценария |
| Парето | График лучшего Score при разных бюджетах, отметка текущего плана и отставание от оптимума |
| Записка акиму | Итог, сильные стороны, компромиссы, риски и следующий шаг; числовые факты округлены приложением |
| Применение совета | Предпросмотр одиночной замены с ростом Score без увеличения расходов, применение и отмена |
| AI-советник | Разбор сценария, вопросы, три демо-вопроса и раскрываемый журнал вызовов инструментов |
Квартальный timeline реализован в движке и доступен AI-агенту через инструмент timeline. Отдельного графика timeline в интерфейсе пока нет.
Демо за несколько минут
- Нажмите «Загрузить эталонный сценарий», затем «Рассчитать сценарий».
- Проверьте расход 95/100 и Score 56.54 при исходном 52.56.
- Посмотрите районные результаты и переключите карту на «Изменение D».
- Сравните текущий план с графиком Парето и его местом среди допустимых наборов.
- Получите совет об улучшении, посмотрите изменение бюджета и Score, примените его и отмените. Для эталона проверен переход 56.54 → 56.99, расход 95 → 86.
- В «AI-советнике» нажмите «Разобрать сценарий» или задайте: «Какой лучший набор, если не трогать Есиль и уложиться в 80?». При настроенной LLM откройте журнал инструментов и проверьте ограничения в вызове
find_best. - Для более заметного эффекта загрузите «Худший сценарий», рассчитайте его и запросите улучшение.
Эталон: M7, M8 и M10 в Нуре; M12 для всего города; M5 в Сарыарке. Подробный сценарий проверки совета — AI_ADVICE_DEMO.md.
Скриншоты
Конструктор сценария
Выбор пяти инициатив и районов.

Результаты
Эталонный сценарий: Score 56.54 при бюджете 95/100.

Карта районов
Изменение районных баллов после выбранных инициатив.

Парето
Сравнение текущего плана с лучшими сценариями при разных бюджетах.

AI-советник
Вопросы и резервный разбор сценария по расчётам движка. На этом снимке показан режим fallback.

Журнал инструментов
Вызовы инструментов с аргументами и результатами расчётов.

AI и проверяемые рекомендации
Три серверных маршрута решают разные задачи:
POST /api/analyzeсоставляет записку по результату симуляции. Приложение формирует числовой итог и сведения о слабейшем районе; LLM добавляет текст. Ответы с техническими именами и цифрами в этой текстовой части заменяются шаблонным объяснением.POST /api/adviceищет улучшение одной заменой, проверяет допустимость, рост отображаемого Score и отсутствие роста расходов. Перед применением клиент повторно проверяет предложение и пересчитывает результат. Это локальное улучшение, а не обещание глобального оптимума.POST /api/agentотвечает в режимахexplainиchat, вызывает инструменты движка и возвращает{ answer, trace, mode }. Панель показывает ответ и фактические вызовы инструментов с их результатами.
Агенту доступны восемь инструментов: evaluate_scenario, validate_scenario, find_best, find_worst, suggest_swaps, pareto, timeline, score_rank. Их аргументы проверяются через Zod. Цикл ограничен шестью раундами инструментов, после которых разрешён один финальный ответ модели без инструментов.
Без LLM или при ошибке провайдера сохраняются расчёты и резервные ответы. В режиме explain агент возвращает шаблонный разбор. В режиме chat резервный ответ явно показывает лучший набор без ограничений из вопроса: полноценное понимание вопроса требует LLM.
Переменные окружения
| Переменная | Где используется |
|---|---|
LLM_API_KEY | Агент и карточка совета; в .env.example также передаётся в OPENAI_API_KEY |
LLM_MODEL | Агент и карточка совета; пример конфигурации выбирает gpt-5.4-mini |
LLM_BASE_URL | OpenAI-совместимый Chat Completions endpoint для агента и карточки совета |
OPENAI_API_KEY | Записка акиму; резервный ключ карточки совета |
OPENAI_MODEL | Модель записки; резервная модель карточки совета |
OPENAI_BASE_URL | Необязательный endpoint SDK для записки |
Значения по умолчанию в маршрутах — gpt-4o-mini; файл примера переопределяет модель. Для собственного совместимого провайдера задайте и LLM_BASE_URL, и OPENAI_BASE_URL, а также доступную модель. Для записки и совета провайдер должен поддерживать структурированный JSON-ответ, для агента — вызов инструментов.
В Docker entrypoint подставляет LLM_* в отсутствующие OPENAI_*. Секреты хранятся только в локальных env-файлах и исключены из Git.
Модель расчёта
Исходные показатели и каталог инициатив синтетические. Симуляция показывает последствия в рамках заданной модели и не является прогнозом фактического эффекта муниципальной политики.
Эффекты инициатив учитывают лаги, синергии и размещение. Значения показателей ограничиваются диапазоном 0–100. Районный балл — взвешенная сумма показателей; средний городской балл учитывает доли населения.
Score = 0.7 × D_avg + 0.3 × D_min − N_crit
D_avg— средневзвешенный балл районов.D_min— балл слабейшего района.N_crit— число показателей ниже 40 во всех районах.
Валидатор требует ровно пять разных мер, бюджет не выше 100, не больше двух мер одного направления, корректное размещение и отсутствие конфликтов. Пустой сценарий используется движком только как baseline.
В квартале q доля эффекта равна max(0, q − L) / 8, где L — лаг меры; синергия включается после лагов обеих мер. Последняя точка timeline совпадает с обычным результатом симуляции. Внутренние расчёты сохраняют точность; отображаемые числа округляются.
| Контрольный сценарий | Расход | Score |
|---|---|---|
| Исходное состояние | 0 | 52.56 |
| Эталон | 95 | 56.54 |
| Лучший найденный | 98 | 57.24 |
| Худший допустимый | 80 | 52.04 |
Оптимизатор перебирает 694 395 допустимых сценариев и сохраняет результаты в памяти серверного процесса. Подробные наборы, параметры и замеры — ENGINE_RESULTS.md; формулы — SIMULATION_SPEC.md.
Архитектура
Next.js App Router, TypeScript, React, Tailwind CSS, Zod, OpenAI SDK и Vitest. Для MVP не нужны база данных, авторизация или GPU.
flowchart LR
data["data/*.json"] --> loader["Zod: загрузка и проверка"]
loader --> engine["TypeScript: симуляция и оптимизатор"]
engine --> ui["React: решения, результаты, карта, Парето"]
geo["Локальный GeoJSON"] --> ui
ui --> api["analyze / advice / agent"]
api --> tools["Проверяемые инструменты"]
tools --> engine
api <--> llm["LLM: текст и вызовы инструментов"]
api --> fallback["Резервный ответ без LLM"]
| Путь | Назначение |
|---|---|
data/*.json | Единственный каталог числовых данных и параметров |
data/geo/ | Геометрия и сведения об источниках |
src/lib/simulation/ | Расчёты, валидация, оптимизатор, инструменты и проверка советов |
src/lib/analysis.ts | Читаемые факты и шаблонная записка |
src/app/api/ | Три серверных AI-маршрута |
src/components/ | Конструктор, результаты, карта, Парето и AI-панель |
tests/ | Проверки движка, API, советов, геоданных и служебных скриптов |
Карта использует локальные геометрии OpenStreetMap, включая реальную границу района Алматы. Модель включает пять районов из задания; она не охватывает полное современное административное деление города. Источники и атрибуция: data/geo/README.md, © OpenStreetMap contributors, ODbL 1.0.
Проверка
npm test
npm run lint
npm run build
Последний полный прогон приложения: 127 тестов в 9 файлах, lint и production build прошли. Проверяются baseline, эталон и невалидные сценарии, оптимизатор, ограничения, рейтинг, Парето, timeline, API и fallback, применение советов, форматирование записки, каталог названий для агента и геоданные. Пользовательский путь «эталон → совет → применить → отменить» также проверен вручную.
Границы MVP
- Отдельный график timeline пока не подключён; расчёт доступен движку и агенту.
- В интерфейсе местами остаются технические коды инициатив и показателей.
- Карточка совета проверяет одну замену; глобальный поиск доступен отдельно.
- События, лидерборд команд, сохранение сценариев и многопользовательский режим не реализованы.
- Данные и веса модели требуют калибровки для использования в реальном планировании.
Команда: Дамир, Расул и Шахнияр.
Авторы коммитов
История коммитов
- 2026-09-23 17:51fix(ui): distinguish AI panel keys and restore README screenshotsingaralai-wq
- 2026-09-23 17:46merge: ui into main (design and screenshots)rasss1k
- 2026-09-23 17:45merge: main into ui, preserve READMErasss1k
- 2026-09-23 17:44feat(ui): design fixes and demo screenshotsrasss1k
- 2026-09-23 17:42fix: harden AI requests and resolve final UI issuesingaralai-wq
- 2026-09-23 17:37docs: align submission README with implemented featuresingaralai-wq
- 2026-09-23 17:29docs: READMEШахнияр
- 2026-09-23 17:25readme: Update README.mdШахнияр
Все коммиты (68)
- 2026-09-23 17:14ui: add minimal agent panel with explain chat and traceШахнияр
- 2026-09-23 17:11fix(ai): make mayor brief readable and groundedingaralai-wq
- 2026-09-23 17:09Merge branch 'main' into codex/damir/simulation-engineHenry Chinaski
- 2026-09-23 17:08Merge pull request #2 from BAITC-Hacks/codex/damir/apply-adviceingaralai-wq
- 2026-09-23 17:08Merge branch 'main' into codex/damir/apply-adviceingaralai-wq
- 2026-09-23 17:06merge: ui в mainrasss1k
- 2026-09-23 17:03fix(map): реальная граница Алматы из data/geo, убрана схемаrasss1k
- 2026-09-23 17:00merge: main в ui (реальная граница Алматы)rasss1k
- 2026-09-23 16:58docs(status): UI по решению PMrasss1k
- 2026-09-23 16:55feat(ui): график Парето бюджет/Scorerasss1k
- 2026-09-23 16:51engine: A3 agent routeШахнияр
- 2026-09-23 16:51feat(ui): лучший/худший сценарий и место в рейтингеrasss1k
- 2026-09-23 16:49test: verify OSM Almaty boundaryШахнияр
- 2026-09-23 16:44data: almaty boundaryШахнияр
- 2026-09-23 16:43engine: A3 agent routeШахнияр
- 2026-09-23 16:41merge: main в uirasss1k
- 2026-09-23 16:31chore(config): select GPT-5.4 mini for advice demoingaralai-wq
- 2026-09-23 16:24feat(layout): две колонки на ≥1200px, результаты справаrasss1k
- 2026-09-23 16:23docs(demo): record readiness checks and map integration handoffingaralai-wq
- 2026-09-23 16:17docs(advice): add local key setup and reproducible demo scenarioingaralai-wq
- 2026-09-23 16:09fix(map): схематичный полигон Алматыrasss1k
- 2026-09-23 16:08engine: U6 dockerШахнияр
- 2026-09-23 16:04fix(map): убрать пустое пространство над картойrasss1k
- 2026-09-23 16:01feat(map): адаптивная шкала и слои D после / изменение Drasss1k
- 2026-09-23 15:59merge: sync verified advice with latest engine tools and team planingaralai-wq
- 2026-09-23 15:57feat(advice): preview, apply and undo verified scenario improvementingaralai-wq
- 2026-09-23 15:56merge: main в ui (E3/E4), карта сохраненаrasss1k
- 2026-09-23 15:54engine: E3c agent toolsШахнияр
- 2026-09-23 15:50engine: E3b worst + rankШахнияр
- 2026-09-23 15:47engine: reassign ticketsШахнияр
- 2026-09-23 15:44feat(scenario): выбор района на карте и onScenarioChangerasss1k
- 2026-09-23 15:44feat(map): SVG-карта районов на основе data/georasss1k
- 2026-09-23 15:40engine: E4 constraints, pareto, timelineШахнияр
- 2026-09-23 15:34engine: E3 optimizer + swapsШахнияр
- 2026-09-23 15:32feat(scenario): индикатор бюджета и счётчик решенийrasss1k
- 2026-09-23 15:32feat(scenario): отображение ошибок валидации и состояние кнопки расчётаrasss1k
- 2026-09-23 15:32merge: main в ui, конфликты конструктора разрешены в пользу компонентной структурыrasss1k
- 2026-09-23 15:25engine: compress plan to 18:00Шахнияр
- 2026-09-23 15:20fix(engine): expose deterministic baseline snapshotingaralai-wq
- 2026-09-23 15:18engine: switch to TS-only architecture, update planШахнияр
- 2026-09-23 15:15feat(scenario): отображение ошибок валидации и состояние кнопки расчётаrasss1k
- 2026-09-23 15:08feat(scenario): индикатор бюджета и счётчик решенийrasss1k
- 2026-09-23 15:07feat(simulation): русские подписи направлений и показателейrasss1k
- 2026-09-23 15:07feat(scenario): слоты решений, выбор меры и района, карточка мерыrasss1k
- 2026-09-23 15:06Merge remote-tracking branch 'origin/main' into uirasss1k
- 2026-09-23 15:06Add deterministic nearby scenario suggestionsingaralai-wq
- 2026-09-23 15:03feat(scenario): слоты решений, выбор меры и района, карточка мерыrasss1k
- 2026-09-23 14:58feat(simulation): русские подписи направлений и показателейrasss1k
- 2026-09-23 14:55engine: project plan, status boardШахнияр
- 2026-09-23 14:53Merge teammate map materials without breaking Next runtimeingaralai-wq
- 2026-09-23 14:47Merge working simulator into mainingaralai-wq
- 2026-09-23 14:45[shakhniyar] merge Python and Next.js gitignore rulesШахнияр
- 2026-09-23 14:43Build end-to-end scenario simulation experienceingaralai-wq
- 2026-09-23 14:41engine: merge local work, remove nested reposШахнияр
- 2026-09-23 14:41Merge pull request #1 from BAITC-Hacks/codex/damir/simulation-engineingaralai-wq
- 2026-09-23 14:41Merge branch 'main' into codex/damir/simulation-engineingaralai-wq
- 2026-09-23 14:33Clarify runtime and teammate workflowingaralai-wq
- 2026-09-23 14:24Scaffold app and add deterministic simulation engineingaralai-wq
- 2026-09-23 14:19chore: project skeleton, data, interface contractШахнияр
- 2026-09-23 14:15Add hackathon product and delivery documentationingaralai-wq
Структура репозитория (100 файлов)
.dockerignore .env.example .gitattributes .githooks/pre-push .gitignore AGENTS.md CLAUDE.md Dockerfile README.md data/config.json data/districts.json data/geo/README.md data/geo/almaty-validation.json data/geo/astana_districts.geojson data/measures.json docker-compose.yml docs/AGENT_PROMPTS.md docs/AI_ADVICE_DEMO.md docs/ARCHITECTURE.md docs/DELIVERY_PLAN.md docs/DEMO.md docs/DEMO_READY.md docs/ENGINE_RESULTS.md docs/PLAN.md docs/PM_HANDOFF.md docs/PRD.md docs/SIMULATION_SPEC.md docs/STATUS.md docs/TEAM_BRIEFING.md docs/examples/advice-scenario.json docs/log/agent-panel.md docs/log/damir.md docs/log/rasul.md docs/log/shakhniyar.md docs/screenshots/ai-trace.png docs/screenshots/ai.png docs/screenshots/main.png docs/screenshots/map.png docs/screenshots/pareto.png docs/screenshots/results.png docs/status/META.md docs/status/ai.md docs/status/engine.md docs/status/ui.md eslint.config.mjs next-env.d.ts next.config.ts package-lock.json package.json postcss.config.mjs scripts/docker-entrypoint.mjs scripts/install-hooks.mjs scripts/status.mjs src/app/api/advice/route.ts src/app/api/agent/route.ts src/app/api/analyze/route.ts src/app/globals.css src/app/layout.tsx src/app/page.tsx src/components/agent-panel/AgentPanel.tsx src/components/map/DistrictMap.tsx src/components/map/districtGeometry.ts src/components/map/generateGeometry.mjs src/components/pareto/ParetoChart.tsx src/components/pareto/paretoActions.ts src/components/results/AdviceCard.tsx src/components/results/MayorBrief.tsx src/components/results/ResultsDashboard.tsx src/components/scenario/BudgetMeter.tsx src/components/scenario/DecisionSlot.tsx src/components/scenario/MeasureDetails.tsx src/components/scenario/ScenarioBuilder.tsx src/components/scenario/ValidationSummary.tsx src/components/scenario/optimizerActions.ts src/lib/agent/demo-questions.json src/lib/analysis.ts src/lib/simulation/advice.ts src/lib/simulation/data.ts src/lib/simulation/index.ts src/lib/simulation/labels.ts src/lib/simulation/numeric.ts src/lib/simulation/optimize.ts src/lib/simulation/optimizer.ts src/lib/simulation/scenarios.ts src/lib/simulation/simulate.ts src/lib/simulation/timeline.ts src/lib/simulation/tools.ts src/lib/simulation/types.ts src/lib/simulation/validate.ts tests/advice-route.test.ts tests/advice.test.ts tests/agent-route.test.ts tests/analysis-route.test.ts tests/analysis.test.ts tests/geo.test.mjs tests/optimizer.test.ts tests/simulation.test.ts tests/status.test.mjs tsconfig.json vitest.config.mjs
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.