AAYMA
RU
Войти

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

Next.js-симулятор: пользователь выбирает 5 мер из 14 (5 направлений, 5 районов) в пределах бюджета 100 и получает детерминированный Astana Quality…

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

О проекте

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

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

Суть

Next.js-симулятор: пользователь выбирает 5 мер из 14 (5 направлений, 5 районов) в пределах бюджета 100 и получает детерминированный Astana Quality of Life Score. AI-агент объясняет результат и предлагает замены, подтверждённые расчётом.

Как устроено

Входные данные — JSON с мерами, районами и событиями. Детерминированный движок проверяет бюджет и несовместимости, применяет лаги, синергии и эффекты мер и считает средний и минимальный районный индекс, критические показатели и Score. suggestSwaps перебирает замены одной меры с выбором района, а bestOverall полным перебором 694 395 допустимых наборов находит оптимум. AI-агент на OpenAI с function calling вызывает 6 инструментов (score_set, validate_set, suggest_swaps, get_contributions, get_district_profile, get_active_event) и собирает структурированный отчёт о сильных сторонах, рисках и компромиссах. Поддерживаются 4 городских события, сохранение сценариев и экспорт в Markdown и PPTX на 8 слайдов.

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

  • function calling
  • exhaustive search optimization
  • structured output schema
  • deterministic scoring engine
  • PPTX generation
  • smoke reference checks

Метрики

Базовый Score 52,56; пример из ТЗ 56,54; оптимум 57,24 найден полным перебором 694 395 наборов

Особенности

Числа считает только детерминированный движок, а рекомендации LLM проверяются тем же движком и полным перебором. Числовая часть работает без API-ключа. Реализованы все 5 опциональных пунктов, включая события и генерацию PPTX. В README есть таблица соответствия каждому требованию и критерию жюри со ссылками на файлы.

Стек

Next.js · React · TypeScript · Tailwind CSS · Node.js

Модели

OpenAI


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

README

README.md

«Аким на 5 часов» — AI-симулятор управления городом

HackAlem AI 2026 · Трек: «Спец-трек Astana Innovations» · Кейс: «Аким на 5 часов» · Один участник: vyunner

Симулятор помогает городскому управленцу или аналитику выбрать программу развития Астаны при ограниченном бюджете: детерминированный движок рассчитывает последствия решений, а AI-агент объясняет результат и предлагает улучшения, подтверждённые расчётом.

Быстрый старт

git clone https://github.com/BAITC-Hacks/hack-3d57eba1-forbes30under30.git
cd hack-3d57eba1-forbes30under30
npm install
cp .env.example .env
# впишите OPENAI_API_KEY в .env; без ключа работают расчёт, рекомендации по расчёту, оптимум, события и экспорт, не работает только текстовый разбор агента
npm run smoke
npm run dev

Откройте http://localhost:3000 → «Загрузить пример из ТЗ» → «Рассчитать» → Score 56,54

Проблема

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

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

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

  • Каталог 14 мер по 5 направлениям. Пользователь выбирает ровно 5 разных мер при бюджете 100, не более двух мер одного направления. Для районных мер нужно указать район; городские действуют на весь город.
  • Живая валидация бюджета, районов, повторов и несовместимостей. Недопустимый набор нельзя отправить на расчёт; сервер повторно проверяет вход.
  • Расчёт Astana Quality of Life Score, дельты к базе, среднего и минимального районного индекса, критических показателей, синергий и вклада каждой меры.
  • Числа появляются после отдельного запроса /api/calc. AI-разбор загружается через /api/analyze, не задерживая карточки, диаграммы и таблицу. Повторные расчёты заблокированы на время анализа.
  • AI-агент с function calling: объяснение сильных сторон, рисков и компромиссов, проверенные рекомендации, сворачиваемый журнал вызовов инструментов.
  • suggestSwaps() возвращает до пяти улучшений заменой одной меры на другую с перебором районов. Блок «Рекомендации по расчёту» показывает их независимо от AI; кнопка «Применить» доступна после завершения текущего анализа, меняет набор и пересчитывает его.
  • bestOverall() выполняет полный перебор 694 395 допустимых наборов для текущего каталога и сохраняет топ-10 в локальный кэш. Без события лучший Score — 57.24. Оптимальный набор можно подставить кнопкой.
  • Четыре неожиданных события: авария на теплосетях в Алматы, смог в Сарыарке, приток жителей в Нуру и паводок в Есиле. Можно выбрать событие или получить случайное; расчёт, замены, оптимум и агент учитывают его последствия.
  • Таблица десяти показателей пяти районов «до → после» с закреплёнными столбцами района и индекса D; SVG-диаграммы, переключатель района и полоски вкладов мер. Критические значения подсвечены.
  • Сохранение сценариев с названием, набором мер, событием, Score и стоимостью. Общая таблица сортируется по Score; сохранённый сценарий можно загрузить для повторного просмотра.
  • Скачивание краткого отчёта в Markdown: меры, стоимость, Score, индексы районов, критические значения, событие, summary и рекомендации агента, если они получены.
  • Генерация краткой презентации PPTX: 8 слайдов, формат 16:9. Выбранные меры, Score, показатели районов, разбор агента и рассчитанные замены формируются из результата сценария. Без AI доступны числовые результаты и рекомендации по расчёту.

Соответствие требованиям кейса

Шесть обязательных требований перечислены в ТЗ, раздел 5. Ниже также указаны все пять опциональных пунктов кейса; все пять реализованы.

#Требование из ТЗ кейсаРеализованоГде смотреть
1Единый виртуальный бюджет 100✅data/measures.json, lib/engine/validate.ts
2Решения по пяти направлениям: ровно 5 мер, не более 2 одного направления✅components/MeasurePicker.tsx, lib/engine/validate.ts
3Автоматический контроль превышения бюджета✅ Индикатор, ошибки, блокировка кнопки и серверная проверкаcomponents/MeasurePicker.tsx, app/api/calc/route.ts
4AI-анализ принятых решений✅ При настроенном ключе OpenAIlib/agent/index.ts, lib/agent/tools/, app/api/analyze/route.ts
5Расчёт Astana Quality of Life Score✅ Детерминированный движок и эталонные проверкиlib/engine/score.ts, scripts/smoke.ts
6Объяснение сильных сторон, рисков и последствий✅ Структурированный AI-отчётlib/agent/prompts.ts, lib/agent/schemas.ts, components/AgentReport.tsx
О1сравнение результатов нескольких команд✅ Минимальная реализация: рейтинг по Score и повторная загрузкаcomponents/Scenarios.tsx, app/api/scenarios/route.ts, lib/scenarios/store.ts
О2визуализация изменений показателей районов✅ Диаграммы, таблица районов и вклады мерcomponents/DistrictChart.tsx, components/DistrictTable.tsx, components/Contributions.tsx
О3AI-рекомендации по улучшению выбранного сценария✅ Агент объясняет проверенные замены; рекомендации по расчёту доступны и без AIlib/engine/optimize.ts, lib/agent/tools/suggest_swaps.ts, components/AgentReport.tsx, app/page.tsx
О4моделирование неожиданных городских событий✅ Четыре события с пересчётом и AI-анализомdata/events.json, lib/engine/events.ts, components/EventPicker.tsx
О5автоматическая генерация краткой презентации решения команды✅ Краткая презентация решения в формате PPTX генерируется из результата расчёта, рекомендаций и разбора агентаlib/presentation.ts, components/ReportDownload.tsx

Соответствие критериям оценки

Названия критериев и баллы приведены по формулировкам организаторов; таблица показывает реализованные возможности, а не присвоенные проекту оценки.

Критерий из ТЗБаллыЧто сделаноГде проверить
Соответствие задаче и работоспособность25Реализованы все 6 must have из таблицы выше и сквозной сценарий выбора, расчёта и AI-разбора; npm run smoke воспроизводит эталоны. Установка и запуск из чистого клона проверены на Node.js 20.20.2 без API-ключа.Быстрый старт, scripts/smoke.ts: 52.56 / 56.54 / 55.67 / 57.24; в UI «Загрузить пример из ТЗ» → «Рассчитать». AI-шаг требует доступного OpenAI.
Техническая реализация25Детерминированный движок с проверками; агент с function calling и шестью инструментами; журнал шагов; полный перебор оптимума; отдельные запросы расчёта и анализа.lib/engine/score.ts, lib/engine/optimize.ts, lib/agent/tools/index.ts, lib/agent/index.ts, app/api/calc/route.ts, app/api/analyze/route.ts; npx tsx scripts/agent-checks.ts.
README и воспроизводимость25Быстрый старт, формулы с расчётом базы, сценарий проверки с ожидаемыми числами, работа числовой части без ключа и мягкий пропуск недоступного AI-шага.Быстрый старт, Как считается Score, Как проверить; npm run smoke с пустым OPENAI_API_KEY.
Ценность и применимость решения15Сопоставление бюджетных сценариев, проверка слабого района и объяснение компромиссов на синтетической модели кейса.Ценность и применимость; в UI Score, самый слабый район, таблица показателей и рассчитанные рекомендации.
Потенциал развития и оригинальность подхода10Разделены числовой расчёт и объяснение LLM, реализованы полный перебор и события для проверки устойчивости плана; направления дальнейшей работы указаны отдельно.Потенциал развития и оригинальность, lib/engine/optimize.ts, lib/engine/events.ts.

Критерии проверки из ТЗ — формулировки из раздела 5:

  • «одинаковый старт» — общий бюджет 100 и исходные данные заданы в data/measures.json и data/districts.json; npm run smoke проверяет базу 52.56 и неизменность датасетов, сравнивать планы следует при одинаковом событии.
  • «нельзя превысить бюджет» — lib/engine/validate.ts отклоняет перерасход, smoke проверяет бюджет 113 и допустимость ровно 100; в UI недопустимый набор блокирует «Рассчитать».
  • «решения влияют на модель» — lib/engine/score.ts применяет эффекты мер; smoke проверяет лаги, отрицательный эффект M11, синергии и вклад каждой меры, а таблица районов показывает «до → после».
  • «понятное объяснение компромиссов» — lib/agent/prompts.ts задаёт содержание разбора, lib/agent/report-validation.ts проверяет числа и рекомендации; в UI раздел «Компромиссы» и журнал «Как агент пришёл к выводу» позволяют сопоставить текст с расчётом, качество объяснения оценивает эксперт.
  • «другой набор → другой Score» — smoke воспроизводит разные эталонные наборы с результатами 56.54, 55.67 и 57.24; в UI замена M5 на M3 в Нуре даёт 57.21 вместо 56.54. Это проверка влияния решений, а не гарантия уникального Score у каждого возможного набора.

Как работает

  1. Пользователь выбирает пять мер и районы либо нажимает «Загрузить пример из ТЗ». Панель сразу показывает использованный бюджет, остаток и нарушения правил.
  2. «Рассчитать» отправляет { decisions, eventId? } в /api/calc. Сервер валидирует набор, рассчитывает показатели, находит одиночные замены и лучший набор для тех же условий.
  3. UI сразу показывает Score, стоимость, вклады, диаграммы и таблицу районов. Затем отправляет тот же набор и событие в /api/analyze; состояние ожидания остаётся только в блоке «Разбор агента».
  4. Агент последовательно вызывает инструменты движка и возвращает проверенный JSON-отчёт. Пользователь видит объяснение и журнал шагов с краткими результатами.
  5. «Применить» использует конкретную рассчитанную замену. «Показать оптимальный набор» загружает результат полного перебора. Оба действия повторяют расчёт и анализ; смена события работает так же.
  6. Пользователь сохраняет сценарий, сравнивает его Score и стоимость с другими сохранёнными вариантами или скачивает Markdown-отчёт и PPTX-презентацию. Загрузка сохранённого сценария восстанавливает решения и событие, затем заново выполняет анализ.

Как считается Score

Правила заданы в data/measures.json, веса показателей и доли населения — в data/districts.json. Реализация: lib/engine/score.ts. Все показатели имеют шкалу 0–100; больше означает лучше. Горизонт расчёта — 8 кварталов.

Для района d, показателя k и меры m:

realized_m = (8 − lag_m) / 8
clip(x) = min(100, max(0, x))

I_start[d,k] = I[d,k]                              без события
I_start[d,k] = clip(I[d,k] + eventDelta[d,k])       с событием

I_after[d,k] = clip(
  I_start[d,k]
  + Σ (effect[m,k] × realized_m)                  по мерам, действующим в районе d
  + Σ synergyBonus[d,k]                          по сработавшим синергиям
)

D_d    = Σ weight_k × I_after[d,k]
D_avg  = Σ population_d × D_d
N_crit = число пар (район, показатель), у которых I_after[d,k] < 40

Score = 0.7 × D_avg + 0.3 × min(D_d) − 1.0 × N_crit

Районная мера меняет только выбранный район, городская — все пять. Например, лаг 4 даёт realized = 0.5: за горизонт учитывается половина эффекта меры.

Синергия срабатывает при выборе обеих мер пары. Фиксированный бонус не масштабируется лагом и применяется в районе первой меры пары; для городской первой меры применялся бы ко всем районам. В текущем каталоге:

ПараБонусГде действует
M1 + M2T1 +2Район M1
M10 + M12B1 +2Район M10
M5 + M6E2 +2Район M5

Веса десяти показателей:

ПоказательНазваниеВес
T1Разгрузка дорог0.10
T2Доступность общественного транспорта0.10
E1Озеленение0.09
E2Качество воздуха0.11
S1Школы и детсады0.11
S2Поликлиники и первичная медпомощь0.11
B1Безопасность улиц0.09
B2Безопасность дорожного движения0.09
C1Надёжность ЖКХ0.10
C2Скорость решения обращений жителей0.10

Пример базы без мер и события:

РайонДоля населенияD до мер
Есиль0.2762.99
Алматы0.2457.06
Сарыарка0.2054.65
Байконур0.1356.63
Нура0.1649.18
D_avg = 0.27×62.99 + 0.24×57.06 + 0.20×54.65 + 0.13×56.63 + 0.16×49.18
      = 56.8624
min(D_d) = 49.18                                  Нура
N_crit = 2                                       Нура: S1 = 38, S2 = 35
Score = 0.7×56.8624 + 0.3×49.18 − 2
      = 52.55768 → 52.56

Ровно 40 не является критическим значением. Дельта сценария равна Score набора − базовый Score. При событии база тоже пересчитывается с этим событием; отдельно показывается Score того же набора до и после события.

Вклад меры — Score полного набора − Score набора без этой меры, с тем же событием. Вклады учитывают исчезающие синергии и штрафы, поэтому их сумма не обязана совпадать с общей дельтой. Округление до двух знаков выполняется только для вывода, а не между шагами расчёта.

Валидатор также запрещает M1 + M3 в любом размещении, M4 + M7 и M5 + M13 в одном районе. Выбирать по одной мере каждого направления не обязательно: действует ограничение не более двух, а не требование охватить все направления.

Роль AI

Все числовые результаты вычисляет детерминированный движок lib/engine/. Агент не рассчитывает Score, эффекты или прогнозы замен самостоятельно: он получает числа только через инструменты. Полный перебор оптимума также выполняет код, а не LLM.

Фактически зарегистрированные инструменты в lib/agent/tools/index.ts:

ToolНазначение
validate_setПроверить набор и вернуть нарушения правил.
score_setРассчитать допустимый набор: Score, базу, показатели, штрафы, лаги и синергии. Для альтернативного набора также вернуть сравнение с исходным.
get_contributionsПолучить вклад каждой выбранной меры через расчёт с мерой и без неё.
suggest_swapsПолучить до пяти допустимых улучшений заменой одной меры, их стоимость, новый Score и дельту.
get_district_profileПолучить профиль района, долю населения и показатели до/после.
get_active_eventПолучить активное событие, ущерб, Score до/после события и реакцию выбранных мер.

Цикл в lib/agent/index.ts использует OpenAI Chat Completions с function calling. Первые вызовы обязательны: score_set → get_contributions → suggest_swaps; при активном событии затем обязателен get_active_event. После этого модель выбирает дополнительные инструменты по необходимости. Всего разрешено не более 8 вызовов, последовательно; общий таймаут анализа — 60 секунд.

Финальный JSON содержит summary, strengths, risks, tradeoffs и recommendations[{ change, expectedDelta, why }]. Ответ проверяется zod; числовые утверждения сопоставляются с результатами tools, а каждая рекомендация — с точным текстом и дельтой рассчитанной замены. При ошибке даётся одна попытка исправления отчёта, затем возвращается понятное сообщение об ошибке AI.

steps — журнал действий { name, detail }: вызванный инструмент, аргументы, краткий результат, проверка отчёта или ошибка. В UI он доступен в блоке «Как агент пришёл к выводу». Это журнал выполненных вызовов, а не скрытые внутренние рассуждения модели.

Без OPENAI_API_KEY или при недоступности OpenAI работают расчёт, рекомендации по расчёту, оптимум, события и экспорт, не работает только текстовый разбор агента. Визуализация и сохранение также доступны; интерфейс показывает причину отсутствия AI-разбора и «Повторить анализ». Повтор AI не запускает заново /api/calc.

Архитектура

Браузер: app/page.tsx + components/
  │
  ├─ POST /api/calc { decisions, eventId? }
  │    └─ validate → score → suggestSwaps → bestOverall
  │         ├─ data/districts.json, measures.json, events.json
  │         └─ data/best.json[.<eventId>] — локальный кэш оптимума
  │    ← calc, cost, suggestions, bestKnownScore, optimalDecisions, данные события
  │
  ├─ POST /api/analyze { те же decisions, eventId? }
  │    └─ lib/agent → OpenAI API → tool calls → lib/engine
  │    ← report, steps, aiError?                  числовой результат уже на экране
  │
  ├─ GET /api/scenarios → список по убыванию Score
  ├─ POST /api/scenarios { name, decisions, eventId? }
  │    └─ серверная проверка и расчёт → data/scenarios.json
  │
  ├─ Markdown-отчёт → Blob → скачивание в браузере
  └─ PPTX-презентация → PptxGenJS → скачивание в браузере

Компоненты:

  • UI хранит выбранный набор в useState, показывает расчёт и отдельно ожидает AI.
  • Движок валидирует решения и детерминированно считает модель. Оптимизатор использует предвычисленные эффекты, раннее отсечение по бюджету и ограничениям, числовые буферы и файловый кэш.
  • Агент вызывает инструменты, объясняет результаты и возвращает проверенный отчёт.
  • Хранилище сценариев работает с локальным JSON; Score, стоимость и время сохранения определяются сервером, а не принимаются на доверии от клиента.
app/
  page.tsx                    выбор, два запроса, результаты и сценарии
  api/calc/route.ts           расчёт без LLM
  api/analyze/route.ts        только AI-разбор и журнал шагов
  api/scenarios/route.ts      сохранение и чтение сценариев
  layout.tsx, globals.css     общий макет и стили
components/
  MeasurePicker.tsx           каталог, бюджет, живая валидация
  ScoreCard.tsx               Score, дельта, слабый район, оптимум
  Contributions.tsx          вклад каждой меры
  DistrictOverview.tsx        исходные профили районов
  DistrictChart.tsx           SVG-диаграммы районов и показателей
  DistrictTable.tsx           таблица до/после
  EventPicker.tsx             выбор и снятие события
  AgentReport.tsx             разбор, применение рекомендаций, журнал
  Scenarios.tsx               сохранение, рейтинг, загрузка
  ReportDownload.tsx          скачивание Markdown и PPTX
lib/
  engine/                    данные, validate, score, optimize, events
  agent/                     цикл агента, промпты, схемы, проверка отчёта
    tools/                   отдельный файл на каждый инструмент
  scenarios/                 схема и файловое хранилище
  openai.ts                  клиент, модель и ключ из окружения
  result-schema.ts           схемы ответов API
  suggestions.ts             описание и применение замены
  format.ts                  отображение чисел
  markdown-report.ts         генерация текста отчёта
  presentation.ts            генерация восьми слайдов PPTX из результата сценария
data/
  districts.json             5 районов × 10 показателей, веса и население
  measures.json              14 мер, правила, синергии и эталоны
  events.json                4 дополнительных события
  sample/scenarios.json      воспроизводимые наборы для смоук-теста
  scenarios.json             создаётся при первом сохранении, не в Git
  best.json[.<eventId>]       создаётся оптимизатором, не в Git
scripts/
  smoke.ts                   основной прогон и реальный AI-вызов при наличии ключа
  *-checks.ts                дополнительные проверки API, агента, UI и движка
AGENTS.md, TZ.md, PROMPTS.md   правила, задание и материалы разработки
.env.example, .nvmrc          пример окружения и версия Node
package.json, package-lock.json

Технологии

  • Node.js 20 LTS, npm, TypeScript strict. .nvmrc указывает Node 20; для смоука с загрузкой .env нужна версия 20.12 или новее в ветке 20.
  • Next.js App Router и React: UI и серверные API в одном приложении, без src/.
  • Tailwind CSS, обычный CSS и inline SVG; дополнительных UI- и графических библиотек нет.
  • Официальный OpenAI SDK, Chat Completions с function calling и структурированным JSON-ответом; zod для валидации.
  • Модель по умолчанию — gpt-6-sol, как в lib/openai.ts и .env.example. Её можно переопределить через OPENAI_MODEL; выбранная модель должна быть доступна API-ключу и поддерживать используемые tools и JSON schema.
  • Данные и локальное хранилище — JSON. Базы данных, ORM и Docker нет.

Основные open-source компоненты и лицензии указаны по метаданным установленных пакетов; версии закреплены в package.json и package-lock.json:

КомпонентВерсияЛицензияИспользование
Next.js15.5.26MITСтраницы и API
React / React DOM19.2.8MITИнтерфейс
TypeScript5.9.3Apache-2.0Типизация и проверка кода
Tailwind CSS3.4.19MITСтили
OpenAI SDK (openai)6.49.0Apache-2.0Вызовы модели и tools
zod4.6.5MITСхемы входов и ответов
pptxgenjs3.12.0MITГенерация PPTX-презентации сценария
tsx4.23.15MITЗапуск TypeScript-проверок
PostCSS / Autoprefixer8.5.28 / 10.6.1MITОбработка CSS

Веса AI-модели не входят в репозиторий: модель вызывается через внешний OpenAI API.

Требования

  • Node.js 20 LTS версии ≥20.12 и npm; Git для клонирования репозитория.
  • Проверено также на Node.js 24: сборка, смоук-тест и запуск проходят без изменений.
  • Для AI-разбора — доступ в интернет, ключ OpenAI API, доступ к выбранной модели и доступная квота.
  • Для сохранения сценариев и кэша оптимизатора — доступ на запись в каталог data/ и сохраняемая файловая система.
  • Для запуска приложения и проверки числовой модели ключ OpenAI не обязателен. Docker не требуется.

Установка

git clone https://github.com/BAITC-Hacks/hack-3d57eba1-forbes30under30.git
cd hack-3d57eba1-forbes30under30

# Если используете nvm:
nvm install
nvm use

npm install
cp .env.example .env

Откройте локальный .env и заполните OPENAI_API_KEY, если нужен AI-разбор. OPENAI_MODEL можно оставить со значением из примера. Для проверки без AI оставьте ключ пустым. Не добавляйте .env в Git: он исключён через .gitignore.

Если nvm не установлен, установите подходящую версию Node.js другим способом и пропустите две команды nvm. Все остальные команды выполняются из корня проекта.

Переменные окружения

ПеременнаяОбязательнаОписаниеКак получить
OPENAI_API_KEYТолько для AIСерверный ключ; никогда не передаётся браузеруВ своём аккаунте OpenAI API
OPENAI_MODELНетМодель для анализа; по умолчанию gpt-6-solУказать идентификатор модели, доступной вашему API-ключу

Next.js загружает .env при запуске. Скрипт smoke.ts также загружает этот файл самостоятельно; вручную экспортировать ключ в терминал не требуется. После изменения окружения перезапустите сервер.

Только при недоступности модели выполняется переход по цепочке без повторов: OPENAI_MODEL (или gpt-6-sol, если не задана) → gpt-6-sol → gpt-5.6-terra → gpt-4.1; рабочая модель запоминается процессом, а ошибки сети, квоты и таймауты не переключают модель.

Запуск

Режим разработки:

npm run dev

Откройте http://localhost:3000.

Продакшн-режим:

npm run build && npm start

Проверки:

npm run typecheck
npm run build
npm run smoke

npm run smoke запускает tsx scripts/smoke.ts на данных из data/sample/scenarios.json. При расхождении он выводит ожидаемое и фактическое значение и завершается с кодом 1; при успехе — с кодом 0.

Как проверить (сценарий для экспертов)

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

Запустите npm run smoke. Ключевые строки ожидаемого вывода:

База: Score = 52.56
Пример из ТЗ: Score = 56.54, стоимость = 95
Дешёвый набор: Score = 55.67, стоимость = 61
Лучший известный набор: Score = 57.24, стоимость = 98
Событие «Авария на теплосетях в Алматы»: база = 51.36, пример = 55.34 (без события: 52.56 и 56.54)
...
ВСЕ ПРОВЕРКИ ПРОЙДЕНЫ

В конце выводится сводная таблица ожидаемых и фактических Score. Также проверяются нарушения правил, синергии, вклады, положительные замены и полный поиск оптимума; выводится время bestOverall() с указанием, прочитан ли кэш.

При заполненном ключе смоук делает один реальный запуск агента на примере из ТЗ, проверяет схему отчёта и обязательные tools. Это использует квоту OpenAI. При проблемах с ключом, доступом к моделям, сетью, таймаутом или квотой выводится AI-проверка пропущена: причина и итог ЧИСЛОВЫЕ ПРОВЕРКИ ПРОЙДЕНЫ (AI-проверка пропущена); при успешном AI выводится ВСЕ ПРОВЕРКИ ПРОЙДЕНЫ, а неверный отчёт остаётся провалом проверки.

Прогон в интерфейсе

Числа ниже относятся к сценарию без активного события. Интерфейс использует десятичную запятую: 56,54 соответствует 56.54 в смоуке.

  1. Нажмите «Загрузить пример из ТЗ»: M7@nura, M8@nura, M10@nura, M12, M5@saryarka. Панель покажет 5 мер, стоимость 95 и остаток 5.
  2. Нажмите «Рассчитать». Score 56.54, вклады, диаграммы и таблица появятся до завершения AI. В блоке агента будет «Агент анализирует сценарий…»; повторный запуск заблокирован.
  3. В блоке «Рекомендации по расчёту» нажмите «Применить» для замены M5 в Сарыарке → M3 в Нуре. Ожидаемый Score — 57.21, стоимость — 100, подтверждённый прирост относительно предыдущего набора — +0.66.
  4. Нажмите «Показать оптимальный набор»: M2, M3@nura, M8@nura, M9@nura, M14. Ожидаемый Score — 57.24, стоимость — 98. Появятся бейдж «Оптимальный набор» и сообщение о лучшем результате среди 694 395 допустимых вариантов.
  5. Проверьте бюджет: нажмите «Сбросить», выберите M2, M3@nura, M5@esil, M8@nura, M14. Стоимость 113, остаток −13; индикатор красный, показана причина, «Рассчитать» недоступна. Также можно проверить четыре решения, повтор направления сверх лимита или районную меру без района.
  6. Для события снова загрузите пример из ТЗ, затем выберите «Авария на теплосетях в Алматы». Автоматический пересчёт покажет 56.54 → 55.34, база станет 51.36. Алматы будет подсвечен, его C1 после события — 38.00; AI объяснит реакцию набора. «Убрать событие» вернёт результат 56.54.
  7. Введите название и нажмите «Сохранить сценарий». Проверьте запись в таблице «Сценарии», её Score и стоимость, затем «Загрузить». Нажмите «Скачать отчёт (Markdown)» и проверьте наличие мер, таблицы D, события и полученного разбора.
  8. Нажмите «Скачать презентацию (PPTX)», откройте файл в PowerPoint или Keynote и проверьте восемь слайдов: выбранные меры, Score, показатели районов, разбор и рассчитанные замены. При отсутствии AI числовые результаты и рекомендации по расчёту остаются в презентации.

Агент может выбрать для объяснения другую подтверждённую замену из топ-5 — текст ответа не детерминирован. Для точного шага 56.54 → 57.21 примените замену M5 → M3@nura в блоке «Рекомендации по расчёту»: она доступна независимо от текста отчёта и наличия AI. Кнопка оптимального набора также работает без AI.

Дополнительные проверки

npx tsx scripts/result-checks.ts
npx tsx scripts/agent-checks.ts
npx tsx scripts/component-checks.ts
npx tsx scripts/scenario-checks.ts
npx tsx scripts/event-checks.ts
npx tsx scripts/event-integration-checks.ts
npx tsx scripts/optimize-checks.ts

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

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

  • data/districts.json и data/measures.json — синтетический датасет организаторов, представленный в JSON. Значения совпадают с исходными файлами, добавленными в историю репозитория; при переносе в data/ значения не менялись.
  • Датасет содержит пять районов Астаны, десять показателей на район, доли населения, веса, четырнадцать мер, лаги, стоимости, синергии, несовместимости и эталоны. Название «Алматы» здесь обозначает район Астаны в модели.
  • data/sample/scenarios.json — три фиксированных набора для воспроизводимых проверок.
  • data/events.json — четыре дополнительных сценарных события. Они изменяют копии исходных показателей в памяти, не перезаписывая датасет.
  • OpenAI API — единственный внешний сервис приложения. Сетевые вызовы выполняет серверный агент. Реальные городские API, персональные данные, внешняя БД и аналитические сервисы не подключены.
  • data/scenarios.json создаётся локально при первом сохранении, исключён из Git. Если файла ещё нет, API возвращает пустой список. Сохраняются название, решения, Score, стоимость, время и eventId, если событие активно; AI-текст при загрузке генерируется заново.
  • data/best.json и data/best.json.<eventId> — локальные кэши оптимума для обычного режима и событий. Они также исключены из Git; результаты проверяются на соответствие текущим данным.

Ценность и применимость

Предполагаемые пользователи — аппарат акима, профильные управления, депутаты на бюджетных слушаниях и аналитики, сопоставляющие варианты распределения бюджета. Симулятор автоматизирует ручное сравнение программ по пяти направлениям с учётом стоимости, лагов и заданных взаимных эффектов. Индекс самого слабого района, критические показатели и таблица «до → после» помогают проверить, не оставлен ли район без необходимого внимания. Пользователь получает прозрачный расчёт и, при доступном AI, объяснение компромиссов и рекомендации с числовым эффектом, который можно проверить повторным расчётом. Подход переносится на реальные данные заменой data/districts.json и data/measures.json на показатели районов и каталог программ со стоимостями в том же формате. Для другого числа районов, показателей или иной формулы потребуется адаптация схем и проверок; готовая интеграция с реальными данными не заявляется.

Потенциал развития и оригинальность

  • Реализовано: расчёт отделён от LLM. Числа формирует движок, агент получает их через инструменты и объясняет; проверка отчёта сопоставляет числовые утверждения с результатами tools, а рекомендации — с рассчитанными заменами.
  • Реализовано: полный перебор вместо эвристического поиска оптимума. bestOverall() перебирает все допустимые наборы текущей модели, а suggestSwaps() отдельно проверяет допустимые одиночные замены; оптимум и прирост каждой рекомендации можно воспроизвести, но это доказательство внутри заданной модели, а не реальной эффективности программы.
  • Реализовано: события как стресс-тест плана. Четыре события в data/events.json меняют исходные показатели; движок, оптимизатор и агент учитывают событие, позволяя проверить устойчивость выбранного набора.
  • Возможное развитие: реальные данные акимата и другие города. Подготовить сопоставимые районные показатели, стоимости и обоснованные эффекты программ; адаптировать схему и проверки для большего числа направлений, районов и мер и оценить время полного перебора на новом каталоге.
  • Возможное развитие: многолетний горизонт с бюджетами по годам. Добавить расходы и результаты по периодам, ограничения годового бюджета и проверку переноса эффектов между годами; текущая версия рассчитывает один итог за фиксированный горизонт.
  • Возможное развитие: участие граждан, публичные слушания и сравнение планов ведомств. Добавить сбор предложений и приоритетов, согласование исходных допущений и отдельное попарное сравнение планов; сейчас доступны только локально сохранённые сценарии, рейтинг по Score и повторная загрузка.

Ограничения

  • Модель фиксирована рамками кейса: пять районов, десять показателей, четырнадцать мер, бюджет 100 и горизонт восемь кварталов. Линейные эффекты, заданные лаги и синергии не заменяют транспортное, экологическое или экономическое моделирование реального города. Стоимость выражена в условных единицах.
  • Оптимум 57.24 относится к этому датасету и отсутствию события. События меняют базу и оптимум; число допустимых наборов и результат не следует переносить на другой каталог без нового расчёта.
  • Одиночная замена может не давать улучшения, хотя глобально существует лучший набор. suggestSwaps() меняет меру на другую; перенос той же меры в другой район отдельно не предлагается. Полный поиск рассматривает назначения районов.
  • AI может неточно объяснить причинность, приоритеты или компромиссы. Проверка наличия числа в результатах tools не доказывает правильность каждого предложения. Числа на карточках и рассчитанные замены остаются результатом движка; текст стоит сопоставлять с таблицей и журналом.
  • Нет гарантии одинакового текста или одинакового выбора рекомендаций при повторном AI-анализе. Нужны доступная модель, сеть и квота; при ошибке или таймауте числовая часть продолжает работать.
  • Сравнение сценариев — общий рейтинг по Score и стоимости с повторной загрузкой. Отдельного экрана попарного сравнения и инструмента compare_sets нет; ограниченное сравнение альтернативы доступно через score_set.
  • Сценарии с разными событиями имеют разные исходные условия, хотя показаны в общей таблице. Для содержательного сравнения выбирайте одинаковые условия.
  • Нет авторизации, ролей, БД, распределённого хранилища, админки и отдельного мобильного приложения. Файловое сохранение рассчитано на один серверный процесс; для устойчивой работы нужна постоянная файловая система. Публичный сервис с несколькими экземплярами потребовал бы другого хранилища и контроля доступа.
  • Подключение реальных данных, резервный AI-провайдер и экспорт в PDF не реализованы. Публичный деплой не выполнялся; проект запускается локально по инструкции из раздела «Быстрый старт».

Ход разработки

История основных этапов разработки в окне 13:00–18:00 до коммита 1bebed1 включительно, от ранних изменений к поздним; время взято из git log --format="%h %ad %s" --date=format:%H:%M. Коммиты участника относятся к 23.09.2026, часовой пояс UTC+05:00; начальный коммит платформы от 18.09.2026 указан отдельно со временем UTC.

ВремяКоммитЧто сделано
18.09 · 17:22 UTC9700fbcПлатформа создала начальный репозиторий.
13:15d13c09dДобавлены требования хакатона и исходные датасеты.
13:22e58acb0Созданы каркас Next.js и детерминированный движок расчёта.
13:343cffa7fДобавлены выбор решений, бюджет и API расчёта.
13:59d3cc0a7Добавлен AI-анализ сценария через инструменты движка.
14:099dc6713Добавлены проверенные замены мер и полный перебор оптимума.
14:341bdbb19Добавлены экран результатов и сохранение сценариев.
14:54fd0dabdДобавлены события, диаграммы районов и экспорт Markdown.
15:1495cccb4Разделены расчёт и AI-анализ, доработаны результаты и ошибки.
15:254030c79Заполнен README для экспертов хакатона.
15:4071eb142Разделены компактный формат чисел и формат Score с двумя знаками.
15:50b931664Добавлен быстрый старт и удалены дубликаты конфигурационных файлов.
15:5283ac3b8Уточнён локальный запуск и удалены заглушки из README.
16:23ded8ad6Добавлены запасные модели и мягкий пропуск AI-проверки при внешних сбоях.
16:30b7037cdДобавлены соответствие критериям оценки, применимость и развитие проекта.
16:34d196f36Добавлена история разработки и упрощено описание переноса на реальные данные.
17:053edd6bfДобавлены рекомендации по расчёту без AI и уточнено соответствие требованиям и критериям оценки в README.
17:211bebed1Добавлен экспорт краткой презентации решения в формате PPTX.

Разработка ведётся 23.09.2026 на площадке хакатона одним участником с использованием Codex в отведённом окне 13:00–18:00. В каждом из пяти часовых интервалов этого окна — 13:00–14:00, 14:00–15:00, 15:00–16:00, 16:00–17:00 и 17:00–18:00 — в репозитории есть зафиксированный промежуточный результат.

Команда

Один участник — vyunner: разработка движка, агента, интерфейса, проверок и документации с использованием Codex.

Все изменения разработки зафиксированы в истории коммитов репозитория. Начальный шаблон репозитория создан ботом платформы; авторские коммиты разработки выполнены от имени vyunner.

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

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

vyunner 19

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

  • 2026-09-23 17:52docs: correct hackathon track namevyunner
  • 2026-09-23 17:31docs: update development history through PPTX exportvyunner
  • 2026-09-23 17:21feat: export scenario presentations as PPTXvyunner
  • 2026-09-23 17:05fix: show calculated recommendations independently of AIvyunner
  • 2026-09-23 16:34docs: record development history and simplify data portabilityvyunner
  • 2026-09-23 16:30docs: map evaluation criteria and describe practical valuevyunner
  • 2026-09-23 16:23fix: fall back on unavailable models and soften AI smoke failuresvyunner
  • 2026-09-23 15:52docs: clarify local launch and remove demo placeholdersvyunner
Все коммиты (19)
  • 2026-09-23 15:50docs: add quick start and remove duplicate config filesvyunner
  • 2026-09-23 15:40fix: distinguish compact values from score formattingvyunner
  • 2026-09-23 15:25docs: complete Russian README for hackathon reviewvyunner
  • 2026-09-23 15:14fix: show calculations before AI analysis and polish resultsvyunner
  • 2026-09-23 14:54feat: add city events, district charts and markdown reportsvyunner
  • 2026-09-23 14:34feat: add full results dashboard and saved scenariosvyunner
  • 2026-09-23 14:09feat: verify recommendations with exhaustive scenario optimizationvyunner
  • 2026-09-23 13:59feat: add tool-driven AI scenario analysisvyunner
  • 2026-09-23 13:34feat: add decision picker and deterministic analyze endpointvyunner
  • 2026-09-23 13:22feat: scaffold Next.js app and deterministic city scoring enginevyunner
  • 2026-09-23 13:15chore: add hackathon requirements and initial datasetsvyunner
Структура репозитория (68 файлов)
.env.example
.gitignore
.nvmrc
AGENTS.md
PROMPTS.md
README.md
TZ.md
app/api/analyze/route.ts
app/api/calc/route.ts
app/api/scenarios/route.ts
app/globals.css
app/layout.tsx
app/page.tsx
components/AgentReport.tsx
components/Contributions.tsx
components/DistrictChart.tsx
components/DistrictOverview.tsx
components/DistrictTable.tsx
components/EventPicker.tsx
components/MeasurePicker.tsx
components/ReportDownload.tsx
components/Scenarios.tsx
components/ScoreCard.tsx
data/districts.json
data/events.json
data/measures.json
data/sample/scenarios.json
lib/agent/index.ts
lib/agent/prompts.ts
lib/agent/report-validation.ts
lib/agent/schemas.ts
lib/agent/tools/get_active_event.ts
lib/agent/tools/get_contributions.ts
lib/agent/tools/get_district_profile.ts
lib/agent/tools/index.ts
lib/agent/tools/score_set.ts
lib/agent/tools/shared.ts
lib/agent/tools/suggest_swaps.ts
lib/agent/tools/validate_set.ts
lib/best-known.ts
lib/engine/data.ts
lib/engine/events.ts
lib/engine/optimize.ts
lib/engine/score.ts
lib/engine/validate.ts
lib/format.ts
lib/markdown-report.ts
lib/openai.ts
lib/presentation.ts
lib/result-schema.ts
lib/scenarios/schema.ts
lib/scenarios/store.ts
lib/suggestions.ts
next-env.d.ts
next.config.ts
package-lock.json
package.json
postcss.config.mjs
scripts/agent-checks.ts
scripts/component-checks.ts
scripts/event-checks.ts
scripts/event-integration-checks.ts
scripts/optimize-checks.ts
scripts/result-checks.ts
scripts/scenario-checks.ts
scripts/smoke.ts
tailwind.config.ts
tsconfig.json

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

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

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