О проекте
Команда: Nomad Minds
Кейс HackAlem.ai: Спецтрек — Astana Innovations
Стадия: Рабочий MVP
Задача кейса: «Аким на 5 часов»: городской симулятор, распределяющий бюджет по 5 направлениям и 5 районам
Суть
Веб-симулятор городских решений для Астаны: пользователь выбирает 5 из 14 мер по пяти районам в рамках бюджета 100. Система считает Score, вклад мер по Шепли и даёт AI-объяснение результата.
Как устроено
Angular-фронт с картой Leaflet получает районы, меры и правила из FastAPI. Бэкенд проверяет ограничения (бюджет, направления, несовместимости) и детерминированно считает эффект по 8 кварталам с лагами и синергиями. Формула: Score = 0.7·AVG + 0.3·MIN − число показателей ниже 40. Готовый результат передаётся в gpt-4o-mini (Responses API, структурированный ответ через Pydantic), которая формулирует выводы, риски и рекомендации; при сбое работает локальный фолбэк. Сценарии сохраняются в SQLite.
Ключевые решения
- значения Шепли
- детерминированный расчёт по правилам
- structured output Pydantic
- OpenAI Responses API
Метрики
Эталонный сценарий: Score 56.54307 при базовом 52.55768 (Δ≈3.985), бюджет 95/100, критических показателей 0 (исходно 2)
Особенности
Роли чётко разделены: числа считает Python, LLM только объясняет. Для жюри подготовлен эталонный сценарий, ожидаемые значения которого закреплены в тестах бэкенда.
Стек
TypeScript · Angular 22 · RxJS · NG-ZORRO · Tailwind CSS · Leaflet · Python · FastAPI · Pydantic · SQLite · pytest · Vitest
Модели
gpt-4o-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-3154ac51-nomad-minds.
README
README.md«Аким на 5 часов» — Nomad Minds
Хакатонный симулятор городских решений на примере Астаны. Пользователь выбирает пять мероприятий при бюджете 100 условных единиц и оценивает изменение качества жизни районов.
Проект предназначен для участников образовательных и городских обсуждений: он помогает исследовать компромиссы между транспортом, экологией, социальной сферой, безопасностью и городскими сервисами. Это демонстрационная модель, а не официальный прогноз муниципального развития.
Что реализовано
- Карта города, выбор района и каталог из 14 мероприятий.
- Расчёт для пяти районов: Есиль, Алматы, Сарыарка, Байконур и Нура.
- Проверка бюджета, количества решений, ограничений по направлениям, повторов, допустимых районов и несовместимости мероприятий.
- Детерминированный расчёт на горизонте восьми кварталов с учётом задержек эффекта, синергий и диапазона показателей 0–100.
- Итоговый Score, изменения показателей, слабейший район и количество критических показателей.
- Распределение прироста Score между мероприятиями методом Шепли.
- AI-анализ: вывод, сильные стороны, риски, компромиссы и рекомендации.
- Локальное резервное объяснение при отсутствии ключа или ошибке OpenAI.
- Сохранение именованных сценариев в SQLite и просмотр истории результатов.
- REST API, Swagger и автоматические тесты.
Как работает решение
- Интерфейс получает исходные показатели, мероприятия и правила от backend.
- Пользователь выбирает пять мероприятий, указывая район для районных мер.
- Backend проверяет ограничения и рассчитывает результат.
- Интерфейс показывает бюджет, изменения показателей и вклад выбранных мер.
- Рассчитанный результат передаётся AI-модели для текстового объяснения.
- Пользователь сохраняет сценарий и открывает его через историю
/results.
Числа рассчитывает Python; OpenAI объясняет готовый результат. Для текущих правил:
Score = 0.7 × средний балл районов с учётом долей населения
+ 0.3 × балл слабейшего района
− количество показателей ниже 40
Балл района — взвешенная сумма десяти показателей. Веса и ограничения заданы в rules.json, алгоритм — в scoring.py.
Технологии
| Компонент | Технологии |
|---|---|
| Интерфейс | TypeScript, Angular 22, RxJS, NG-ZORRO, Tailwind CSS, SCSS |
| Карта | Leaflet, GeoJSON, OpenStreetMap |
| Backend | Python, FastAPI, Uvicorn, Pydantic, pydantic-settings |
| Хранение | JSON для исходных данных, SQLite для сценариев |
| AI | OpenAI Python SDK, Responses API, структурированный ответ Pydantic; модель по умолчанию gpt-4o-mini |
| Проверки | pytest, HTTPX, Angular Test Runner / Vitest |
Версии зависимостей находятся в backend/requirements.txt, frontend/package.json и frontend/package-lock.json.
Архитектура
Браузер: Angular + Leaflet
│ HTTP /api/v1
▼
FastAPI
├── dataset_repository.py → JSON районов, мероприятий и правил
├── scoring.py → валидация и расчёт
├── scenario_repository.py → SQLite
└── ai_adapter.py → OpenAI / локальное объяснение
| Путь | Назначение |
|---|---|
backend/api.py | HTTP endpoints |
backend/schemas.py | Модели запросов |
backend/settings.py | Настройки окружения |
backend/app/data/ | Районы, мероприятия и правила |
backend/tests/ | Тесты backend |
frontend/src/app/ | Интерфейс сценария, история, API-клиент |
frontend/public/config.js | Публичный адрес API |
frontend/public/maps/ | Границы районов и описание источников |
frontend/proxy.conf.json | Прокси локального сервера разработки |
Установка и запуск
Требования
- Git и доступ к репозиторию, если он приватный.
- Python 3.11 или новее.
- Node.js, совместимый с Angular из lock-файла:
^22.22.3,^24.15.0или>=26.0.0. - npm; в
package.jsonуказан[email protected]. - Интернет для установки зависимостей, подложки карты и запросов OpenAI.
- Собственный OpenAI API-ключ с доступом к выбранной модели — для AI-анализа. Без ключа работают расчёты и резервное объяснение.
1. Получить проект
git clone https://github.com/BAITC-Hacks/hack-3154ac51-nomad-minds.git
cd hack-3154ac51-nomad-minds
Все команды backend выполняются из корня репозитория.
2. Подготовить backend
Windows PowerShell:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r backend/requirements.txt
if (!(Test-Path .env)) { Copy-Item .env.example .env }
Если PowerShell запрещает активацию, используйте .\.venv\Scripts\python.exe вместо python в последующих командах.
Linux / macOS:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r backend/requirements.txt
test -f .env || cp .env.example .env
В .env задайте параметры. Для описанного запуска используется порт 8000:
HOST=127.0.0.1
PORT=8000
CORS_ORIGINS=http://localhost:4200,http://127.0.0.1:4200
DATABASE_PATH=backend/app.db
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o-mini
OPENAI_TIMEOUT_SECONDS=30
Для OpenAI заполните OPENAI_API_KEY своим ключом. .env не коммитьте; в .env.example оставляйте ключ пустым. Ключ не должен попадать в публичную конфигурацию frontend.
Запустите backend:
python backend/api.py
- Swagger: http://127.0.0.1:8000/docs
- Проверка сервиса: http://127.0.0.1:8000/api/v1/health
SQLite-файл и таблица сценариев создаются автоматически при обращении к хранилищу. Отдельный сервер БД не нужен. При изменении DATABASE_PATH родительская папка должна существовать.
3. Запустить frontend
В отдельном терминале из корня репозитория:
cd frontend
npm ci
npm start
Откройте http://localhost:4200. Оба терминала должны оставаться запущенными. Остановка — Ctrl+C.
Frontend проксирует /api/** на http://127.0.0.1:8000. Если backend нужен на 8085, измените PORT=8085 в .env и target в frontend/proxy.conf.json на http://127.0.0.1:8085, затем перезапустите обе части. Swagger тогда доступен на http://127.0.0.1:8085/docs.
Как проверить решение
Повторяемый сценарий для жюри
На странице /scenario выберите:
| Мера | Район |
|---|---|
| M7 — школа и детсад | Нура |
| M8 — поликлиника | Нура |
| M10 — освещение и камеры | Нура |
| M12 — платформа обращений | Весь город |
| M5 — перевод частного сектора на чистое топливо | Сарыарка |
Выполните расчёт, просмотрите объяснение, сохраните сценарий с именем и откройте его в истории /results.
Тот же расчёт проверяется через Swagger: POST /api/v1/scenarios/calculate → Try it out → передайте:
{
"decisions": [
{"measureId": "M7", "districtId": "nura"},
{"measureId": "M8", "districtId": "nura"},
{"measureId": "M10", "districtId": "nura"},
{"measureId": "M12"},
{"measureId": "M5", "districtId": "saryarka"}
]
}
Для данных версии 1.0.0 ожидаются значения, закреплённые в тестах backend:
| Поле | Результат |
|---|---|
valid | true |
budget.spent / budget.remaining | 95 / 5 |
baselineScore | 52.55768 |
score | 56.54307 |
scoreDelta | примерно 3.98539 |
criticalCount | 0 (в исходном состоянии — 2) |
weakestDistrictId | nura |
Незначительные хвосты десятичных дробей обусловлены арифметикой floating-point. Для проверки ошибочного сценария удалите одну меру: расчёт вернёт valid: false и описание нарушения.
Для отдельной проверки AI передайте полный ответ расчёта в POST /api/v1/scenarios/analyze как значение поля result. source: "openai" означает ответ модели; source: "fallback" — локальное объяснение. Поле aiProvider в health отражает наличие ключа, а не проверяет доступность OpenAI.
Автоматические проверки
Из корня репозитория в Python-окружении проекта:
python -m pytest backend/tests -q
python -m pip check
Проверяются формулы, ограничения, синергии, API, хранение сценариев, настройки и AI-адаптер. Тесты AI используют подменённый клиент и не подтверждают работоспособность настоящего ключа.
Из папки frontend:
npm test -- --watch=false
npm run build
Результат сборки — frontend/dist/frontend/browser относительно корня проекта.
API
| Метод и путь | Назначение |
|---|---|
GET /api/v1/health | Состояние сервиса и настройка AI |
GET /api/v1/dataset | Районы, мероприятия, правила, исходный Score |
POST /api/v1/scenarios/validate | Проверка в том числе неполного набора решений |
POST /api/v1/scenarios/calculate | Расчёт полного сценария |
POST /api/v1/scenarios/analyze | Объяснение рассчитанного результата |
POST /api/v1/scenarios | Сохранение: name и decisions; сервер пересчитывает результат |
GET /api/v1/scenarios | История сохранённых сценариев |
Данные и интеграции
- Модель города:
backend/app/data/districts.json,measures.jsonиrules.json. Это фиксированные входные данные симулятора, а не онлайн-статистика городских служб. Бюджет и цены выражены в условных единицах. - География: локальный снимок границ OpenStreetMap в GeoJSON. Источники, идентификаторы отношений и лицензия ODbL описаны в документации карты.
- Подложка карты: тайлы
https://tile.openstreetmap.org/{z}/{x}/{y}.png, загружаемые браузером; требуется интернет. - OpenAI: backend отправляет JSON рассчитанного сценария во внешний API. Модель задаётся
OPENAI_MODEL. Текст ответа может отличаться между запросами. - SQLite: хранит имя, решения, результат, версию данных и дату создания сценария.
Ограничения текущей версии
- Модель охватывает пять районов. Сарайшык отображается на карте, но не участвует в расчёте: в наборе данных нет его показателей и доли населения.
- Эффекты и веса заданы фиксированными правилами; модель не обучается на реальных городских данных и не подтверждает причинный эффект мероприятий в реальности.
- Автоматический поиск оптимального набора мероприятий не реализован.
- Страницы
/comparisonи/project-infoпока являются заглушками. - В API нет авторизации, разделения истории по пользователям и ограничения частоты запросов. Публичное использование требует дополнительной настройки защиты.
- AI может ошибаться; рекомендации нужно проверять. Резервный ответ проще, сформирован на английском и содержит пустой список рекомендаций.
- Endpoint анализа принимает готовый результат от клиента и не пересчитывает его; для проверки используйте результат
/calculate. - Для сохранности истории нужен постоянный SQLite-файл и резервное копирование. Удаление файла или временное хранилище сервера приводит к потере сценариев.
Развёрнутая версия
В текущей конфигурации репозитория публичная ссылка на работающий экземпляр не указана. Для демонстрации используйте локальный запуск выше.
Для размещения frontend адрес backend задаётся в frontend/public/config.js (или config.js внутри готовой сборки) полем apiBaseUrl, включая /api/v1. Значение по умолчанию /api/v1 требует прокси на том же домене. При разных доменах настройте CORS_ORIGINS backend. Подробности — в frontend/README.md.
Авторы коммитов
История коммитов
- 2026-09-23 17:50Add images to README for visualizationZharkyn
- 2026-09-23 17:46commit 13luckyZharkyn
- 2026-09-23 17:39commit 11luckyZharkyn
- 2026-09-23 17:32Document project setup and demo in RussianZhanaliBer
- 2026-09-23 17:22commit 9luckyZharkyn
- 2026-09-23 17:22commit 10luckyZharkyn
- 2026-09-23 17:16Remove exposed configuration and local databaseZhanaliBer
- 2026-09-23 17:00commit 8luckyZharkyn
Все коммиты (26)
- 2026-09-23 16:43commit 7luckyZharkyn
- 2026-09-23 16:27commit 6luckyZharkyn
- 2026-09-23 16:07Add OpenAI scenario analysisZhanaliBer
- 2026-09-23 15:50commit 5luckyZharkyn
- 2026-09-23 15:29commit 4luckyZharkyn
- 2026-09-23 15:28Prepare backend for deploymentZhanaliBer
- 2026-09-23 15:06Add scenario persistenceZhanaliBer
- 2026-09-23 14:52commit 2luckyZharkyn
- 2026-09-23 14:52commit 3luckyZharkyn
- 2026-09-23 14:51Implement scenario scoring and APIZhanaliBer
- 2026-09-23 14:26Add dataset repository and implementation planZhanaliBer
- 2026-09-23 14:22Add simulation datasets and rulesZhanaliBer
- 2026-09-23 14:15Add districts and measures datasetsZhanaliBer
- 2026-09-23 14:12Add districts datasetZhanaliBer
- 2026-09-23 13:55Add district database dataZhanaliBer
- 2026-09-23 13:51initial frontendluckyZharkyn
- 2026-09-23 11:08Add backend dependency checkerZhanaliBer
- 2026-09-23 10:29test 1luckyZharkyn
Структура репозитория (102 файлов)
.env.example .gitignore IMPLEMENTATION_PLAN.md README.md backend/ai_adapter.py backend/api.py backend/app/data/districts.json backend/app/data/measures.json backend/app/data/rules.json backend/dataset_repository.py backend/requirements.txt backend/scenario_repository.py backend/schemas.py backend/scoring.py backend/settings.py backend/tests/test_ai_adapter.py backend/tests/test_api.py backend/tests/test_scenario_repository.py backend/tests/test_scoring.py backend/tests/test_settings.py frontend/.editorconfig frontend/.gitignore frontend/.prettierrc frontend/README.md frontend/angular.json frontend/codex.md frontend/package-lock.json frontend/package.json frontend/proxy.conf.json frontend/public/config.js frontend/public/favicon.ico frontend/public/favicon.svg frontend/public/maps/README.md frontend/public/maps/astana-districts.geojson frontend/src/app/app.config.ts frontend/src/app/app.html frontend/src/app/app.routes.ts frontend/src/app/app.scss frontend/src/app/app.spec.ts frontend/src/app/app.ts frontend/src/app/core/api/models/city-presentation.ts frontend/src/app/core/api/models/scenario-api.models.ts frontend/src/app/core/api/services/scenario-api.service.spec.ts frontend/src/app/core/api/services/scenario-api.service.ts frontend/src/app/core/api/testing/scenario-api.fixtures.ts frontend/src/app/core/api/utils/scenario-api.mapper.spec.ts frontend/src/app/core/api/utils/scenario-api.mapper.ts frontend/src/app/core/config/api.config.ts frontend/src/app/core/data/mock-city.data.ts frontend/src/app/core/models/city.models.ts frontend/src/app/core/services/app-alert.service.spec.ts frontend/src/app/core/services/app-alert.service.ts frontend/src/app/core/services/scenario-storage.service.ts frontend/src/app/features/history/pages/history-page/history-page.html frontend/src/app/features/history/pages/history-page/history-page.scss frontend/src/app/features/history/pages/history-page/history-page.spec.ts frontend/src/app/features/history/pages/history-page/history-page.ts frontend/src/app/features/scenario/components/ai-analysis/ai-analysis.html frontend/src/app/features/scenario/components/ai-analysis/ai-analysis.scss frontend/src/app/features/scenario/components/ai-analysis/ai-analysis.ts frontend/src/app/features/scenario/components/app-header/app-header.html frontend/src/app/features/scenario/components/app-header/app-header.scss frontend/src/app/features/scenario/components/app-header/app-header.ts frontend/src/app/features/scenario/components/city-map/city-map.html frontend/src/app/features/scenario/components/city-map/city-map.scss frontend/src/app/features/scenario/components/city-map/city-map.ts frontend/src/app/features/scenario/components/district-list/district-list.html frontend/src/app/features/scenario/components/district-list/district-list.scss frontend/src/app/features/scenario/components/district-list/district-list.ts frontend/src/app/features/scenario/components/measure-catalog/measure-catalog.html frontend/src/app/features/scenario/components/measure-catalog/measure-catalog.scss frontend/src/app/features/scenario/components/measure-catalog/measure-catalog.spec.ts frontend/src/app/features/scenario/components/measure-catalog/measure-catalog.ts frontend/src/app/features/scenario/components/scenario-results/scenario-results.html frontend/src/app/features/scenario/components/scenario-results/scenario-results.scss frontend/src/app/features/scenario/components/scenario-results/scenario-results.ts frontend/src/app/features/scenario/components/selected-decisions/selected-decisions.html frontend/src/app/features/scenario/components/selected-decisions/selected-decisions.scss frontend/src/app/features/scenario/components/selected-decisions/selected-decisions.ts frontend/src/app/features/scenario/pages/scenario-page/scenario-page.html frontend/src/app/features/scenario/pages/scenario-page/scenario-page.scss frontend/src/app/features/scenario/pages/scenario-page/scenario-page.ts frontend/src/app/features/scenario/pages/scenario-results-page/scenario-results-page.html frontend/src/app/features/scenario/pages/scenario-results-page/scenario-results-page.scss frontend/src/app/features/scenario/pages/scenario-results-page/scenario-results-page.spec.ts frontend/src/app/features/scenario/pages/scenario-results-page/scenario-results-page.ts frontend/src/app/features/scenario/services/scenario-calculator.service.spec.ts frontend/src/app/features/scenario/services/scenario-calculator.service.ts frontend/src/app/features/scenario/services/scenario-store.service.spec.ts frontend/src/app/features/scenario/services/scenario-store.service.ts frontend/src/app/shared/components/app-alerts/app-alerts.html frontend/src/app/shared/components/app-alerts/app-alerts.scss frontend/src/app/shared/components/app-alerts/app-alerts.ts frontend/src/app/shared/pages/placeholder-page/placeholder-page.html frontend/src/app/shared/pages/placeholder-page/placeholder-page.scss frontend/src/app/shared/pages/placeholder-page/placeholder-page.ts frontend/src/index.html frontend/src/main.ts frontend/src/styles.scss frontend/tsconfig.app.json frontend/tsconfig.json frontend/tsconfig.spec.json
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.