О проекте
Команда: EzLyam
Кейс HackAlem.ai: Спецтрек — Astana Innovations
Стадия: Рабочий MVP
Задача кейса: «Аким на 5 часов»: городской симулятор, распределяющий бюджет по 5 направлениям и 5 районам
Суть
Симулятор «Аким на 5 часов» на Next.js: выбор 5 мер по районам, валидация правил датасета, детерминированный Score, рекомендации одиночных замен и AI-анализ сильных сторон и рисков.
Как устроено
В src/lib/simulation.ts валидатор (5 уникальных мер, ≤2 мер одного направления, бюджет ≤100, несовместимости), лаг (8−лаг)/8 и синергии, в scoring.ts формула Score = 0,7·D_avg + 0,3·min(D) − N_crit. API /api/analyze заново считает сценарий на сервере и передаёт факты в OpenAI Responses API со Structured Outputs. Ответ проверяется на структуру и отсутствие цифр, без ключа работает детерминированный fallback. Рекомендации получаются перебором допустимых одиночных замен.
Ключевые решения
- детерминированный scoring
- перебор одиночных замен
- OpenAI Responses API Structured Outputs
- валидация ответа LLM на отсутствие чисел
- детерминированный fallback
Метрики
33 теста, все 2002 сочетания 5 мер проверены; контрольный пример Score 52,55768 → 56,54307 (+3,98539)
Особенности
Из ответа LLM запрещены числовые символы, поэтому числа в объяснении берутся только из движка. Тесты перебирают все 2002 комбинации мер и сверяют с независимой реализацией формулы. Работа шла в три параллельных потока Codex (AGENTS.md, CODEX_PROMPTS).
Стек
Next.js 15 · React 18 · TypeScript · OpenAI SDK · node:test · tsx · pnpm
Модели
gpt-4o-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-91d6f2bb-ezlyam.
README
README.mdQala Balance AI — «Аким на 5 часов»
Рабочий симулятор HackAlem AI / Astana Innovations: пять городских решений, бюджет 100 условных единиц, пять районов и детерминированный Astana Quality of Life Score. Данные синтетические: приложение объясняет последствия внутри заданной модели и не является прогнозом для реальной Астаны.
Быстрый запуск
Требуются Node.js 22+ и pnpm 11.25.0. Проверено на Windows с Node.js 24.19.0. Команды выполняются в папке с package.json.
node --version
npm install --global [email protected]
pnpm install --frozen-lockfile
pnpm dev
Откройте адрес из терминала (обычно http://localhost:3001). Остановка — Ctrl+C. Если node не найден, установите Node.js и заново откройте терминал. Если PowerShell блокирует pnpm.ps1, используйте pnpm.cmd вместо pnpm.
Production-запуск:
pnpm build
pnpm start
Другой порт: pnpm dev --port 3001 или pnpm start --port 3001. Сервер Next.js необходим для AI API; статический экспорт не подходит.
Ключ для работы расчёта не нужен. Без ключа приложение честно показывает «локальный анализ (без LLM)», а не имитирует вызов AI. Внешние шрифты, база данных и авторизация не требуются.
pnpm-workspace.yaml разрешает установочные скрипты esbuild и unrs-resolver. С разрешения владельца проекта добавлено исключение минимального возраста публикации строго для уже закреплённого Next.js 15.5.26 и связанных пакетов той же версии. Ограничения для остальных пакетов не отключены.
Проверка за одну минуту
pnpm test
pnpm typecheck
pnpm lint
pnpm check:example
pnpm build
33 автоматических теста проверяют данные, правила, расчёты и API, включая все 2002 сочетания пяти уникальных ID с детерминированным распределением районов и независимой проверкой формулы. Это не перебор всех возможных назначений районов. Отдельно проверены 120 перестановок контрольного набора. Для тестов не нужен реальный OpenAI-ключ: ответы сервиса имитируются.
pnpm check:example проверяет утверждениями и печатает полный расчёт:
| Решение | Район |
|---|---|
| M7 — школа и детсад | Нура |
| M8 — центр семейного здоровья | Нура |
| M10 — освещение и камеры | Нура |
| M12 — платформа обращений | Город; districtId отсутствует |
| M5 — чистое топливо | Сарыарка |
Ожидаемые значения:
- Стоимость 95, остаток 5.
- База 52.55768, отображение 52,56.
- Средняя оценка после мер 58.0776.
- Слабейший район Нура: 52.9625.
- Критических ячеек 2 → 0.
- Синергия M10 + M12: B1 +2 в Нуре, без лага.
- Итог 56.54307, отображение 56,54; изменение +3.98539 (на экране +3,99).
В JavaScript возможен двоичный хвост дроби; проверки используют допуск 1e-9. Округление применяется только при отображении.
Сценарий демонстрации
- Откройте страницу и нажмите «Загрузить пример из датасета». Покажите две социальные меры, бюджет 95 и Score 56,54.
- Покажите разложение Score, районные баллы, исчезновение критических значений и синергию. Раскройте таблицы всех показателей и вкладов мер с учётом лагов.
- В решении M10 смените Нуру на Есиль: стоимость останется той же, Score изменится. Верните пример кнопкой.
- Замените M5 на M4 и выберите Нуру: появится конфликт M4 + M7, итоговый Score исчезнет, AI-кнопка будет недоступна.
- Верните пример. Получите анализ и покажите источник: OpenAI либо явно обозначенный локальный режим.
- Сбросьте сценарий. Результат прошлого сценария и его объяснение исчезнут.
Для проверки перерасхода: M2 (город), M5 (Сарыарка), M7 (Нура), M12 (город), M14 (город) стоят 101. Результат не рассчитывается.
Точные правила датасета
Источник требований — предоставленные PDF «Датасет районов» и описание кейса HackAlem AI. Название «на 5 часов» не меняет горизонт модели: 8 кварталов.
- Ровно 5 уникальных мероприятий из M1–M14.
- Не более 2 мер одного направления; по одной из каждого направления не требуется.
- Бюджет ≤100 условных единиц. Неиспользованный остаток не даёт бонуса. Перевод в тенге документом не задан.
- Районные меры требуют один из пяти районов; городские не принимают districtId и действуют на все районы. Цена городской меры оплачивается один раз.
- M1 + M3 запрещены в любых районах. M4 + M7 и M5 + M13 запрещены только в одном районе.
- При нарушении любого правила Score сценария не вычисляется. В API возвращается только ошибка с причиной.
- Порядок решений не влияет на результат.
Все 50 исходных показателей, доли населения и 14 мер сверяются с данными PDF в тестах. Значения хранятся в src/data/. Названия, описания и качественные риски в каталоге служат пояснениями; они не добавляют новых численных эффектов к датасету.
Формула
Показатели T1/T2 (транспорт), E1/E2 (экология), S1/S2 (соцсфера), B1/B2 (безопасность), C1/C2 (сервисы) направлены одинаково: больше — лучше.
I'[район, показатель] = clip(I + Σ(эффект × (8 − лаг)/8) + синергии, 0, 100)
D = 0.10·T1 + 0.10·T2 + 0.09·E1 + 0.11·E2 + 0.11·S1
+ 0.11·S2 + 0.09·B1 + 0.09·B2 + 0.10·C1 + 0.10·C2
D_avg = Σ(доля населения района × D района)
N_crit = число ячеек «район × показатель» со значением строго <40
Score = 0.7 × D_avg + 0.3 × min(D) − N_crit
Ограничение 0–100 применяется один раз после всех эффектов и синергий. Сам итоговый Score не ограничивается. Порог ровно 40 не штрафуется. Обычные эффекты, в том числе отрицательный T1 у M11, учитываются с лагом; стоимость лагом не уменьшается.
Синергии фиксированные: M1+M2 → T1 +2 в районе M1; M10+M12 → B1 +2 в районе M10; M5+M6 → E2 +2 в районе M5. Лагом бонус не уменьшается. Все три пары доступны в интерфейсе.
Для городских дельт показателей интерфейс использует доли населения. Карточки направлений показывают среднее двух показателей с учётом населения, а не вклад направления в Score. Вклад меры показан как реализованная прибавка к показателям до ограничения; складывать его как вклад в Score нельзя из-за минимума района, синергий и штрафа.
AI и API
Настройка в PowerShell:
Copy-Item .env.example .env.local
notepad .env.local
Заполните OPENAI_API_KEY своим API-ключом, при необходимости OPENAI_MODEL, сохраните и перезапустите сервер. Не повторяйте Copy-Item поверх уже заполненного файла. Ключ остаётся на сервере; .env.local исключён из Git. Не добавляйте NEXT_PUBLIC_ к имени ключа. Модель по умолчанию — gpt-4o-mini; доступность зависит от вашего API-проекта.
POST /api/analyze принимает только выбор, а не клиентские расчёты:
{
"selection": [
{ "initiativeId": "M7", "districtId": "nura" },
{ "initiativeId": "M8", "districtId": "nura" },
{ "initiativeId": "M10", "districtId": "nura" },
{ "initiativeId": "M12" },
{ "initiativeId": "M5", "districtId": "saryarka" }
]
}
Сервер повторно валидирует выбор и рассчитывает его через simulate(). Статусы: 200 — объяснение; 400 — неверный ввод/конфликт; 422 — превышение бюджета. У невалидного набора нет Score или AI-объяснения.
OpenAI получает готовые показатели до/после, дельты, бюджет, Score, район каждой меры, эффекты с лагом и синергии. Через Responses API со Structured Outputs модель формирует только качественные сильные стороны и риски. Ответ проверяется по структуре, длине и отсутствию числовых символов. Итоговая числовая сводка, компромиссы и рекомендация всегда формируются детерминированно. Такая проверка не доказывает истинность каждой качественной фразы: AI-текст всё ещё требует критического чтения.
Рекомендация — перебор допустимых одиночных замен, включая перенос меры в другой район и смену направления. Каждая альтернатива повторно проходит валидацию. Это локальное улучшение, не гарантия глобально лучшего сценария.
Без ключа возвращается source: "fallback", fallbackReason: "missing_key". При тайм-ауте (20 секунд), отказе, ошибке сети или невалидном ответе — source: "fallback", fallbackReason: "unavailable". При успешном вызове — source: "openai". Изменение выбора сбрасывает старое объяснение и отменяет клиентский запрос; запоздавший ответ не отображается.
Реальный вызов OpenAI в этой проверке не подтверждён: ключ не предоставлен. Перед демонстрацией с LLM проверьте, что интерфейс показывает источник OpenAI. Тесты имитации подтверждают обработку контракта, но не доступность модели, ключа или сети.
Устройство проекта
src/data/ пять районов и каталог M1–M14
src/types/simulation.ts Decision[] и результаты расчёта
src/lib/scoring.ts районные баллы, веса, штраф, Score
src/lib/simulation.ts валидатор, лаги, синергии, рекомендации
src/lib/simulation.test.ts данные PDF, границы и независимая проверка
src/lib/fallbackAnalysis.ts объяснение из готовых результатов
src/app/page.tsx выбор пяти мер и результаты
src/app/api/analyze/route.ts серверный расчёт и OpenAI
src/app/api/analyze/route.test.ts проверки API и имитации провайдера
scripts/check-example.ts воспроизводимый контрольный пример
Стек: Next.js 15, React 18, TypeScript, OpenAI SDK, node:test + tsx. Формула используется совместно клиентом и сервером; AI её не дублирует.
Перед подачей
- Проверить свежую копию проекта командами выше; сохранить успешный вывод проверок.
- Уточнить роли участников ниже при необходимости.
- Проверить реальный OpenAI-вызов, если демонстрируется AI, а не только локальный режим.
- Уточнить по материалам организаторов формат подачи, дедлайн, доступность репозитория и необходимость видео/ссылки на сайт. Эти условия не следует придумывать по скриншоту.
- Проверить изменения в отдельной ветке и PR перед объединением; не переписывать историю.
Ограничения и развитие
Нет реальных смет, динамики населения, вероятностных событий и операционных расходов. Нет авторизации, сохранения сценариев между посещениями, межкомандного рейтинга и развёртывания в интернете. Для публичного использования AI endpoint потребует ограничения частоты/расходов и контроля доступа. Для реального управления нужны проверенные данные, калибровка и методологическая экспертиза. Следующие полезные расширения: сравнение сохранённых сценариев, карта районов, экспорт отчёта и анализ чувствительности.
Команда
- Участник 1: Danial Kalzhan
- Участник 2: Yerali Karkinbayev
- Участник 3: Aknur Galymzhankyzy
Проект разрабатывался с помощью Codex.
Авторы коммитов
История коммитов
- 2026-09-23 17:57Update participant name in README.mdGalymzhankyzy Aknur
- 2026-09-23 17:55Update localhost port in README instructionsGalymzhankyzy Aknur
- 2026-09-23 17:03Merge pull request #3 from BAITC-Hacks/feature/rebuildGalymzhankyzy Aknur
- 2026-09-23 16:59finish AI analysis and rebuild integrationaknur
- 2026-09-23 17:51Merge pull request #4 from BAITC-Hacks/fix/dataset-complianceYerali
- 2026-09-23 17:49fix: align city simulation with official dataset rulesASUS-Gaming
- 2026-09-23 16:12add interface for dataset simulationaknur
- 2026-09-23 17:07updateDanial Kalzhan
Все коммиты (19)
- 2026-09-23 16:01rebuild simulation from district datasetaknur
- 2026-09-23 15:27merge: reconcile AI analysis and documentation changesDanial
- 2026-09-23 15:23feat: add server AI analysis with deterministic fallbackDanial
- 2026-09-23 15:21feat: add server AI analysis fallback and docsASUS-Gaming
- 2026-09-23 15:15Merge pull request #2 from BAITC-Hacks/feature/full-solutionDanial Kalzhan
- 2026-09-23 15:02merge: add scenario recommendationsDanial
- 2026-09-23 15:00feat: suggest affordable and higher-scoring scenariosDanial
- 2026-09-23 14:46Merge pull request #1 from BAITC-Hacks/feature/analysis-docsDanial Kalzhan
- 2026-09-23 14:40docs: provide prompts for interface and analysis lanesDanial
- 2026-09-23 14:38feat: add deterministic city budget simulationDanial
- 2026-09-23 14:29chore: coordinate three Codex workstreamsDanial
Структура репозитория (27 файлов)
.env.example .gitignore AGENTS.md README.md docs/CODEX_PROMPTS.md docs/TEAM_WORKFLOW.md docs/VERIFICATION.md eslint.config.mjs next-env.d.ts next.config.mjs package.json pnpm-lock.yaml pnpm-workspace.yaml scripts/check-example.ts src/app/api/analyze/route.test.ts src/app/api/analyze/route.ts src/app/globals.css src/app/layout.tsx src/app/page.tsx src/data/districts.ts src/data/initiatives.ts src/lib/fallbackAnalysis.ts src/lib/scoring.ts src/lib/simulation.test.ts src/lib/simulation.ts src/types/simulation.ts tsconfig.json
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.