AAYMA
RU
Войти

Qala Balance AI

Симулятор «Аким на 5 часов» на Next.js: выбор 5 мер по районам, валидация правил датасета, детерминированный Score, рекомендации одиночных замен и…

Рабочий MVP
Команда «EzLyam» 3💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: 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.md

Qala 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. Округление применяется только при отображении.

Сценарий демонстрации

  1. Откройте страницу и нажмите «Загрузить пример из датасета». Покажите две социальные меры, бюджет 95 и Score 56,54.
  2. Покажите разложение Score, районные баллы, исчезновение критических значений и синергию. Раскройте таблицы всех показателей и вкладов мер с учётом лагов.
  3. В решении M10 смените Нуру на Есиль: стоимость останется той же, Score изменится. Верните пример кнопкой.
  4. Замените M5 на M4 и выберите Нуру: появится конфликт M4 + M7, итоговый Score исчезнет, AI-кнопка будет недоступна.
  5. Верните пример. Получите анализ и покажите источник: OpenAI либо явно обозначенный локальный режим.
  6. Сбросьте сценарий. Результат прошлого сценария и его объяснение исчезнут.

Для проверки перерасхода: 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.

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

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

Danial Kalzhan 10Galymzhankyzy Aknur 6Yerali 3

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

  • 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
Ветки (7)

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

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

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