О проекте
Команда: 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 |
| 4 | AI-анализ принятых решений | ✅ При настроенном ключе OpenAI | lib/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 |
| О3 | AI-рекомендации по улучшению выбранного сценария | ✅ Агент объясняет проверенные замены; рекомендации по расчёту доступны и без AI | lib/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 у каждого возможного набора.
Как работает
- Пользователь выбирает пять мер и районы либо нажимает «Загрузить пример из ТЗ». Панель сразу показывает использованный бюджет, остаток и нарушения правил.
- «Рассчитать» отправляет
{ decisions, eventId? }в/api/calc. Сервер валидирует набор, рассчитывает показатели, находит одиночные замены и лучший набор для тех же условий. - UI сразу показывает Score, стоимость, вклады, диаграммы и таблицу районов. Затем отправляет тот же набор и событие в
/api/analyze; состояние ожидания остаётся только в блоке «Разбор агента». - Агент последовательно вызывает инструменты движка и возвращает проверенный JSON-отчёт. Пользователь видит объяснение и журнал шагов с краткими результатами.
- «Применить» использует конкретную рассчитанную замену. «Показать оптимальный набор» загружает результат полного перебора. Оба действия повторяют расчёт и анализ; смена события работает так же.
- Пользователь сохраняет сценарий, сравнивает его 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 + M2 | T1 +2 | Район M1 |
| M10 + M12 | B1 +2 | Район M10 |
| M5 + M6 | E2 +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.27 | 62.99 |
| Алматы | 0.24 | 57.06 |
| Сарыарка | 0.20 | 54.65 |
| Байконур | 0.13 | 56.63 |
| Нура | 0.16 | 49.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.js | 15.5.26 | MIT | Страницы и API |
| React / React DOM | 19.2.8 | MIT | Интерфейс |
| TypeScript | 5.9.3 | Apache-2.0 | Типизация и проверка кода |
| Tailwind CSS | 3.4.19 | MIT | Стили |
OpenAI SDK (openai) | 6.49.0 | Apache-2.0 | Вызовы модели и tools |
| zod | 4.6.5 | MIT | Схемы входов и ответов |
| pptxgenjs | 3.12.0 | MIT | Генерация PPTX-презентации сценария |
| tsx | 4.23.15 | MIT | Запуск TypeScript-проверок |
| PostCSS / Autoprefixer | 8.5.28 / 10.6.1 | MIT | Обработка 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 в смоуке.
- Нажмите «Загрузить пример из ТЗ»:
M7@nura,M8@nura,M10@nura,M12,M5@saryarka. Панель покажет 5 мер, стоимость 95 и остаток 5. - Нажмите «Рассчитать». Score 56.54, вклады, диаграммы и таблица появятся до завершения AI. В блоке агента будет «Агент анализирует сценарий…»; повторный запуск заблокирован.
- В блоке «Рекомендации по расчёту» нажмите «Применить» для замены M5 в Сарыарке → M3 в Нуре. Ожидаемый Score — 57.21, стоимость — 100, подтверждённый прирост относительно предыдущего набора — +0.66.
- Нажмите «Показать оптимальный набор»:
M2,M3@nura,M8@nura,M9@nura,M14. Ожидаемый Score — 57.24, стоимость — 98. Появятся бейдж «Оптимальный набор» и сообщение о лучшем результате среди 694 395 допустимых вариантов. - Проверьте бюджет: нажмите «Сбросить», выберите
M2,M3@nura,M5@esil,M8@nura,M14. Стоимость 113, остаток −13; индикатор красный, показана причина, «Рассчитать» недоступна. Также можно проверить четыре решения, повтор направления сверх лимита или районную меру без района. - Для события снова загрузите пример из ТЗ, затем выберите «Авария на теплосетях в Алматы». Автоматический пересчёт покажет 56.54 → 55.34, база станет 51.36. Алматы будет подсвечен, его C1 после события — 38.00; AI объяснит реакцию набора. «Убрать событие» вернёт результат 56.54.
- Введите название и нажмите «Сохранить сценарий». Проверьте запись в таблице «Сценарии», её Score и стоимость, затем «Загрузить». Нажмите «Скачать отчёт (Markdown)» и проверьте наличие мер, таблицы D, события и полученного разбора.
- Нажмите «Скачать презентацию (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 UTC | 9700fbc | Платформа создала начальный репозиторий. |
| 13:15 | d13c09d | Добавлены требования хакатона и исходные датасеты. |
| 13:22 | e58acb0 | Созданы каркас Next.js и детерминированный движок расчёта. |
| 13:34 | 3cffa7f | Добавлены выбор решений, бюджет и API расчёта. |
| 13:59 | d3cc0a7 | Добавлен AI-анализ сценария через инструменты движка. |
| 14:09 | 9dc6713 | Добавлены проверенные замены мер и полный перебор оптимума. |
| 14:34 | 1bdbb19 | Добавлены экран результатов и сохранение сценариев. |
| 14:54 | fd0dabd | Добавлены события, диаграммы районов и экспорт Markdown. |
| 15:14 | 95cccb4 | Разделены расчёт и AI-анализ, доработаны результаты и ошибки. |
| 15:25 | 4030c79 | Заполнен README для экспертов хакатона. |
| 15:40 | 71eb142 | Разделены компактный формат чисел и формат Score с двумя знаками. |
| 15:50 | b931664 | Добавлен быстрый старт и удалены дубликаты конфигурационных файлов. |
| 15:52 | 83ac3b8 | Уточнён локальный запуск и удалены заглушки из README. |
| 16:23 | ded8ad6 | Добавлены запасные модели и мягкий пропуск AI-проверки при внешних сбоях. |
| 16:30 | b7037cd | Добавлены соответствие критериям оценки, применимость и развитие проекта. |
| 16:34 | d196f36 | Добавлена история разработки и упрощено описание переноса на реальные данные. |
| 17:05 | 3edd6bf | Добавлены рекомендации по расчёту без AI и уточнено соответствие требованиям и критериям оценки в README. |
| 17:21 | 1bebed1 | Добавлен экспорт краткой презентации решения в формате 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.
Авторы коммитов
История коммитов
- 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 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.