О проекте
Команда: Xarabis
Кейс HackAlem.ai: Спецтрек — Astana Innovations
Стадия: Доведённый продукт
Задача кейса: «Аким на 5 часов»: городской симулятор, распределяющий бюджет по 5 направлениям и 5 районам
Суть
AI-симулятор управления Астаной: детерминированный движок считает Astana Quality of Life Score, AI-советник разбирает план. Есть 3D-карта Астаны на MapLibre/OSM, превью проектов на Unity WebGL и отдельная песочница на Three.js.
Как устроено
Датасет кейса лежит в одном JSON (бюджет, 5 районов, 14 мер, синергии, несовместимости). Backend на FastAPI с чистой архитектурой проверяет план и считает Score = 0.7×среднее по городу + 0.3×худший район − число значений ниже 40, с учётом лага и синергий. Сценарии и история чата хранятся в SQLite/PostgreSQL. Отдельный ai_service получает доверенный контекст расчёта и отвечает через OpenAI Responses API по строгой JSON-схеме со SSE-стримингом; ключ хранится только в этом сервисе.
Ключевые решения
- детерминированный расчёт Score с лагом и синергиями
- OpenAI Responses API Structured Outputs
- SSE-стриминг
- MapLibre + OSM geojson
- Unity WebGL
- Three.js
- rate limiting AI-запросов
Метрики
Контрольный сценарий: Score 52.56 → 56.54 при бюджете 95/100, критических значений 2 → 0
Особенности
Три отдельных сервиса (движок, AI-советник, фронтенд), ключ модели изолирован в ai_service. Настоящие геоданные Астаны (здания, дороги, парки, реки) и Unity-превью проектов. Таблица, где каждому требованию ТЗ сопоставлена реализация.
Стек
Python · FastAPI · SQLAlchemy · Alembic · SQLite · PostgreSQL · Docker · React · TypeScript · Vite · MapLibre · Unity WebGL · Three.js
Модели
gpt-6-luna (OpenAI)
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-6744d423-xarabis.
README
README.mdАким на 5 часов — AI-симулятор управления городом
Пользователь получает бюджет 100 единиц и данные пяти районов Астаны. Он принимает ровно 5 решений из каталога 14 мероприятий, получает итоговый Astana Quality of Life Score и разбор плана от AI-советника. Все расчёты делает детерминированный Python-движок. AI ничего не считает: он объясняет уже рассчитанный результат.
Данные синтетические, взяты из датасета кейса. Карта и 3D-макет условные, это не цифровой двойник города.
Как закрыты требования кейса
| Требование ТЗ | Где реализовано |
|---|---|
| Единый виртуальный бюджет и исходные данные | Один файл backend/src/city_simulator/resources/simulation.v1.json: бюджет 100, 5 районов, 14 мер, синергии, несовместимости |
| 5 решений по 5 направлениям | Ровно 5 мер, не больше 2 из одного направления, без повторов; для районных мер обязателен район, для городских его быть не должно |
| Автоматический контроль превышения бюджета | Валидатор backend (budget_exceeded). Кнопка расчёта неактивна, пока план не проходит проверку |
| Расчёт Astana Quality of Life Score | Score = 0.7 × среднее по городу (с весами населения) + 0.3 × худший район − 1 × число значений ниже 40, с учётом лага и синергий (backend/.../domain/services.py) |
| AI-анализ решений: сильные стороны, риски, последствия, рекомендации | ai_service: модель получает от backend готовый расчёт и возвращает ответ по строгой JSON-схеме |
| Изменение набора решений меняет Score | Каждый расчёт сохраняется. История сценариев доступна на вкладке «Астана» |
Контрольная точка из кейса: без решений Score 52.56. Пример M7, M8, M10 в Нуре, M12 и M5 в Сарыарке стоит 95 и даёт 56.54, критических значений 2 → 0.
Как устроено
Браузер ──► frontend (React + Vite, :5173)
│ /api/v1/* (прокси, без ключей)
▼
backend (FastAPI, :8000) ── правила, расчёт Score, сценарии, история чата
│ доверенный контекст расчёта │
▼ ▼
ai_service (FastAPI, :8001) SQLite / PostgreSQL
│ ключ OpenAI хранится только здесь
▼
OpenAI Responses API (структурированный JSON, стриминг)
Единственная точка входа для браузера — backend. Ключ модели лежит только в ai_service/.env и не попадает во frontend.
Что установить
| Что | Версия | Проверка |
|---|---|---|
| Python | 3.12 (backend требует ≥ 3.11) | py -3.12 --version / python3.12 --version |
| Node.js | 22.16+ (проверено на 24) | node --version |
| Ключ OpenAI API | с доступом к модели из ai_service/.env.example | — |
| PostgreSQL | не обязателен: для локальной проверки хватает SQLite | — |
Быстрый запуск: 3 терминала, из корня репозитория
Команды даны для Windows PowerShell. Для Linux и macOS отличия указаны под каждым шагом.
Терминал 1 — backend (расчёт и правила)
cd backend
py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
$env:DATABASE_URL = "sqlite+aiosqlite:///./.venv/sandbox-local.db"
.\.venv\Scripts\python.exe -m city_simulator.init_local_db
.\.venv\Scripts\python.exe -m uvicorn city_simulator.main:app --host 127.0.0.1 --port 8000
Linux/macOS: python3.12 -m venv .venv, .venv/bin/python вместо .\.venv\Scripts\python.exe, export DATABASE_URL=... вместо $env:DATABASE_URL = ....
Проверка: http://127.0.0.1:8000/api/v1/health отвечает {"status":"ok"}, Swagger — http://127.0.0.1:8000/docs. Для этого режима файл backend/.env не нужен. Запуск с PostgreSQL и Docker описан в backend/README.md.
Терминал 2 — AI-советник
py -3.12 -m venv ai_service/.venv
& ./ai_service/.venv/Scripts/python.exe -m pip install -r ai_service/requirements-lock.txt
Copy-Item ai_service/.env.example ai_service/.env
notepad ai_service/.env
& ./ai_service/.venv/Scripts/python.exe -m uvicorn ai_service.app:app --host 127.0.0.1 --port 8001
В ai_service/.env вставьте ключ в OPENAI_API_KEY=. Модель уже указана (OPENAI_MODEL=gpt-6-luna), с ней команда проверяла сервис. Если у вашего API-проекта нет доступа к этой модели, укажите любую, которая поддерживает Responses API, Structured Outputs и стриминг. После изменения .env сервис нужно перезапустить.
Linux/macOS: python3.12 -m venv ai_service/.venv, ai_service/.venv/bin/python ..., cp ai_service/.env.example ai_service/.env.
Проверка: http://127.0.0.1:8001/health отвечает {"status":"ok"}. Этот эндпоинт не проверяет ключ: ключ проверяется первым вопросом советнику.
Терминал 3 — интерфейс
cd frontend
Copy-Item .env.example .env
npm ci
npm run dev
Linux/macOS: cp .env.example .env, дальше те же команды.
Откройте http://127.0.0.1:5173/.
Основной сценарий проверки
Приложение открывается на вкладке «Песочница». Это дополнительная учебная игра «Новый Берег» со своими правилами (бюджет 200, 12 кварталов), она не входит в расчёт кейса. Сценарий из ТЗ находится на вкладке «Астана» (или сразу по адресу http://127.0.0.1:5173/#astana):
- Вкладка «Астана» → «Попробовать пример». В план попадут 5 мер из примера кейса.
- «Сохранить план» → «Рассчитать результат».
- На экране результатов должно быть: Astana Quality of Life Score 56.54 (до плана 52.56), бюджет 95 / 100, критических показателей 2 → 0, изменения по пяти районам и вклад каждой меры.
- Откройте AI-советника. В ответе есть блоки «Сильные стороны», «Риски», «Возможные последствия», «Рекомендации». Числа в ответе совпадают с расчётом.
- Замените любую меру и пересчитайте: Score изменится. Попробуйте нарушить правило, например добавить M4 и M7 в один район. Расчёт не запустится, валидатор назовёт причину.
Если backend не запущен, вкладка «Астана» работает в явно отмеченном деморежиме. Там показан только пример из кейса (52.56 → 56.54), произвольные планы не считаются.
Переменные окружения
Все секреты и адреса берутся из .env рядом с сервисом. Шаблоны лежат в *.env.example, сами .env в Git не попадают. Переменные процесса имеют приоритет над файлом.
| Файл | Переменная | По умолчанию | Зачем |
|---|---|---|---|
ai_service/.env | OPENAI_API_KEY | — (обязательно) | Ключ модели; без него советник отвечает 503 ai_not_configured |
OPENAI_MODEL | gpt-6-luna | Модель с Responses API + Structured Outputs + стримингом | |
OPENAI_TIMEOUT_SECONDS | 45 | Предел ожидания ответа модели, до 120 | |
OPENAI_MAX_OUTPUT_TOKENS | 4000 | Лимит длины ответа, 256–16000 | |
backend/.env (необязателен) | DATABASE_URL | sqlite+aiosqlite:///./xarabis.db | База сценариев; для PostgreSQL см. шаблон |
AI_SERVICE_URL | http://127.0.0.1:8001 | Адрес AI-советника | |
CORS_ALLOWED_ORIGINS | http://localhost:5173,http://127.0.0.1:5173 | Откуда разрешены запросы браузера | |
AI_CHAT_REQUESTS_PER_MINUTE | 10 | Лимит вопросов советнику | |
frontend/.env | PYTHON_API_URL | http://127.0.0.1:8000 | Куда проксировать /api/v1 |
PORT, HOST | 3000, 127.0.0.1 | Только для npm run serve (production-сборка) |
backend/.env читается из текущей папки, поэтому backend запускайте из backend/.
Тесты
Ключ и сеть для тестов не нужны.
# backend: правила, формула, API, сценарии
cd backend; .\.venv\Scripts\python.exe -m pytest -q; cd ..
# AI-советник: контракт, схемы, стриминг, ошибки (модель подменяется)
& ./ai_service/.venv/Scripts/python.exe -m pytest ai_service/tests -q
# frontend: SSE, прокси, размещение проектов; затем проверка типов и сборка
cd frontend; npm test; npm run build; cd ..
Если не работает
| Симптом | Причина и решение |
|---|---|
ImportError: cannot import name 'StrEnum' | Python старше 3.11. Пересоздайте .venv через py -3.12 |
| На вкладке «Астана» деморежим | Не запущен backend на :8000 или в frontend/.env неверный PYTHON_API_URL |
Советник: ai_not_configured | В ai_service/.env пустой ключ или модель. Заполните и перезапустите сервис |
Советник: ai_unavailable | Нет доступа к модели, закончились кредиты или нет сети до api.openai.com. Проверьте OPENAI_MODEL |
Советник: ai_timeout | Модель не ответила вовремя. Увеличьте OPENAI_TIMEOUT_SECONDS (до 120) |
Ключ в .env «не применяется» | В терминале осталась переменная $env:OPENAI_API_KEY или $env:OPENAI_MODEL. Откройте новый терминал |
address already in use | Порт 8000, 8001 или 5173 занят. Остановите старый процесс или смените порт |
| Пустая 3D-сцена | Нужен браузер с WebGL. Открывайте через http://, а не file://; вид «Список районов» работает без WebGL |
Структура
| Папка | Что внутри | Подробно |
|---|---|---|
backend/ | FastAPI: датасет, валидатор, расчёт Score, сценарии, чат с советником, песочница | backend/README.md, контракт — frontend-integration.md |
ai_service/ | FastAPI: промпты, строгие схемы ответа, OpenAI Responses API, SSE | ai_service/README.md |
frontend/ | React + TypeScript: карта Астаны (MapLibre + OSM), Unity WebGL, песочница на Three.js | frontend/README.md |
Ограничения
- Данные районов и эффекты мер синтетические, из датасета кейса. Это не прогноз для реальной Астаны.
- AI не считает и не меняет Score. Цифры в его ответе берутся из расчёта backend. Проверку допустимости плана делает только валидатор.
- Карта, объекты проектов и движение машин — условная визуализация, а не транспортная или градостроительная модель.
- Локальный запуск рассчитан на
127.0.0.1. Для публичного сервера нужны аутентификация и лимиты на платные AI-запросы.
Авторы коммитов
История коммитов
- 2026-09-23 18:01docs: describe local startup and city visualizationshutovBro
- 2026-09-23 18:01Merge remote-tracking branch 'origin/main' into codex/astana-city-mapshutovBro
- 2026-09-23 18:00feat: Implement automatic analysis feature in the city simulatorXarabis
- 2026-09-23 18:00fix: update label for score layer to improve clarityXarabis
- 2026-09-23 17:58feat(frontend): animate Astana streets and refine map UXshutovBro
- 2026-09-23 17:42Update README.mdOlzhas Azirali
- 2026-09-23 17:41Merge branch 'main' of https://github.com/BAITC-Hacks/hack-6744d423-xarabisSultan
- 2026-09-23 17:40delete readme from backendSultan
Все коммиты (38)
- 2026-09-23 17:34feat: implement ScenarioConsultantPanel component and update test scriptXarabis
- 2026-09-23 17:33Merge branch 'main' of https://github.com/BAITC-Hacks/hack-6744d423-xarabisXarabis
- 2026-09-23 17:28Merge branch 'main' of https://github.com/BAITC-Hacks/hack-6744d423-xarabisXarabis
- 2026-09-23 17:28Merge remote-tracking branch 'origin/main' into codex/astana-city-mapshutovBro
- 2026-09-23 17:28feat: add report streaming functionality and validationXarabis
- 2026-09-23 17:25feat(frontend): build city-first decision workspace and Unity project previewsshutovBro
- 2026-09-23 17:04fixSultan
- 2026-09-23 16:59fixSultan
- 2026-09-23 16:46feat(frontend): integrate Astana geodata and AI consultant APIshutovBro
- 2026-09-23 16:37Merge remote-tracking branch 'origin/main' into codex/astana-city-mapshutovBro
- 2026-09-23 16:35Add interactive Astana 3D map and district boundariesshutovBro
- 2026-09-23 16:32feat(backend): integrate external AI consultantSultan
- 2026-09-23 16:16Merge frontend into mainshutovBro
- 2026-09-23 16:15feat(backend): complete frontend integration APISultan
- 2026-09-23 16:13Merge branch 'main' of https://github.com/BAITC-Hacks/hack-6744d423-xarabisXarabis
- 2026-09-23 16:13feat: Implement streaming response for chat endpoint with incremental answer deliveryXarabis
- 2026-09-23 16:12Add standalone frontend with Unity and city API integrationshutovBro
- 2026-09-23 15:55docs(backend): add frontend integration guideSultan
- 2026-09-23 15:40Merge branch 'main' of https://github.com/BAITC-Hacks/hack-6744d423-xarabisSultan
- 2026-09-23 15:40feat(backend): persist scenarios in PostgreSQLSultan
- 2026-09-23 15:24Merge branch 'main' of https://github.com/BAITC-Hacks/hack-6744d423-xarabisXarabis
- 2026-09-23 15:23feat: Add chat request and response examples, evaluation questions, and knowledge loading functionalityXarabis
- 2026-09-23 15:18feat(backend): complete deterministic simulation coreSultan
- 2026-09-23 14:45Add Unity Blender to project descriptionOlzhas Azirali
- 2026-09-23 14:44chore: update .gitignore to include additional file types and create ai_service/.gitignoreXarabis
- 2026-09-23 14:39feat(backend): add FastAPI city simulator foundationSultan
- 2026-09-23 14:03Add Akim to the README.mdOlzhas Azirali
- 2026-09-23 13:59Add location information to READMEOlzhas Azirali
- 2026-09-23 13:53impove gitignoreSultan
- 2026-09-23 13:44Первый гитигнорXarabis
Структура репозитория (186 файлов)
.gitignore README.md ai_service/.env.example ai_service/.gitignore ai_service/README.md ai_service/__init__.py ai_service/app.py ai_service/config.py ai_service/data/city.json ai_service/district_schemas.py ai_service/districts.py ai_service/errors.py ai_service/examples/chat-request.json ai_service/examples/chat-with-result.json ai_service/examples/evaluation.json ai_service/knowledge.py ai_service/prompts/consultant.txt ai_service/prompts/districts.txt ai_service/prompts/sandbox.txt ai_service/provider.py ai_service/requirements-dev.txt ai_service/requirements-lock.txt ai_service/requirements.txt ai_service/sandbox.py ai_service/schemas.py ai_service/streaming.py ai_service/tests/__init__.py ai_service/tests/test_api.py ai_service/tests/test_config.py ai_service/tests/test_contract.py ai_service/tests/test_districts.py ai_service/tests/test_examples.py ai_service/tests/test_knowledge.py ai_service/tests/test_sandbox.py ai_service/tests/test_streaming.py backend/.dockerignore backend/.env.example backend/.gitignore backend/Dockerfile backend/README.md backend/alembic.ini backend/compose.yml backend/docs/frontend-integration.md backend/migrations/env.py backend/migrations/script.py.mako backend/migrations/versions/20260923_0001_scenarios.py backend/migrations/versions/20260923_0002_chat.py backend/pyproject.toml backend/scripts/smoke.py backend/src/city_simulator/__init__.py backend/src/city_simulator/application/__init__.py backend/src/city_simulator/application/chat.py backend/src/city_simulator/application/scenarios.py backend/src/city_simulator/application/use_cases.py backend/src/city_simulator/core/__init__.py backend/src/city_simulator/core/config.py backend/src/city_simulator/core/logging.py backend/src/city_simulator/domain/__init__.py backend/src/city_simulator/domain/chat.py backend/src/city_simulator/domain/chat_ports.py backend/src/city_simulator/domain/district_sandbox.py backend/src/city_simulator/domain/entities.py backend/src/city_simulator/domain/enums.py backend/src/city_simulator/domain/exceptions.py backend/src/city_simulator/domain/ports.py backend/src/city_simulator/domain/sandbox.py backend/src/city_simulator/domain/scenario_ports.py backend/src/city_simulator/domain/scenarios.py backend/src/city_simulator/domain/services.py backend/src/city_simulator/infrastructure/__init__.py backend/src/city_simulator/infrastructure/chat_repository.py backend/src/city_simulator/infrastructure/consultant_client.py backend/src/city_simulator/infrastructure/database.py backend/src/city_simulator/infrastructure/models.py backend/src/city_simulator/infrastructure/repositories.py backend/src/city_simulator/infrastructure/result_codec.py backend/src/city_simulator/infrastructure/scenario_repository.py backend/src/city_simulator/init_local_db.py backend/src/city_simulator/main.py backend/src/city_simulator/presentation/__init__.py backend/src/city_simulator/presentation/chat_routes.py backend/src/city_simulator/presentation/chat_schemas.py backend/src/city_simulator/presentation/dependencies.py backend/src/city_simulator/presentation/district_sandbox_routes.py backend/src/city_simulator/presentation/middleware.py backend/src/city_simulator/presentation/routes.py backend/src/city_simulator/presentation/sandbox_chat_routes.py backend/src/city_simulator/presentation/sandbox_routes.py backend/src/city_simulator/presentation/scenario_routes.py backend/src/city_simulator/presentation/scenario_schemas.py backend/src/city_simulator/presentation/schemas.py backend/src/city_simulator/resources/__init__.py backend/src/city_simulator/resources/simulation.v1.json backend/tests/conftest.py backend/tests/fixtures/invalid_dataset.json backend/tests/test_api.py backend/tests/test_chat_api.py backend/tests/test_config.py backend/tests/test_consultant_client.py backend/tests/test_dataset.py backend/tests/test_district_sandbox_api.py backend/tests/test_init_local_db.py backend/tests/test_logging.py backend/tests/test_middleware.py backend/tests/test_persistence_api.py backend/tests/test_sandbox_api.py backend/tests/test_sandbox_chat_stream.py backend/tests/test_scenario_repository.py backend/tests/test_score_calculator.py backend/tests/test_validation.py frontend/.env.example frontend/.gitignore frontend/README.md frontend/docs/UNITY-BRIDGE.md frontend/docs/WORKSPACE-QA.md frontend/index.html frontend/package-lock.json frontend/package.json frontend/proxy.mjs frontend/public/data/astana/README.md frontend/public/data/astana/buildings.geojson frontend/public/data/astana/buildings.geojson.gz frontend/public/data/astana/districts.geojson frontend/public/data/astana/districts.geojson.gz frontend/public/data/astana/manifest.json frontend/public/data/astana/parks.geojson frontend/public/data/astana/parks.geojson.gz frontend/public/data/astana/qa.json frontend/public/data/astana/rivers.geojson frontend/public/data/astana/rivers.geojson.gz frontend/public/data/astana/roads.geojson frontend/public/data/astana/roads.geojson.gz frontend/public/unity/Build/unity.data.gz frontend/public/unity/Build/unity.framework.js.gz frontend/public/unity/Build/unity.loader.js frontend/public/unity/Build/unity.loader.js.gz frontend/public/unity/Build/unity.wasm.gz frontend/public/unity/FONT-LICENSE.txt frontend/public/unity/build-manifest.json frontend/public/unity/geo/astana-atlas.json frontend/public/unity/geo/astana-atlas.json.gz frontend/public/unity/geo/atlas-report.json frontend/scripts/check-project-previews.mjs frontend/serve.mjs frontend/src/App.tsx frontend/src/AstanaMap.tsx frontend/src/CityLifeLayer.ts frontend/src/ConsultantPanel.tsx frontend/src/DistrictCity.tsx frontend/src/DistrictSandbox.tsx frontend/src/Sandbox.tsx frontend/src/SandboxCity.tsx frontend/src/ScenarioConsultantPanel.tsx frontend/src/ScenarioLibrary.tsx frontend/src/UnityCityView.tsx frontend/src/Workspace.tsx frontend/src/automaticAnalysis.ts frontend/src/caseData.ts frontend/src/cityApi.ts frontend/src/cityLifeData.ts frontend/src/cityNetwork.ts frontend/src/cityNetworkDemo.ts frontend/src/consultant.ts frontend/src/districtApi.ts frontend/src/districts.css frontend/src/main.tsx frontend/src/offlineAstanaStyle.ts frontend/src/projectPreview.ts frontend/src/reportStream.ts frontend/src/roadGeometry.ts frontend/src/sandbox.css frontend/src/sandboxApi.ts frontend/src/styles.css frontend/src/traffic.ts frontend/src/useCitySimulator.ts frontend/src/vite-env.d.ts frontend/src/workspace.css frontend/tests/automaticAnalysis.test.mjs frontend/tests/proxy.test.mjs frontend/tests/roadGeometry.test.mjs frontend/tests/sandbox.test.mjs frontend/tests/stream.test.mjs frontend/tests/traffic.test.mjs frontend/tsconfig.json frontend/vite.config.ts patches/claude-readme-env-score.patch
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.