О проекте
Команда: NoSleep.dev
Кейс HackAlem.ai: Спецтрек — Astana Innovations
Стадия: Доведённый продукт
Задача кейса: «Аким на 5 часов»: городской симулятор, распределяющий бюджет по 5 направлениям и 5 районам
Суть
Симулятор «Аким на 5 часов» с Go-бэкендом и интерфейсом на Next.js: выбор 5 из 14 инициатив, карта районов, расчёт Score, поиск оптимального набора, кризисные события и AI-разбор результата.
Как устроено
Данные (districts, initiatives, rules, events) хранятся в JSON и встроены в Go-бинарник. Движок scoring на Go считает Score = 0.7·D_avg + 0.3·min(D_d) − N_crit с учётом лагов и синергий и покрыт golden-тестами; scripts/verify.py независимо воспроизводит числа. Optimizer полным перебором ищет оптимальный сценарий, отдельный модуль выдаёт детерминированные городские события. Go-клиент LLM (OpenAI) объясняет результат, опираясь на проверенные факты; без ключа возвращается кэшированный разбор. Next.js UI показывает карту районов (Three.js) и сравнение сохранённых сценариев.
Ключевые решения
- deterministic scoring engine
- golden fixture tests
- exhaustive optimum search
- deterministic crisis event deck
- grounded LLM explanation with cached fallback
- scenario comparison
Особенности
Сначала зафиксировали контракты (contracts/api.md, scoring.md, примеры JSON), потом параллельно разрабатывали ветками ai/be/fe. Формулу проверяет независимая реализация на Python. Полный перебор для поиска оптимума, Docker в один контейнер, работает без API-ключа.
Стек
Go 1.26 · Gin · Next.js 16 · TypeScript · Three.js · shadcn/ui · Docker · Docker Compose · Makefile · Python
Модели
OpenAI
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-2e79658e-nosleep-dev.
README
README.md
Пять решений. Один город. Последствия видны сразу.
Примерьте роль акима Астаны: распределите 100 условных единиц между городскими инициативами и увидьте, как ваш выбор влияет на пять районов.
Демо за 60 секунд · Docker · Локальная разработка · Формула · Статус
BesSheshim за 20 секунд
| Вы делаете | Город показывает | Вы понимаете |
|---|---|---|
| Выбираете ровно 5 из 14 инициатив в пределах 100 единиц бюджета | Карту районов, показатели качества жизни и итоговый Score | Какие районы выиграли, какие проблемы остались и за что пришлось заплатить |
Задача симулятора — сделать компромиссы городского бюджета понятными. Сильный сценарий поднимает средний результат, но также помогает самому уязвимому району и сокращает число критических показателей.
Главная страница
Первый экран: задача и правила понятны до начала игры.
Ниже на главной: как продукт помогает увидеть эффект и объяснить результат.
Экран симулятора
Рабочий экран: слева — каталог из 14 инициатив по категориям, в центре — карта районов с индексом качества жизни, справа — выбранный район и план из пяти решений в пределах бюджета 100 единиц.
Демо за 60 секунд
- Откройте http://localhost:3000 и нажмите «Начать игру». На старте у города 2 критических показателя в Нуре; базовый Score по проверенной модели — 52,57.
- Добавьте пять мер в указанном порядке: M7 «Школа + детсад» → Нура, M8 «Поликлиника» → Нура, M10 «Освещение и камеры» → Нура, M12 «Платформа обращений» → весь город, M5 «Чистое топливо» → Сарыарка.
- В панели «Ваш план» проверьте бюджет: 95 / 100. Нажмите «Посмотреть результат».
- Проверенный сценарий даёт 56,49 (+3,92). Оба критических показателя Нуры выходят из красной зоны; сочетание M10 + M12 дополнительно улучшает безопасность Нуры. Разбор объясняет и оставшиеся слабые места: транспорт и экологию Нуры.
Это эталонный сценарий из контракта. Числа независимо воспроизводит scripts/verify.py и проверяют golden-фикстуры.
Как запустить
Для запуска без Docker нужны Go 1.26+ и Node.js 20.9+. Ключ LLM для демонстрации не нужен.
git clone https://github.com/BAITC-Hacks/hack-2e79658e-nosleep-dev.git
cd hack-2e79658e-nosleep-dev
cp .env.example .env
npm --prefix apps/web ci
make dev
Затем откройте http://localhost:3000. API доступен на http://localhost:8000/api/v1; проверка работоспособности — /health.
[!IMPORTANT] Фронтенд по умолчанию обращается к Go API через
/api/v1/*. Для локальной разработки запустите API вместе с Next.js черезmake dev. Локальные демоданные включаются только приNEXT_PUBLIC_USE_MOCKS=true.
Docker: один контейнер
Docker-образ собирает Go API и production-сборку Next.js. Внутри контейнера работают оба процесса; наружу открыт только порт веб-приложения 3000. Запросы браузера к /api/v1/* Next.js перенаправляет на API внутри контейнера. Поэтому один и тот же образ работает на localhost, IP-адресе сервера или за обратным прокси без пересборки с новым адресом API.
Быстрый запуск
Установите Docker с плагином Compose и запустите из корня репозитория:
docker compose up -d --build --wait
docker compose ps
curl -f http://localhost:3000/api/v1/health
Откройте http://localhost:3000 и перейдите в симулятор. Ответ проверки API — {"status":"ok"}. --wait дожидается статуса healthy; запуск API может занять около полуминуты. Compose поднимает один сервис app; файл .env и ключ LLM для этого демо не требуются. При первом запуске Docker загрузит базовые образы, установит Go и npm-зависимости и соберёт оба приложения.
Без Compose
docker build -t bessheshim:local .
docker run --name bessheshim -d --restart unless-stopped -p 3000:3000 bessheshim:local
docker ps --filter name=bessheshim
curl -f http://localhost:3000/api/v1/health
Если проверка сразу после docker run ещё не отвечает, подождите, пока docker ps покажет (healthy), и повторите curl. Для остановки и удаления контейнера: docker rm -f bessheshim. Контейнер не использует базу данных или постоянный том; данные приложения и контрактные примеры входят в образ.
Порт и размещение на сервере
В Compose APP_PORT меняет порт хоста, а APP_BIND — адрес привязки. Например, если HTTPS завершает обратный прокси на том же сервере:
APP_BIND=127.0.0.1 APP_PORT=8080 docker compose up -d --build --wait
curl -f http://127.0.0.1:8080/api/v1/health
Направьте обратный прокси на http://127.0.0.1:8080 и передавайте через него все пути, включая /api/v1/* и /_next/*. Для прямого доступа с другой машины оставьте APP_BIND=0.0.0.0 (значение по умолчанию), откройте выбранный APP_PORT в сетевом экране и перейдите на http://IP_СЕРВЕРА:APP_PORT. Для публичного сайта настройте домен и HTTPS на обратном прокси. В варианте docker run порт меняется флагом -p 8080:3000.
Не меняйте внутренний API_PORT=8000 или PORT=3000: собранный Next.js направляет API-запросы на 127.0.0.1:8000, а Docker публикует порт 3000.
Обновление и диагностика
git pull
docker compose up -d --build --wait
docker compose logs -f app
docker compose ps
docker compose down
docker compose up -d --build --wait пересобирает образ из текущего кода, заменяет контейнер и дожидается его здоровья. В docker compose ps статус должен быть healthy: встроенная проверка обращается к /api/v1/health через Next.js и тем самым проверяет оба процесса. Если порт занят, задайте APP_PORT=8080. Если проверка не проходит, посмотрите docker compose logs app и убедитесь, что оба процесса запустились. При завершении одного из процессов контейнер останавливает второй, после чего Compose перезапускает сервис.
Образ собирается с NEXT_PUBLIC_USE_MOCKS=false и относительным адресом API /api/v1; эти значения в браузерном коде фиксируются во время сборки. Без ключа LLM API возвращает разбор с пометкой cached; при настроенном OPENAI_API_KEY он может вернуть живой AI-разбор.
Настройка AI-разбора (необязательно)
Для живого AI-разбора скопируйте .env.example в .env, заполните OPENAI_API_KEY, затем запустите docker compose up -d --build --wait. Compose передаёт в контейнер LLM_BASE_URL, OPENAI_API_KEY, LLM_MODEL, LLM_TIMEOUT_SECONDS и DEMO_MODE; .env не включается в образ и игнорируется Git. При DEMO_MODE=true, пустом ключе или ошибке провайдера API отдаёт разбор с пометкой cached. Для docker run добавьте --env-file .env перед именем образа. Внутренние API_PORT и PORT оставьте как в образе.
Как считается результат
Результат рассчитывается по открытой спецификации, на одинаковых исходных данных для каждого игрока:
$$ \text{Score} = 0.7 \cdot D_{avg} + 0.3 \cdot \min_d(D_d) - N_{crit} $$
Здесь D_avg — средний балл районов с учётом населения, min D_d — балл самого уязвимого района, N_crit — число показателей ниже 40. Формула учитывает не только общий рост, но и состояние самого слабого района. Эффекты инициатив учитывают задержку, а некоторые сочетания дают синергию.
| Проверенный пример | До | После |
|---|---|---|
| Итоговый Score | 52,57 | 56,49 |
| Критические показатели | 2 | 0 |
| Бюджет | 0 / 100 | 95 / 100 |
Данные и правила лежат в data/, а независимая реализация формулы — в scripts/verify.py. Реализованный AI-клиент получает готовые результаты для объяснения; он не рассчитывает Score.
Что реализовано
| Часть | Текущий статус |
|---|---|
| Главная и интерфейс симулятора | Запускаются локально; выбор инициатив, районов, бюджет и результат доступны в UI |
| Детерминированный движок Score | Реализован на Go в internal/scoring и покрыт golden-тестами |
| HTTP API | Маршруты подключены к движку Score; каждый набор решений рассчитывается по данным модели |
| AI-разбор | Go-клиент подключён к HTTP API; без ключа или при сбое LLM возвращается разбор с пометкой cached |
| Оптимум, сравнение, городские события | Optimizer и события подключены к API; сохранение и сравнение сценариев доступны в UI, события остаются в API |
Это hackathon demo build. Для точной проверки модели используйте тесты и эталонный сценарий выше.
Архитектура
flowchart LR
Player[Игрок] --> Web[Next.js интерфейс]
Web --> API[Go + Gin API]
API --> Engine[Движок Score на Go]
API --> LLM[Go AI-клиент и cached fallback]
Data[data/*.json] --> Engine
Engine --> Tests[Golden-тесты и независимая проверка]
Контракт API — в contracts/api.md. Основные маршруты: GET /catalog, POST /simulate, POST /explain, GET /optimum, POST /compare, POST /events/draw.
Проверка
python3 scripts/verify.py
cd apps/api && go test ./...
Ожидаемые опорные значения: BASE Score=52.5650, EX Score=56.4851. Go-тесты проверяют scoring, HTTP, LLM-клиент, optimizer и события. Проверены npm run lint и npm run build для веб-приложения.
Команда
NoSleep.dev · BAITC Hackathon 2026 · Астана, Казахстан
BesSheshim — «Аким на 5 часов». Пять решений. Один город.
Авторы коммитов
История коммитов
- 2026-09-23 17:54fixAlikhan Sailybaev
- 2026-09-23 17:52Update README.mdalibektasten
- 2026-09-23 17:52fixAlikhan Sailybaev
- 2026-09-23 17:43feat sim screen readmeAlikhan Sailybaev
- 2026-09-23 17:31docs(ai): record merged implementation statusAlibek
- 2026-09-23 17:31fix(api): retry invalid structured LLM outputAlibek
- 2026-09-23 17:24Merge pull request #20 from BAITC-Hacks/codex/openai-api-defaultAlikhan Sailybaev
- 2026-09-23 17:23docs: refresh graph after OpenAI branch mergeAlikhan Sailybaev
Все коммиты (64)
- 2026-09-23 17:22Merge remote-tracking branch 'origin/main' into HEADAlikhan Sailybaev
- 2026-09-23 17:19Merge pull request #19 from BAITC-Hacks/codex/play-cockpit-redesignAlikhan Sailybaev
- 2026-09-23 17:18Merge main into play cockpit and integrate scenario comparisonAlikhan Sailybaev
- 2026-09-23 17:05feat: ship BesSheshim demo in one Docker containerAlikhan Sailybaev
- 2026-09-23 16:51docs(handoff): note current API process predates summary changed.shaimurat
- 2026-09-23 16:50refactor(llm): clarify scenario summary wordingd.shaimurat
- 2026-09-23 16:45Merge pull request #18 from BAITC-Hacks/codex/openai-api-defaultDanial
- 2026-09-23 16:45Merge pull request #15 from BAITC-Hacks/ai/demo-qaDanial
- 2026-09-23 16:44docs: refresh judge README and homepage screenshotsAlikhan Sailybaev
- 2026-09-23 16:41Merge main into ai/demo-qa and resolve handoffAlibek
- 2026-09-23 16:36docs(handoff): link backend pull requestd.shaimurat
- 2026-09-23 16:33Merge pull request #17 from BAITC-Hacks/codex/openai-verificationAlikhan Sailybaev
- 2026-09-23 16:25docs(handoff): record local API smoke checkd.shaimurat
- 2026-09-23 16:23fix(llm): ground scenario explanation and constrain recommendationsd.shaimurat
- 2026-09-23 16:05docs(backend): record live LLM fixture evaluationd.shaimurat
- 2026-09-23 16:00test(ai): verify OpenAI defaults and live integrationAlibek
- 2026-09-23 15:53docs: add spoken timing check to demo preparationAlibek
- 2026-09-23 15:52Merge pull request #16 from BAITC-Hacks/codex/openai-api-defaultDanial
- 2026-09-23 15:51docs: flag live comparison timeout riskAlibek
- 2026-09-23 15:50docs: add rehearsed 60-second demo and QA statusAlibek
- 2026-09-23 15:48fix(llm): accept grounded OpenAI explanationsd.shaimurat
- 2026-09-23 15:35docs(backend): record OpenAI review handoffd.shaimurat
- 2026-09-23 15:34feat(llm): default to OpenAI API key and modeld.shaimurat
- 2026-09-23 15:17Merge pull request #14 from BAITC-Hacks/ai/llm-groundingDanial
- 2026-09-23 15:15fix(ai): ground live explanations in verified scoring factsAlibek
- 2026-09-23 15:14Merge pull request #13 from BAITC-Hacks/be/demo-readinessDanial
- 2026-09-23 15:12docs: record demo readiness PR handoffd.shaimurat
- 2026-09-23 15:10fix(demo): make clean startup and Compose web runnabled.shaimurat
- 2026-09-23 14:58docs: define BesSheshim brand and graph workflowAlikhan Sailybaev
- 2026-09-23 14:53feat(web): build editorial landing pageAlikhan Sailybaev
- 2026-09-23 14:51Merge pull request #10 from BAITC-Hacks/be/startup-integrationDanial
- 2026-09-23 14:50Merge main and resolve frontend API client conflictd.shaimurat
- 2026-09-23 14:48Merge pull request #9 from BAITC-Hacks/fe/scenario-compareAlikhan Sailybaev
- 2026-09-23 14:45docs: record backend review handoffd.shaimurat
- 2026-09-23 14:44feat(web): add saved scenario comparisonAlikhan Sailybaev
- 2026-09-23 14:44fix(api): restore demo startup and explanation datad.shaimurat
- 2026-09-23 14:32Merge pull request #8 from BAITC-Hacks/fe/real-api-integrationAlikhan Sailybaev
- 2026-09-23 14:29feat(web): integrate live API and optimum gaugeAlikhan Sailybaev
- 2026-09-23 14:27Merge pull request #7 from BAITC-Hacks/ai/real-servicesAlikhan Sailybaev
- 2026-09-23 14:25feat(api): wire real AI data services into serverAlibek
- 2026-09-23 14:21Merge pull request #6 from BAITC-Hacks/be/api-bootstrapDanial
- 2026-09-23 14:15docs(backend): record API bootstrap handoffd.shaimurat
- 2026-09-23 14:14feat(api): scaffold fixture-backed simulator endpointsd.shaimurat
- 2026-09-23 14:12Merge pull request #5 from BAITC-Hacks/fe/live-simulatorAlikhan Sailybaev
- 2026-09-23 14:11Merge pull request #4 from BAITC-Hacks/docs/readme-designAlikhan Sailybaev
- 2026-09-23 14:09feat(web): build live city budget simulatorAlikhan Sailybaev
- 2026-09-23 14:04docs: redesign README as product page with SVG bannerAlikhan Sailybaev
- 2026-09-23 14:03Merge pull request #3 from BAITC-Hacks/ai/scoringAlikhan Sailybaev
- 2026-09-23 14:01fix(api): embed scoring datasets for standalone runtimeAlibek
- 2026-09-23 13:57Merge pull request #2 from BAITC-Hacks/ai/scoringAlikhan Sailybaev
- 2026-09-23 13:55feat(api): add deterministic crisis event deckAlibek
- 2026-09-23 13:55feat(api): add exhaustive optimum scenario searchAlibek
- 2026-09-23 13:53Merge pull request #1 from BAITC-Hacks/ai/scoringAlikhan Sailybaev
- 2026-09-23 13:50feat(api): add resilient structured LLM explainerAlibek
- 2026-09-23 13:49feat(api): implement scoring engine per contractsAlibek
- 2026-09-23 13:40docs: add team plan, API contract, dataset and workflow for Akim-5h simulatorAlikhan Sailybaev
Структура репозитория (133 файлов)
.ai/DECISIONS.md .ai/JUDGING.md .ai/PLAN.md .ai/STATUS.md .ai/WORKFLOW.md .ai/handoffs/ai-data.md .ai/handoffs/backend.md .ai/handoffs/frontend.md .ai/prompts/ai-data.md .ai/prompts/backend.md .ai/prompts/frontend.md .dockerignore .env.example .gitignore AGENTS.md DEMO.md Dockerfile Makefile README.md apps/api/cmd/server/main.go apps/api/go.mod apps/api/go.sum apps/api/internal/events/events.go apps/api/internal/events/events_test.go apps/api/internal/http/fixtures.go apps/api/internal/http/live.go apps/api/internal/http/live_test.go apps/api/internal/http/router.go apps/api/internal/http/router_test.go apps/api/internal/http/types.go apps/api/internal/llm/llm.go apps/api/internal/llm/llm_test.go apps/api/internal/llm/prompt.go apps/api/internal/optimizer/optimizer.go apps/api/internal/optimizer/optimizer_test.go apps/api/internal/real/services.go apps/api/internal/real/services_test.go apps/api/internal/scoring/data/districts.json apps/api/internal/scoring/data/initiatives.json apps/api/internal/scoring/data/rules.json apps/api/internal/scoring/scoring.go apps/api/internal/scoring/scoring_test.go apps/web/.gitignore apps/web/AGENTS.md apps/web/CLAUDE.md apps/web/DESIGN.md apps/web/README.md apps/web/components.json apps/web/docs/frontend-plan.md apps/web/docs/landing-prompt.md apps/web/eslint.config.mjs apps/web/next.config.ts apps/web/package-lock.json apps/web/package.json apps/web/postcss.config.mjs apps/web/public/brand/hero-amber.png apps/web/public/brand/hero-emerald-poster.jpg apps/web/public/brand/hero-emerald.mp4 apps/web/public/brand/tools/ai-analysis.png apps/web/public/brand/tools/budget.png apps/web/public/brand/tools/city-impact.png apps/web/public/brand/tools/district-map.png apps/web/public/brand/tools/scenario.png apps/web/public/brand/tools/transparent-score.png apps/web/public/file.svg apps/web/public/globe.svg apps/web/public/next.svg apps/web/public/vercel.svg apps/web/public/window.svg apps/web/src/app/globals.css apps/web/src/app/icon.svg apps/web/src/app/layout.tsx apps/web/src/app/page.tsx apps/web/src/app/play/page.tsx apps/web/src/components/astana-three-map.tsx apps/web/src/components/block/arrow-fill-button.css apps/web/src/components/block/arrow-fill-button.tsx apps/web/src/components/block/dotted-grid.jsx apps/web/src/components/block/rectangular-text-reveal.jsx apps/web/src/components/city-map.tsx apps/web/src/components/landing-page.tsx apps/web/src/components/scenario-compare.tsx apps/web/src/components/score-counter.tsx apps/web/src/components/scroll-reveal.tsx apps/web/src/components/simulator.tsx apps/web/src/components/simulator/district-picker.tsx apps/web/src/components/simulator/initiative-catalog.tsx apps/web/src/components/simulator/live-impact-panel.tsx apps/web/src/components/simulator/plan-tray.tsx apps/web/src/components/simulator/scenario-header.tsx apps/web/src/components/simulator/scenario-result.tsx apps/web/src/components/site-header.tsx apps/web/src/components/ui/badge.tsx apps/web/src/components/ui/button.tsx apps/web/src/components/ui/card.tsx apps/web/src/components/ui/progress.tsx apps/web/src/components/ui/select.tsx apps/web/src/components/ui/separator.tsx apps/web/src/components/ui/skeleton.tsx apps/web/src/data/README.md apps/web/src/data/astana-districts.json apps/web/src/lib/api-client.ts apps/web/src/lib/types.ts apps/web/src/lib/utils.ts apps/web/tsconfig.json contracts/api.md contracts/examples/catalog.json contracts/examples/compare.json contracts/examples/error.json contracts/examples/event.json contracts/examples/explain-cached.json contracts/examples/optimum.json contracts/examples/simulate-invalid-budget.json contracts/examples/simulate-partial.json contracts/examples/simulate-valid.json contracts/go-interfaces.md contracts/scoring.md data/districts.json data/events.json data/fixtures/golden.json data/initiatives.json data/rules.json docker-compose.yml docs/BRAND.md docs/assets/banner.svg docs/assets/homepage-features.png docs/assets/homepage-hero.png docs/assets/simulator-screen.png graphify-out/GRAPH_REPORT.md graphify-out/graph.html graphify-out/graph.json scripts/start-container.mjs scripts/verify.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.