О проекте
Команда: 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-разбор с проверяемыми фактами и проверкой предложенной альтернативы.
- Готовый пример, подтверждение сброса и замены плана, адаптивная компоновка, управление картой и диалогами с клавиатуры.
Как работает решение
- Составьте план развития города. Выберите район и добавьте мероприятия из каталога. Для городских мероприятий выбор района не нужен. Исходные проблемы районов доступны в раскрываемом блоке.
- Проверьте свой план. Когда выбраны пять допустимых мероприятий, нажмите «Проверить план →». На втором шаге показаны состав, районы, бюджет и мероприятия, усиливающие друг друга.
- Посмотрите результат. Нажмите «Посмотреть результат». Приложение сразу рассчитает индекс качества жизни, изменения районов и число показателей ниже нормы. Если AI настроен, разбор загружается отдельно и не блокирует математический результат.
- Сравните другой вариант. Нажмите «Изменить план», замените мероприятие или его район и повторите расчёт. Изменение плана сбрасывает прежний результат; запоздавший 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_ENABLED | true для включения AI; в шаблоне false |
OPENAI_API_KEY | Серверный ключ OpenAI |
OPENAI_MODEL | ID доступной вашему проекту модели, совместимой с используемым форматом |
Имя файла должно быть точно .env.local, без пробелов. Перезапустите сервер после изменения настроек. Ключ не должен попадать в README, браузер или Git. При включённом AI переход к результату автоматически запускает запрос, который может быть платным.
3. Запустите production-сборку локально
npm run build
npm run start
Next.js-сервер нужен для /api/analyze; это не только набор статических файлов.
Как проверить решение: сценарий для жюри
Готовый план за две минуты
-
На первом шаге нажмите «Попробовать готовый план». При наличии мероприятий подтвердите замену.
-
В плане будут:
Мероприятие Территория M7 — Школа и детсад Нура M8 — Центр семейного здоровья / поликлиника Нура M10 — Освещение и камеры Нура M12 — Единая цифровая платформа обращений Весь город M5 — Перевод частного сектора на чистое топливо Сарыарка -
Проверьте бюджет 95 из 100, остаток 5, количество 5 из 5.
-
Нажмите «Проверить план →», затем «Посмотреть результат».
-
Ожидаемый индекс в интерфейсе: 52,56 → 56,54 (+3,99). Показателей ниже нормы: 2 → 0. Точные контрольные значения:
52.55768 → 56.54307. -
Посмотрите диаграмму пяти районов, раскройте «Показать точные значения» и подробности расчёта. При отключённом AI должен быть виден статус «AI-разбор сейчас недоступен».
-
Нажмите «Изменить план», через «Изменить район» перенесите школу в другой доступный район и пересчитайте. Результат и диаграмма должны обновиться.
-
Нажмите «Сбросить план» и сначала «Отмена»: план останется. При подтверждении «Сбросить» мероприятия и результат удаляются.
Дополнительная проверка модели
Составьте план 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-сборки само по себе не подтверждает публикацию сайта.
Авторы коммитов
История коммитов
- 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 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.