AAYMA
RU
Войти

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

Веб-симулятор городского планирования на Next.js: пользователь выбирает 5 мероприятий из 14 для 5 районов Астаны на SVG-карте, приложение проверяет…

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

О проекте

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

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

Суть

Веб-симулятор городского планирования на Next.js: пользователь выбирает 5 мероприятий из 14 для 5 районов Астаны на SVG-карте, приложение проверяет план и рассчитывает индекс качества жизни с диаграммой «До/После» и AI-разбором по проверяемым фактам.

Как устроено

Валидатор проверяет, что в плане 5 различных мер, бюджет ≤100, не более двух мер одного направления и нет несовместимых пар. Симулятор применяет эффект × (8 − задержка)/8 и синергии, ограничивает показатели 0–100. Индекс = 0,7 × средневзвешенная оценка + 0,3 × минимум района − число показателей ниже 40. Сервер /api/analyze пересчитывает сценарий и передаёт факты в OpenAI Responses API (Structured Outputs); модель выбирает только ID фактов и может предложить замену одной меры, которая проходит через тот же валидатор и симулятор. Тексты формирует код.

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

  • детерминированная симуляция с лагами и синергиями
  • Zod-валидация схем
  • Structured Outputs
  • проверка альтернативы через симулятор
  • кэш и лимиты запросов

Особенности

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

Стек

TypeScript · Next.js · React · Tailwind CSS · Zod · OpenAI SDK · Vitest

Модели

OpenAI


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

README

README.md

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

Учебный симулятор городского планирования: пять мероприятий, бюджет 100 единиц и результат через два условных года.

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

Все показатели, доли населения, стоимости и эффекты в модели синтетические. Результат не является прогнозом развития Астаны или обоснованием реального управленческого решения.

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

  • Три шага: составление плана, проверка и просмотр результата.
  • Каталог из 14 мероприятий по пяти направлениям: транспорт, экология, социальная сфера, безопасность и сервисы.
  • Пять районов: Есиль, Алматы, Сарыарка, Байконур, Нура. Выбор через локальную SVG-карту или синхронизированный список.
  • Добавление районного мероприятия одним нажатием после выбора района. Городские мероприятия действуют на весь город.
  • План с бюджетом, остатком, счётчиком мероприятий, удалением и изменением района. Смена контекста на карте не перемещает уже добавленные мероприятия.
  • Проверка бюджета, количества, повторов, направлений и несовместимости мероприятий.
  • Детерминированный расчёт индекса, районных оценок и отдельных показателей с учётом задержек и совместных эффектов.
  • Диаграмма «До / После» для пяти районов на общей шкале 0–100, раскрываемая точная таблица и подробности расчёта.
  • Необязательный AI-разбор с проверяемыми фактами и проверкой предложенной альтернативы.
  • Готовый пример, подтверждение сброса и замены плана, адаптивная компоновка, управление картой и диалогами с клавиатуры.

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

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

Состояние плана хранится в памяти открытого приложения. Возврат между шагами сохраняет выбор, перезагрузка страницы — нет. «Сбросить план» требует подтверждения; «Отмена» лишь закрывает окно, а не сохраняет план на сервере.

Технологии

ОбластьРеализация в репозитории
ЯзыкTypeScript
ПриложениеNext.js 16, App Router; React 19
ИнтерфейсHTML/CSS, Tailwind CSS 4, локальный SVG; диаграмма на HTML/CSS
Проверка данныхZod 4
AIСерверный OpenAI SDK 6, Responses API, Structured Outputs
ТестыVitest 5, jsdom 30
Инструментыnpm, TypeScript compiler, Prettier

Конкретная AI-модель не зафиксирована: её ID задаётся в OPENAI_MODEL. Код ожидает модель с поддержкой Responses API и строгого структурированного ответа. Подтверждённую модель нельзя назвать по пустому шаблону конфигурации. Базы данных, регистрации, картографического API и внешней аналитики в текущей реализации нет.

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

app/page.tsx → Simulator → каталог, карта, план, проверка, результат
                            │
                            ├─ validateScenario → simulateScenario
                            │                       └─ оценки и диаграмма
                            │
                            └─ AnalysisRequest → POST /api/analyze
                                                  ├─ проверка входных данных
                                                  ├─ повторный расчёт на сервере
                                                  ├─ кэш / лимиты → OpenAI
                                                  └─ проверка фактов и альтернативы
Файл или каталогНазначение
components/Simulator.tsxСостояние плана и переходы между тремя шагами
components/Карта, каталог, сводка плана, диалоги, результаты, диаграмма и AI-блок
data/city.ts, data/measures.ts, data/rules.tsРайоны и показатели, каталог, веса и синергии
data/example.tsГотовый демонстрационный план
lib/validate.ts, lib/scenario-schema.tsПравила допустимого плана и строгая схема входных данных
lib/simulate.tsОбщий расчёт для интерфейса и сервера
lib/format.tsОтображение чисел и склонения
lib/ai-client.tsОтправка запроса, отмена и защита от повторных и устаревших ответов
app/api/analyze/route.ts, lib/server/Серверная интеграция OpenAI, проверка ответа, лимиты и кэш
data/geography/Локальная геометрия карты, исходное изображение и атрибуция
tests/Проверки расчёта, правил, UI-сценариев и AI с моками

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

Допустимый план содержит ровно пять различных мероприятий, стоит не больше 100 единиц и включает не больше двух мероприятий одного направления. Все пять направлений охватывать необязательно.

Несовместимости: M1 и M3 — при любых районах; M4 и M7, M5 и M13 — только в одном районе. Районное мероприятие требует район; городское не принимает его.

Горизонт — восемь кварталов. Эффект с учётом задержки:

эффект = полный эффект × (8 − задержка) / 8

Учитываются и отрицательные эффекты. Дополнительно применяются фиксированные совместные эффекты:

  • M1 + M2: T1 +2 в районе M1;
  • M10 + M12: B1 +2 в районе M10;
  • M5 + M6: E2 +2 в районе M5.

После суммирования отдельные показатели ограничиваются диапазоном 0–100. Районная оценка — взвешенная сумма десяти показателей; веса заданы в data/rules.ts.

средняя оценка города = сумма(доля населения района × оценка района)
индекс качества жизни = 0,7 × средняя оценка города
                      + 0,3 × минимальная оценка района
                      − число показателей ниже 40

Считаются пары «район / показатель». Порог строго ниже 40 относится к отдельным показателям, а не к оценке района. Сам итоговый индекс не ограничивается диапазоном 0–100. Неиспользованный бюджет не даёт бонуса.

Каждый расчёт начинается с исходного датасета. Внутренние значения не округляются; интерфейс использует ru-RU и два десятичных знака с устранением двоичного шума при отображении.

Как устроен AI-разбор

AI не рассчитывает индекс. Сервер самостоятельно проверяет план и использует тот же симулятор, что и интерфейс. Модели передаются раздельно индекс, средняя оценка города, минимум по районам, показатели, бюджет, мероприятия с областью действия, эффекты с учётом задержки, синергии и каталог допустимых вариантов.

Модель выбирает ID фактов для разделов «Главное улучшение», «Что осталось проблемой», «Что можно изменить» и может предложить замену одного мероприятия или смену его района. Добавление шестого мероприятия не предлагается как допустимый вариант.

  • Отрицательные эффекты выбранных мероприятий обязательно включаются в краткий разбор и подробности, даже если модель их не выбрала. Это эффекты отдельных мероприятий, а не утверждение об итоговом ухудшении показателя после всех изменений.
  • Строгая схема Zod проверяет структуру ответа, затем код проверяет существование и категории фактов.
  • Формулировки и численные подписи формируются кодом по этим фактам. Это ограниченный формат AI-разбора, а не свободный текст модели и не fallback при ошибке провайдера.
  • Предложенная альтернатива проходит существующий валидатор и симулятор. Числа берутся только из расчёта; улучшение не гарантируется.
  • Краткая и подробная части используют один ответ. Раскрытие «Подробный анализ» не вызывает новый запрос.
  • Ошибка или отключение AI не мешает просмотру результата; доступно действие «Повторить анализ».

API принимает только { "decisions": [...] }, возвращает { analysis, scenarioId, cached }. Поля analysis: improvement, problem, next, factIds, details, alternative. Модель возвращает improvementFactId, problemFactId, detailFactIds и replacement либо null в этом поле. Клиентские цены и рассчитанные значения не принимаются как достоверные.

Установка и запуск

1. Подготовьте окружение

Нужны Node.js и npm. Поддерживаемые версии Node.js из package.json: 22.12+ в ветке 22, 24.x или 26+.

Откройте терминал в корне репозитория:

npm ci
npm run dev

Откройте локальное приложение. Для симуляции ключи не нужны. Без настроек AI отключён.

2. При необходимости включите AI

Если .env.local ещё не существует, создайте его из шаблона:

cp .env.example .env.local

Если файл уже существует, не перезаписывайте его. В локальном редакторе укажите:

ПеременнаяНазначение
AI_ENABLEDtrue для включения AI; в шаблоне false
OPENAI_API_KEYСерверный ключ OpenAI
OPENAI_MODELID доступной вашему проекту модели, совместимой с используемым форматом

Имя файла должно быть точно .env.local, без пробелов. Перезапустите сервер после изменения настроек. Ключ не должен попадать в README, браузер или Git. При включённом AI переход к результату автоматически запускает запрос, который может быть платным.

3. Запустите production-сборку локально

npm run build
npm run start

Next.js-сервер нужен для /api/analyze; это не только набор статических файлов.

Как проверить решение: сценарий для жюри

Готовый план за две минуты

  1. На первом шаге нажмите «Попробовать готовый план». При наличии мероприятий подтвердите замену.

  2. В плане будут:

    МероприятиеТерритория
    M7 — Школа и детсадНура
    M8 — Центр семейного здоровья / поликлиникаНура
    M10 — Освещение и камерыНура
    M12 — Единая цифровая платформа обращенийВесь город
    M5 — Перевод частного сектора на чистое топливоСарыарка
  3. Проверьте бюджет 95 из 100, остаток 5, количество 5 из 5.

  4. Нажмите «Проверить план →», затем «Посмотреть результат».

  5. Ожидаемый индекс в интерфейсе: 52,56 → 56,54 (+3,99). Показателей ниже нормы: 2 → 0. Точные контрольные значения: 52.55768 → 56.54307.

  6. Посмотрите диаграмму пяти районов, раскройте «Показать точные значения» и подробности расчёта. При отключённом AI должен быть виден статус «AI-разбор сейчас недоступен».

  7. Нажмите «Изменить план», через «Изменить район» перенесите школу в другой доступный район и пересчитайте. Результат и диаграмма должны обновиться.

  8. Нажмите «Сбросить план» и сначала «Отмена»: план останется. При подтверждении «Сбросить» мероприятия и результат удаляются.

Дополнительная проверка модели

Составьте план M1 Сарыарка, M2 весь город, M4 Сарыарка, M8 Сарыарка, M14 весь город. Ожидаются бюджет 91, остаток 9, индекс 54,16. В Нуре сохраняются показатели S1 = 38 и S2 = 35. Совместный эффект M1 + M2 действует в Сарыарке; районные автобусные полосы не распространяются на весь город.

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

npm run test
npm run typecheck
npm run build

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

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

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

Данные симуляции

Источник всех численных входных данных — типизированные файлы data/city.ts, data/measures.ts и data/rules.ts. Это локальный синтетический набор, а не онлайн-статистика города. Во время работы приложение не получает реальные муниципальные показатели.

Карта и атрибуция

Карта — схематическая векторизация карты пяти районов 2023 года, а не точные или актуальные административные границы. Источник, зафиксированный в репозитории: Astana districts coloured 2023.png, автор Bogomolov.PL, редакция от 7 января 2023 года.

Исходное изображение и производная картографическая графика распространяются по CC BY-SA 4.0. Контуры упрощены, центральный участок Есиля оцифрован по тому же изображению; добавлены цвета, подписи, выноска и синтетические оценки. Атрибуция также показана под картой. Эти условия не устанавливают лицензию для всего приложения.

Геометрия хранится локально; картографические сервисы, ключи и загрузка тайлов не нужны. Север сверху, пропорции сохранены. Источники, ограничения точности и методика описаны в data/geography/README.md. Шестой район в расчёт не включён.

Внешняя интеграция

Единственная внешняя API-интеграция в коде — OpenAI, вызываемый с сервера при включённом AI. Для просмотра карты и математического расчёта внешние API не нужны.

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

  • Учебная модель не учитывает инфляцию, рост населения, макроэкономику и реальные сроки или стоимость проектов.
  • Нет учётных записей, базы данных, сохранения плана между перезагрузками, экспорта и полноценного оптимизатора.
  • География схематическая и ограничена пятью районами кейса; измерять по ней расстояния нельзя.
  • AI может выбрать не самый полезный факт или предложить неудачный вариант. Проверяемость чисел не означает оптимальность рекомендации. Совместимость конкретной модели и успешный реальный вызов не подтверждаются тестами с моками.
  • OpenAI-вызов ограничен 25 секундами и 1400 выходными токенами; store: false, автоматические повторы отключены. Неполный ответ считается ошибкой.
  • Тело AI-запроса ограничено 8 КБ. На процесс разрешены 6 новых вызовов в минуту и 2 одновременно.
  • Кэш содержит до 100 записей на 10 минут. Ключ включает сценарий, модель, DATA_VERSION и ANALYSIS_VERSION (grounded-facts-v4). Попадание в кэш не вызывает OpenAI повторно.
  • Лимиты и кэш хранятся в памяти, сбрасываются при перезапуске и не объединяются между экземплярами сервера. Аутентификации платного endpoint нет; текущая защита не заменяет общий контроль расходов при публичной эксплуатации.

Опубликованная версия

Подтверждённая ссылка на deployed-версию в репозитории не указана. Для демонстрации используйте локальный запуск по инструкции выше. Наличие production-сборки само по себе не подтверждает публикацию сайта.

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

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

ansagantoleutai 4mtokbola 1

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

  • 2026-09-23 17:46fix: designansagantoleutai
  • 2026-09-23 17:39fix: project logicsansagantoleutai
  • 2026-09-23 17:27feat: project logicsansagantoleutai
  • 2026-09-23 16:23add: project filesansagantoleutai
  • 2026-09-23 14:57docs: add initial Russian README for hackathon projectmtokbola
Структура репозитория (56 файлов)
.env.example
.gitignore
README.md
SECURITY_REVIEW.md
app/api/analyze/route.ts
app/globals.css
app/layout.tsx
app/page.tsx
components/AiAdvisor.tsx
components/DistrictComparison.tsx
components/DistrictMap.tsx
components/DistrictOverview.tsx
components/DistrictPicker.tsx
components/MeasureCatalog.tsx
components/Modal.tsx
components/PlanSummary.tsx
components/ScenarioResults.tsx
components/Simulator.tsx
data/city.ts
data/example.ts
data/geography/README.md
data/geography/astana-2023.json
data/geography/astana-districts-2023-source.png
data/measures.ts
data/rules.ts
lib/ai-client.ts
lib/analysis-schema.ts
lib/format.ts
lib/measure-copy.ts
lib/scenario-schema.ts
lib/server/analysis-context.ts
lib/server/analyze.ts
lib/server/openai.ts
lib/server/request-limits.ts
lib/simulate.ts
lib/types.ts
lib/validate.ts
next-env.d.ts
package-lock.json
package.json
postcss.config.mjs
scripts/trace-district-map.py
tests/ai-client.test.ts
tests/analysis-fixture.ts
tests/analysis-verification.test.ts
tests/analyze.test.ts
tests/district-comparison.test.tsx
tests/format.test.ts
tests/openai.test.ts
tests/plan-edit.test.ts
tests/server-only.ts
tests/simulate.test.ts
tests/simulator.test.tsx
tests/validate.test.ts
tsconfig.json
vitest.config.mts

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

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

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