О проекте
Команда: 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 | Порт backend | 7000 |
NODE_ENV | Режим backend | development |
.env игнорируется Git. Шаблонные auth/blog/media-маршруты отключены: JWT, SMTP и отдельные учётные записи для симулятора не нужны. Если меняете PORT, для разработки потребуется согласовать target прокси в apps/frontend/webpack.config.cjs; в режиме yarn serve достаточно открыть новый порт.
Проверка основного сценария
- Откройте «Решения» в боковой навигации и нажмите «Загрузить демо-набор» в панели «План города». Выбраны M7/Нура, M8/Нура, M10/Нура, M12/весь город, M5/Сарыарка. Альтернативно нажимайте на район на условной карте и добавляйте меры из каталога справа, затем при необходимости уточняйте район в плане. Можно закрыть панель, чтобы увидеть подсветку: три районные меры в Нуре, одну в Сарыарке и пять отметок городской M12. Для расчёта снова откройте «Решения» или нажмите кнопку ▶ вверху. Стоимость M12 учитывается один раз.
- Нажмите «Рассчитать результат». Стоимость 95, остаток 5, исходный Score 52.56, новый Score 56.54 (до округления 56.5431), срабатывает синергия M10 + M12. Откроется AI-отчёт; его можно закрыть крестиком или Escape. Показатели направлений внизу — средние арифметические значений направления по районам, отдельно от взвешенного итогового Score.
- При корректном
OPENAI_API_KEYдождитесь AI-объяснения сильных сторон и рисков. Без него интерфейс покажет сообщение об отсутствии конфигурации рядом с рассчитанными числами. - Уберите одну меру из плана и повторите расчёт: получите сообщение «Выберите ровно 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.
Авторы коммитов
История коммитов
- 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
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.