AAYMA
RU
Войти

«Аким на 5 часов»

Веб-симулятор городских решений для Астаны: пользователь выбирает 5 из 14 мер по пяти районам в рамках бюджета 100.

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

О проекте

Команда: 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 и автоматические тесты.

Как работает решение

  1. Интерфейс получает исходные показатели, мероприятия и правила от backend.
  2. Пользователь выбирает пять мероприятий, указывая район для районных мер.
  3. Backend проверяет ограничения и рассчитывает результат.
  4. Интерфейс показывает бюджет, изменения показателей и вклад выбранных мер.
  5. Рассчитанный результат передаётся AI-модели для текстового объяснения.
  6. Пользователь сохраняет сценарий и открывает его через историю /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
BackendPython, FastAPI, Uvicorn, Pydantic, pydantic-settings
ХранениеJSON для исходных данных, SQLite для сценариев
AIOpenAI 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.pyHTTP 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

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:

ПолеРезультат
validtrue
budget.spent / budget.remaining95 / 5
baselineScore52.55768
score56.54307
scoreDeltaпримерно 3.98539
criticalCount0 (в исходном состоянии — 2)
weakestDistrictIdnura

Незначительные хвосты десятичных дробей обусловлены арифметикой 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.

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

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

luckyZharkyn 13ZhanaliBer 12Zharkyn 1

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

  • 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 комментариев

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

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