AAYMA
RU
Войти

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

Городской симулятор с процедурным 3D-городом на Three.js: пользователь выбирает 5 мер из 14 для пяти районов при бюджете 100, получает…

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

О проекте

Команда: SKYNOMADS
Кейс HackAlem.ai: Спецтрек — Astana Innovations
Стадия: Рабочий MVP

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

Суть

Городской симулятор с процедурным 3D-городом на Three.js: пользователь выбирает 5 мер из 14 для пяти районов при бюджете 100, получает детерминированный Score и AI-объяснение компромиссов.

Как устроено

React SPA запрашивает каталог через GET /api/simulator и отправляет 5 решений через POST /api/simulator. Express (или Cloudflare Worker) с общим валидатором и service.ts считает показатели по db.json с учётом лагов, синергий, слабейшего района и штрафа за значения ниже 40. Готовые выводы передаются в OpenAI Responses API (gpt-4o-mini) для объяснения сильных сторон и рисков. Выбранные меры подсвечиваются на 3D-карте; в отдельных ветках лежат альтернативные версии: изометрическая концепция и командные сценарии с событиями.

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

  • deterministic scoring with lags and synergies
  • weakest-district score
  • procedural 3D city scene
  • LLM explanation of computed results

Особенности

Много внимания визуалу: процедурный 3D-город с подсветкой выбранных мер, портреты акима и советников, собственные иллюстрации к инициативам. Три варианта продукта в разных ветках. Бэкенд собран из шаблона, в нём остались выключенные auth/blog/media-маршруты.

Стек

React 19 · TypeScript · Webpack 5 · Express 5 · Three.js · Yarn workspaces · Cloudflare Workers · PixiJS

Модели

gpt-4o-mini


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

README

README.md

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

Три варианта исполнения проекта

У проекта три варианта исполнения, размещённые в отдельных ветках:

ВариантНазначениеСсылка
1. Основная версия — mainАктуальный MVP: 3D-город на Three.js, выбор инициатив, расчёт Score и AI-анализ. Поддерживает локальный запуск и Cloudflare Worker.Открыть ветку main
2. Концептуальная версия — concept/isometric-city-simОтдельная ветка концепции изометрического городского симулятора. Состояние и команды запуска смотрите в этой ветке.Открыть концептуальную ветку
3. Альтернативная версия — feature/city-team-scenariosОтдельный вариант проекта в ветке командных городских сценариев. Состояние реализации и команды запуска смотрите в этой ветке.Открыть ветку city-team-scenarios

Инструкции ниже относятся к основной версии в ветке main.

Что решает

Симулятор городских решений: распределите ограниченный бюджет между инициативами для условных районов Астаны, получите рассчитанный Astana Quality of Life Score и объяснение компромиссов от AI.

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

  • Общие для всех пользователей бюджет 100 и синтетические показатели пяти районов, 14 мероприятий (полные исходные данные — docs/data-set.md и docs/data-set.json, рабочие данные backend — apps/backend/db.json).
  • Выбор ровно пяти мер, проверка районов, повторов, ограничений по направлениям, несовместимостей и бюджета.
  • Детерминированный расчёт показателей и Score по сокращённому apps/backend/db.json, сверяемому с docs/data-set.json: лаги, синергии, слабейший район и штраф за значения ниже 40.
  • Серверный вызов OpenAI для объяснения уже рассчитанного результата. Если ключа нет или API недоступен, Score остаётся доступен, а ошибка AI показана явно.
  • Тёмный экран симулятора: процедурный 3D-город на Three.js с рекой, мостами, кварталами и деревьями; каталог мероприятий, план на пять мер и результат. Вращение — перетаскиванием, масштаб — колёсиком. Это иллюстративная модель, не географически точная карта Астаны. При недоступном WebGL остаются кнопки выбора районов.
  • Выбранные инициативы сразу подсвечивают условные участки и здания: районные — в назначенном районе, городские — во всех пяти. Цвет показывает направление; открытая инициатива выделяется голубым. Нажмите на значок меры или «На карте» в каталоге, чтобы увидеть её название, стоимость и лаг. При смене района, удалении или сбросе отметки обновляются; мера без назначенного района не получает случайное место на карте. Подсветка обозначает выбор в плане, а не факт строительства или уже рассчитанный эффект.
  • Предупреждающие тосты сообщают о неполном плане, превышении бюджета, выборе районной меры без района и ошибках API/AI. Сообщение можно закрыть вручную; оно исчезает через 8 секунд, таймер приостанавливается при наведении или фокусе. Ошибки расчёта также остаются в форме.

Архитектура

React SPA (apps/frontend/) запрашивает каталог через GET /api/simulator и отправляет пять объектов { "measureId": "M7", "district": "Нура" } в массиве decisions через POST /api/simulator. Локально HTTP обслуживает Express, на Cloudflare — apps/backend/cloudflare.ts. Оба используют общий валидатор, расчёт из features/simulator/service.ts и OpenAI-интеграцию. Данные apps/backend/db.json импортируются и включаются в сборку. LLM получает стоимость, остаток бюджета, эффекты и готовые выводы о районах; не рассчитывает Score. Сценарии не сохраняются. База данных и миграции не нужны.

React / Three.js → Express validator → deterministic Score → OpenAI → explanation
                                           ↑
                                   apps/backend/db.json

Технологии

React 19 + TypeScript + Webpack 5 — интерфейс; Express 5 + TypeScript — API и расчёт; OpenAI Responses API (gpt-4o-mini) — текстовое объяснение; Yarn Classic workspaces — зависимости и запуск.

Изображения инициатив

Подготовленные командой PNG находятся в apps/frontend/public/initiatives/. Карточки используют уменьшенные WebP-копии 256×256 из подпапки optimized/, а при их отсутствии загружают исходный PNG. Все 14 иллюстраций включаются в сборку локально, без зависимости от внешнего хостинга. SVG-рисунки в карточках заменены растровыми изображениями.

Портреты akim.png и advisor-social.png, advisor-transport.png, advisor-environment.png находятся в той же папке. Карточки используют WebP-копии 192×256 с резервной загрузкой исходного PNG. Стрелки и точки в карточке советника переключают портрет, подпись и подсказку вместе.

Шрифты

Основной шрифт — Outfit для латиницы и цифр; кириллицу, отсутствующую в Outfit, отображает Inter. Оба шрифта хранятся локально в WOFF2: внешнего подключения к Google Fonts при запуске нет. Обычный текст имеет вес 300, выделения — 400, основные заголовки и числовые акценты — максимум 500. Источники и лицензия SIL OFL находятся в apps/frontend/app/shared/assets/fonts/.

Требования

Рекомендуем Node.js 24 LTS и Yarn Classic 1.22.22 (проверенная среда: Node.js 24.19.0). Минимум по проекту — Node.js 22.18.0. Нужен современный браузер с WebGL2 для 3D-карты; при недоступном WebGL остаются выбор районов и расчёт.

Для AI нужен интернет и ключ OpenAI API с доступом к gpt-4o-mini и оплаченной API-квотой (подписка ChatGPT не заменяет API-квоту). Для расчёта Score ключ не требуется. Установка зависимостей требует доступа к реестру пакетов.

Быстрый запуск

Если Yarn отсутствует, после установки Node.js выполните npm install --global [email protected]. Все команды ниже выполняются из корня репозитория; не запускайте отдельный yarn install внутри apps.

git clone https://github.com/BAITC-Hacks/hack-30aab437-skynomads.git
cd hack-30aab437-skynomads
yarn install --frozen-lockfile

Создайте конфигурацию:

PowerShell (Windows):

Copy-Item apps/backend/.env.example apps/backend/.env

macOS/Linux:

cp apps/backend/.env.example apps/backend/.env

Откройте apps/backend/.env в редакторе, замените значение OPENAI_API_KEY собственным ключом. Не отправляйте его в чат или Git. Для проверки без AI оставьте значение пустым. Затем из корня:

yarn start

Откройте http://localhost:3000; backend работает на http://localhost:7000. Webpack проксирует /api в Express. Остановить оба сервера — Ctrl+C.

Запуск готовой сборки (рекомендуется для демо)

yarn verify
yarn serve

Откройте http://localhost:7000 — Express раздаёт и собранный интерфейс, и API с одного адреса. Предварительная сборка обязательна. Это локальный запуск, не развёртывание на внешний хостинг. yarn start — режим разработки на 3000, yarn serve — запуск собранного проекта на 7000.

Конфигурация

Переменная (apps/backend/.env)НазначениеПример в .env.example
OPENAI_API_KEYРеальный вызов AI; без неё Score считается, AI возвращает явную ошибкуreplace-with-your-openai-api-key
PORTПорт backend7000
NODE_ENVРежим backenddevelopment

.env игнорируется Git. Шаблонные auth/blog/media-маршруты отключены: JWT, SMTP и отдельные учётные записи для симулятора не нужны. Если меняете PORT, для разработки потребуется согласовать target прокси в apps/frontend/webpack.config.cjs; в режиме yarn serve достаточно открыть новый порт.

Проверка основного сценария

  1. Откройте «Решения» в боковой навигации и нажмите «Загрузить демо-набор» в панели «План города». Выбраны M7/Нура, M8/Нура, M10/Нура, M12/весь город, M5/Сарыарка. Альтернативно нажимайте на район на условной карте и добавляйте меры из каталога справа, затем при необходимости уточняйте район в плане. Можно закрыть панель, чтобы увидеть подсветку: три районные меры в Нуре, одну в Сарыарке и пять отметок городской M12. Для расчёта снова откройте «Решения» или нажмите кнопку ▶ вверху. Стоимость M12 учитывается один раз.
  2. Нажмите «Рассчитать результат». Стоимость 95, остаток 5, исходный Score 52.56, новый Score 56.54 (до округления 56.5431), срабатывает синергия M10 + M12. Откроется AI-отчёт; его можно закрыть крестиком или Escape. Показатели направлений внизу — средние арифметические значений направления по районам, отдельно от взвешенного итогового Score.
  3. При корректном OPENAI_API_KEY дождитесь AI-объяснения сильных сторон и рисков. Без него интерфейс покажет сообщение об отсутствии конфигурации рядом с рассчитанными числами.
  4. Уберите одну меру из плана и повторите расчёт: получите сообщение «Выберите ровно 5 мероприятий» без нового Score. Если сумма выбранных мер превысит 100, отправка будет заблокирована; сервер также отклоняет такие запросы.

Проверки кода:

yarn verify

Команда последовательно запускает backend-тесты (включая HTTP-проверки), TypeScript и production build. Отдельно доступны yarn test, yarn typecheck, yarn build. Автотесты OpenAI подменяют HTTP только внутри тестов и не расходуют квоту; приложение использует настоящий API. Сценарий демонстрации и чек-лист сдачи — docs/submission.md.

После сборки можно автоматически проверить запущенное приложение:

yarn smoke
yarn smoke --live

Первая команда запускает встроенный сервер на свободном порту, проверяет UI/ресурсы, API и обработку отсутствующего ключа, не обращаясь к OpenAI. --live делает один реальный платный API-вызов с ключом из backend .env и проверяет наличие объяснения. Обе команды сами останавливают свой проверочный сервер.

Если что-то не запускается

  • Порт занят: остановите предыдущий экземпляр проекта; не запускайте yarn start и yarn serve одновременно на одном backend-порту.
  • Не удалось загрузить каталог: убедитесь, что Express работает на 7000. В production открывайте 7000, а не отдельный статический сервер фронтенда на 5000.
  • Ошибка AI / ключ отклонён: проверьте apps/backend/.env, доступ к модели и API-баланс, затем перезапустите backend. Результат Score не теряется; готового ответа вместо AI нет.
  • Установка отклонена из-за Node: используйте Node 24 LTS и Yarn 1.22.22; не смешивайте npm install и yarn install.
  • 3D недоступно: разрешите аппаратное ускорение/попробуйте современный браузер; резервные кнопки выбора работают без WebGL.

Ограничения

Датасет синтетический; цифры не являются прогнозом для реальной Астаны. Визуализация изменений районов реализована. Сравнение команд, случайные события, история сценариев, автоматические презентации и поиск оптимального плана не входят в MVP. AI-текст недетерминирован и может содержать неточные формулировки; числовой результат определяется только кодом и показывается отдельно.

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

Шаблонные auth/blog/media-файлы сохранены, но их маршруты не подключены. Backend использует apps/backend/db.json; при изменении полного набора в docs/data-set.json синхронизируйте рабочую копию — это проверяет тест. Webpack предупреждает о размере Three.js и исходных PNG; миниатюры в UI загружаются из оптимизированных WebP. Конфиги Vercel унаследованы от шаблона; актуальная конфигурация Cloudflare — wrangler.jsonc. Статус фактической публикации смотрите в журнале релиза, наличие конфига не означает успешный deploy.

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

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

baau 17Your Name 4ytleuov 1

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

  • 2026-09-23 18:19docs: link third project variant to city team scenarios branchbaau
  • 2026-09-23 18:09docs: remove Cloudflare Workers instructions from READMEbaau
  • 2026-09-23 18:05feat: build PixiJS city prototype and Docker dev loopYour Name
  • 2026-09-23 17:59docs: update README.mdbaau
  • 2026-09-23 17:55feat: add team scenarios, events and presentation draftytleuov
  • 2026-09-23 17:52docs: record release publication and pending Cloudflare loginbaau
  • 2026-09-23 17:51feat: add Cloudflare Workers deployment for city simulatorbaau
  • 2026-09-23 17:40feat: implement city scenario simulator backend and frontend featuresbaau
Все коммиты (22)
  • 2026-09-23 17:04feat: add frontend city simulator features with 3D scene rendering and character portraitsbaau
  • 2026-09-23 16:24feat: implement 3D city scene using Three.js and add project documentationbaau
  • 2026-09-23 16:18feat: add simulator dashboard components, scene logic, and initiative image assetsbaau
  • 2026-09-23 15:51Add raster visual referencesTim TJey Jun
  • 2026-09-23 15:45feat: add Home page component and stylesbaau
  • 2026-09-23 15:37feat: add Three.js city scene simulator and project context filesbaau
  • 2026-09-23 15:29feat: add Three.js city simulator and home page componentsbaau
  • 2026-09-23 15:25docs: add visual references for isometric city conceptTim TJey Jun
  • 2026-09-23 15:12feat: add CityMap component for interactive district visualization in simulatorbaau
  • 2026-09-23 15:11feat: implement city scenario simulator backend with analysis and testsbaau
  • 2026-09-23 14:48docs: add isometric city simulator conceptTim TJey Jun
  • 2026-09-23 14:41feat: add city management simulator MVP with backend scoring logic, OpenAI integration, and frontend UIbaau
  • 2026-09-23 14:22feat: scaffold initial monorepo structure with backend and frontend applicationsbaau
  • 2026-09-23 14:04chore: initialize repository structure, agent instructions, project context, and documentationbaau
Структура репозитория (158 файлов)
.gitignore
AGENTS.md
README.md
apps/backend/.env.example
apps/backend/.gitignore
apps/backend/.npmrc
apps/backend/README.md
apps/backend/api/index.ts
apps/backend/cloudflare.test.ts
apps/backend/cloudflare.ts
apps/backend/db.json
apps/backend/features/auth/controller.ts
apps/backend/features/auth/middleware.ts
apps/backend/features/auth/repository.ts
apps/backend/features/auth/router.ts
apps/backend/features/auth/service.ts
apps/backend/features/blog/controller.ts
apps/backend/features/blog/repository.ts
apps/backend/features/blog/router.ts
apps/backend/features/blog/service.ts
apps/backend/features/media/controller.ts
apps/backend/features/media/repository.ts
apps/backend/features/media/router.ts
apps/backend/features/media/service.ts
apps/backend/features/simulator/analysis.test.ts
apps/backend/features/simulator/analysis.ts
apps/backend/features/simulator/dataset.test.ts
apps/backend/features/simulator/http.test.ts
apps/backend/features/simulator/router.ts
apps/backend/features/simulator/service.test.ts
apps/backend/features/simulator/service.ts
apps/backend/models/blog/model.json
apps/backend/models/media/model.json
apps/backend/models/user/model.json
apps/backend/package.json
apps/backend/public/404.html
apps/backend/public/favicon.ico
apps/backend/public/index.html
apps/backend/public/robots.txt
apps/backend/public/styles.css
apps/backend/public/uploads/no-avatar.png
apps/backend/public/uploads/not-found.png
apps/backend/server.ts
apps/backend/shared/errors/errorResponse.ts
apps/backend/shared/index.ts
apps/backend/shared/middleware/asyncHandler.ts
apps/backend/shared/middleware/errorHandler.ts
apps/backend/shared/middleware/fileUpload.ts
apps/backend/shared/services/nodeMailer.ts
apps/backend/tsconfig.json
apps/backend/types/express/index.d.ts
apps/backend/vercel.json
apps/backend/yarn.lock
apps/frontend/.gitignore
apps/frontend/.npmrc
apps/frontend/README.md
apps/frontend/app/App.tsx
apps/frontend/app/features/simulator/CharacterPortrait.tsx
apps/frontend/app/features/simulator/CityMap.tsx
apps/frontend/app/features/simulator/InitiativeThumbnail.tsx
apps/frontend/app/features/simulator/ScenarioDashboard.tsx
apps/frontend/app/features/simulator/WarningToast.tsx
apps/frontend/app/features/simulator/api.ts
apps/frontend/app/features/simulator/cityHighlights.ts
apps/frontend/app/features/simulator/cityScene.ts
apps/frontend/app/features/simulator/mapInitiatives.ts
apps/frontend/app/global.d.ts
apps/frontend/app/globals.scss
apps/frontend/app/index.tsx
apps/frontend/app/pages/Home.scss
apps/frontend/app/pages/Home.tsx
apps/frontend/app/pages/NotFound.tsx
apps/frontend/app/providers.tsx
apps/frontend/app/routes.tsx
apps/frontend/app/shared/assets/fonts/Inter-Cyrillic.woff2
apps/frontend/app/shared/assets/fonts/Inter-CyrillicExt.woff2
apps/frontend/app/shared/assets/fonts/OFL.txt
apps/frontend/app/shared/assets/fonts/Outfit-Latin.woff2
apps/frontend/app/shared/assets/fonts/Outfit-LatinExt.woff2
apps/frontend/app/shared/assets/fonts/SOURCES.md
apps/frontend/app/shared/assets/fonts/inter/Inter-Bold.ttf
apps/frontend/app/shared/assets/fonts/inter/Inter-Medium.ttf
apps/frontend/app/shared/assets/fonts/inter/Inter-Regular.ttf
apps/frontend/app/shared/assets/fonts/inter/Inter-SemiBold.ttf
apps/frontend/app/shared/constants/index.ts
apps/frontend/app/shared/constants/routes.ts
apps/frontend/app/shared/ui/Layout/Layout.scss
apps/frontend/app/shared/ui/Layout/index.tsx
apps/frontend/app/shared/ui/index.ts
apps/frontend/index.js
apps/frontend/index.ts
apps/frontend/package.json
apps/frontend/public/_headers
apps/frontend/public/apple-touch-icon.png
apps/frontend/public/favicon.ico
apps/frontend/public/index.html
apps/frontend/public/initiatives/advisor-environment.png
apps/frontend/public/initiatives/advisor-social.png
apps/frontend/public/initiatives/advisor-transport.png
apps/frontend/public/initiatives/akim.png
apps/frontend/public/initiatives/m1-bus-lane.png
apps/frontend/public/initiatives/m10-lighting-cameras.png
apps/frontend/public/initiatives/m11-safe-crossing.png
apps/frontend/public/initiatives/m12-citizen-platform.png
apps/frontend/public/initiatives/m13-utility-pipes.png
apps/frontend/public/initiatives/m14-utility-response.png
apps/frontend/public/initiatives/m2-smart-traffic-lights.png
apps/frontend/public/initiatives/m3-light-rail.png
apps/frontend/public/initiatives/m4-city-park.png
apps/frontend/public/initiatives/m5-clean-heating.png
apps/frontend/public/initiatives/m6-green-belt.png
apps/frontend/public/initiatives/m7-school-kindergarten.png
apps/frontend/public/initiatives/m8-health-clinic.png
apps/frontend/public/initiatives/m9-sports-court.png
apps/frontend/public/initiatives/optimized/advisor-environment.webp
apps/frontend/public/initiatives/optimized/advisor-social.webp
apps/frontend/public/initiatives/optimized/advisor-transport.webp
apps/frontend/public/initiatives/optimized/akim.webp
apps/frontend/public/initiatives/optimized/m1-bus-lane.webp
apps/frontend/public/initiatives/optimized/m10-lighting-cameras.webp
apps/frontend/public/initiatives/optimized/m11-safe-crossing.webp
apps/frontend/public/initiatives/optimized/m12-citizen-platform.webp
apps/frontend/public/initiatives/optimized/m13-utility-pipes.webp
apps/frontend/public/initiatives/optimized/m14-utility-response.webp
apps/frontend/public/initiatives/optimized/m2-smart-traffic-lights.webp
apps/frontend/public/initiatives/optimized/m3-light-rail.webp
apps/frontend/public/initiatives/optimized/m4-city-park.webp
apps/frontend/public/initiatives/optimized/m5-clean-heating.webp
apps/frontend/public/initiatives/optimized/m6-green-belt.webp
apps/frontend/public/initiatives/optimized/m7-school-kindergarten.webp
apps/frontend/public/initiatives/optimized/m8-health-clinic.webp
apps/frontend/public/initiatives/optimized/m9-sports-court.webp
apps/frontend/public/robots.txt
apps/frontend/public/uploads/apple-touch-icon.png
apps/frontend/tsconfig.json
apps/frontend/vercel.json
apps/frontend/webpack.config.cjs
apps/frontend/yarn.lock
build-plan.md
context/current-feature.md
context/decisions.md
context/findings.md
context/project-overview.md
context/review.md
current-prd.md
docs/PRD.md
docs/data-set.json
docs/data-set.md
docs/product-brief.md
docs/specs/001-city-scenario-feature.md
docs/specs/001-city-scenario-implementation.md
docs/submission.md
history/features/DOC-1-document-conversion.md
history/release-cloudflare.md
package.json
scripts/smoke.mjs
wrangler.jsonc
yarn.lock
Ветки (3)

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

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

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