AAYMA
RU
Войти

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

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

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

О проекте

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

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

Суть

Симулятор городских решений: пять районов Астаны, каталог из 14 инициатив, бюджет 100, ровно 5 решений. Расчёт на 8 кварталов со Score, критическими показателями и синергиями, текст анализа пишет OpenAI.

Как устроено

React-фронтенд собирает сценарий и отправляет его в FastAPI (/api/simulate). Валидатор проверяет бюджет, число мер, категории и несовместимости. Симуляция умножает эффект меры на (8−lag)/8, добавляет синергии и ограничивает показатели диапазоном 0–100. Score считается как взвешенный индекс районов по долям населения. Факты от backend передаются в OpenAI Responses API со Structured Output. Модель пишет только анализ (сильные стороны, риски, компромиссы) и не меняет числа; при ошибке возвращается резервный анализ.

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

  • детерминированная симуляция с лагом и синергиями
  • валидация правил сценария
  • OpenAI Responses API Structured Output
  • интерактивная SVG-карта районов

Особенности

Числа считает только backend, а LLM получает готовые факты и проверяется при форматировании (analysis_facts, analysis_formatter). Симуляция работает и без ключа OpenAI.

Стек

Python · FastAPI · Pydantic · React 18 · Vite · OpenAI SDK

Модели

gpt-4o-mini


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

README

README.md

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

Учебный симулятор управления городом от команды East Vision. Пользователь выбирает пять инициатив в рамках бюджета и сравнивает их влияние на районы Астаны. Backend рассчитывает показатели и рейтинг качества жизни, а OpenAI помогает сформулировать анализ.

Данные синтетические. Проект предназначен для хакатона и не прогнозирует реальные последствия городских решений. Числовая симуляция работает и без ключа OpenAI.

Скриншоты

Обзор города: схема пяти районов, исходный рейтинг и бюджет.

Обзор HackAlem: карта районов Астаны, Score 52.56 и бюджет 100

Пять решений в рамках бюджетаРезультат симуляции
Сценарий: пять инициатив, потрачено 81 из 100 единиц бюджетаРезультат: Score 54, прирост 1.44, две синергии и два критических показателя

Реальные экраны контрольного сценария из раздела демонстрации. Нажмите на изображение, чтобы открыть его в полном размере.

Основные возможности

  • Пять районов: Есиль, Алматы, Сарыарка, Байконур и Нура.
  • Каталог из 14 инициатив: транспорт, экология, социальная сфера, безопасность и городские сервисы.
  • Выбор районных и общегородских мер, контроль бюджета и совместимости.
  • Интерактивная схема города с режимами «Сейчас» и «С решениями».
  • Расчёт на 8 кварталов: изменения показателей, Score, критические значения и синергии.
  • Анализ результата: вывод, сильные стороны, риски, компромиссы и рекомендации.

Карта условная, не географическая. «Алматы» здесь означает район Астаны. Пятичасового таймера в приложении нет.

Стек и архитектура

ЧастьТехнологии
FrontendReact 18, JavaScript/JSX, Vite 6, CSS, SVG, Fetch API
BackendPython, FastAPI 0.115.6, Uvicorn 0.32.1, Pydantic 2.10.3
AIOpenAI Python SDK >=3.14.1,<4.0.0, Responses API, Structured Output
Настройки и тестыpython-dotenv, unittest, FastAPI TestClient
Пользователь -> React -> FastAPI
                          |
                          +-> Валидация -> Симуляция -> Score
                          |
                          +-> Факты backend -> OpenAI -> Проверка анализа
                          |
                    JSON-ответ -> Экран результатов

Frontend собирает сценарий и показывает результат. Backend отвечает за правила, расчёты и обращения к OpenAI. AI не рассчитывает Score и не меняет числовые результаты.

Базы данных нет: исходные данные находятся в Python-коде, выбранный сценарий хранится в памяти React. Каждый расчёт начинается с исходного состояния.

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

Требования

  • Git, Node.js 22.x и npm.
  • Python 3.10+ по синтаксису кода; backend-тесты проверены на 3.13.
  • pip и venv; интернет для установки зависимостей.
  • Ключ OpenAI и доступ к сервису нужны только для AI-анализа.

Отдельная версия Node в проекте не закреплена; 22.x подходит требованиям Vite. Полная матрица совместимости Python не задана.

1. Клонирование

git clone https://github.com/BAITC-Hacks/hack-6712142d-east-vision.git Hackalem
cd Hackalem

Если репозиторий уже скачан, откройте его корень. Все следующие пути указаны относительно папки Hackalem.

2. Backend: терминал 1

Windows PowerShell, из корня проекта:

cd backend
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt

Linux/macOS, из корня проекта:

cd backend
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt

Для AI настройте backend/.env по следующему разделу; без AI файл необязателен. Затем из backend запустите:

python -m uvicorn main:app --reload --host 127.0.0.1 --port 8000

Запускайте именно из backend: текущие импорты рассчитаны на эту рабочую папку. В новом терминале повторно активируйте окружение; переустанавливать пакеты каждый раз не нужно.

3. Frontend: терминал 2

Из корня проекта:

cd frontend
npm install
npm run dev -- --host 127.0.0.1 --port 5173

Откройте http://127.0.0.1:5173. Если порт занят, Vite может выбрать следующий: используйте адрес из терминала. Даже если node_modules уже есть после клонирования, выполните npm install.

Оба сервера должны работать одновременно. Для остановки используйте Ctrl+C. Без backend интерфейс показывает демоданные, но расчёт отключён.

Адреса

АдресНазначение
http://127.0.0.1:5173Приложение
http://127.0.0.1:8000/api/healthПроверка backend
http://127.0.0.1:8000/docsSwagger UI: документация и отправка запросов
http://127.0.0.1:8000/redocДокументация ReDoc
http://127.0.0.1:8000/openapi.jsonПолная схема API

На http://127.0.0.1:8000/ нет главной страницы: ответ 404 на этом пути ожидаем.

Настройка .env

Backend: backend/.env

Файла .env.example сейчас нет. Минимальный пример по реальным настройкам кода:

# Оставьте пустым для запуска без AI; для AI укажите свой ключ локально.
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o-mini
ПеременнаяНазначение
OPENAI_API_KEYНеобязательный для симуляции секретный ключ OpenAI
OPENAI_MODELМодель; при пустом или отсутствующем значении используется gpt-4o-mini

Файл должен называться .env, а не .env.txt. Backend читает его рядом с config.py, независимо от расположения других env-файлов.

Переменные процесса имеют приоритет над файлом, даже если пустые. Поэтому старое значение в терминале или настройках запуска IDE может перекрыть ключ из .env. После изменения окружения процесса перезапустите backend.

Не коммитьте ключ и не передавайте его во frontend. .env исключён через .gitignore, но это не защищает уже закоммиченные секреты.

Frontend: frontend/.env.local

Необязательная настройка для другого адреса backend:

VITE_API_URL=http://127.0.0.1:8000

Это также адрес по умолчанию в src/api/api.js. Указывайте базовый URL без /api: пути добавляет API-клиент. После изменения перезапустите Vite; собранную версию нужно пересобрать.

Переменные VITE_* доступны браузеру. Секреты в них хранить нельзя.

API

Все прикладные запросы и ответы используют JSON. Авторизации, загрузки изображений и multipart/form-data нет.

МетодEndpointВходОтвет
GET/api/healthБез тела{"status":"ok"}
GET/api/dataБез телаdistricts, indicators, initiatives, budget
POST/api/simulateМассив decisionsРасчёт сценария и ai_analysis

/api/data возвращает 5 районов, 10 показателей, 14 инициатив и бюджет 100. У района есть population_share и indicators; у инициативы: id, category, name, type, cost, lag, effects.

Пример POST /api/simulate

В /docs откройте endpoint, нажмите Try it out, вставьте JSON и нажмите Execute. Для программного запроса задайте Content-Type: application/json.

{
  "decisions": [
    {"initiative_id": "M1", "district": "Алматы"},
    {"initiative_id": "M2", "district": null},
    {"initiative_id": "M4", "district": "Сарыарка"},
    {"initiative_id": "M10", "district": "Байконур"},
    {"initiative_id": "M12", "district": null}
  ]
}

Для районной меры требуется точное название района из /api/data. Для городской меры district равен null или отсутствует.

Сокращённый ответ без AI; остальные поля описаны ниже:

{
  "valid": true,
  "used_budget": 81,
  "remaining_budget": 19,
  "baseline_score": 52.56,
  "final_score": 54.0,
  "score_delta": 1.44,
  "critical_indicators": [
    {"district": "Нура", "indicator": "S1", "value": 38.0},
    {"district": "Нура", "indicator": "S2", "value": 35.0}
  ],
  "ai_analysis": {
    "summary": "AI-анализ временно недоступен. Числовой результат симуляции рассчитан успешно.",
    "strengths": [],
    "risks": [],
    "tradeoffs": [],
    "recommendations": []
  }
}

Полный ответ также содержит decisions, final_districts, changes с before/after/delta, applied_synergies, initiative_contributions, score и ai_summary. Последнее поле дублирует ai_analysis.summary. Точные схемы: backend/models.py и /docs.

Ошибки

СтатусПричина
400Нарушены правила сценария: бюджет, количество, повторы, районы или совместимость
422Некорректный JSON, отсутствующие поля или неверные типы
404 / 405Неверный путь / HTTP-метод
500Внутренняя ошибка; подробности в терминале backend

Например, {"decisions":[]} возвращает 400 с {"detail":"Нужно выбрать ровно 5 решений."}. При неверной структуре detail содержит список ошибок проверки.

Ошибка OpenAI не отменяет допустимый расчёт: сервер возвращает 200 с резервным анализом. Недоступный сервер вообще не возвращает HTTP-ответ.

Рейтинг и основная логика

Правила выбора: ровно 5 разных инициатив, стоимость не выше 100, не более 2 мер одной категории. M1 и M3 несовместимы глобально; M4/M7 и M5/M13 нельзя выбирать в одном районе.

КодыПоказатели
T1 / T2Разгрузка дорог / доступность общественного транспорта
E1 / E2Озеленение / качество воздуха
S1 / S2Школы и детсады / поликлиники
B1 / B2Безопасность улиц / безопасность дорожного движения
C1 / C2Надёжность ЖКХ / скорость решения обращений
  1. Эффект меры умножается на (8 - lag) / 8, где lag задаётся в кварталах.
  2. Районная мера применяется к одному району, городская ко всем.
  3. Добавляются синергии: M1+M2 дают T1 +2 в районе M1; M10+M12 дают B1 +2 в районе M10; M5+M6 дают E2 +2 в районе M5.
  4. Показатели ограничиваются диапазоном 0–100. Значение строго ниже 40 считается критическим.
Индекс района = сумма(показатель * вес)
d_avg = сумма(индекс района * доля населения района)
n_crit = число показателей ниже 40 во всех районах
Score = 0.7 * d_avg + 0.3 * минимальный индекс района - n_crit

Данные и веса находятся в data.py, расчёты в simulation.py и score.py. Формула использует неокруглённые значения; отображаемый Score округляется до двух знаков. Остаток бюджета бонуса не даёт.

initiative_contributions показывает прямой эффект мер с учётом задержки. Полная delta также учитывает другие меры, синергии и ограничение диапазона.

Как работает AI

scenario_service.py вызывает ai_service.py после расчёта. analysis_facts.py программно собирает изменение Score, три крупнейших улучшения, все критические показатели, отрицательные изменения, синергии, незатронутые проблемы и рекомендации из каталога.

OpenAI получает только эти факты и возвращает структуру AIAnalysis. Таймаут SDK составляет 15 секунд, автоматические повторы отключены.

После успешного ответа analysis_formatter.py проверяет вывод и заново формирует четыре списка анализа из фактов backend, максимум по три пункта. Все критические показатели объединяются в пункт рисков. Нулевая delta сама по себе не считается преимуществом или риском, остаток бюджета является информацией.

Без ключа или при ошибке AI возвращается резервный текст и пустые списки анализа. Числа и отдельный список critical_indicators сохраняются. Причину ошибки OpenAI ищите в backend-логе.

Демонстрационный сценарий для хакатона

  1. Покажите исходные показатели Нуры: S1=38 и S2=35.
  2. В каталоге соберите набор из примера API:
IDМераРайонСтоимость
M1Выделенные полосы для автобусовАлматы18
M2Умные светофорыВесь город22
M4Парк / скверСарыарка15
M10Освещение и камерыБайконур12
M12Единая цифровая платформа обращенийВесь город14
  1. Нажмите «Запустить симуляцию»: бюджет 81/100, остаток 19, Score 52.56 → 54.00 (+1.44).
  2. Покажите две синергии и то, что S1=38 и S2=35 в Нуре остались критическими.
  3. При успешном AI-анализе обе проблемы присутствуют в рисках, а рекомендации содержат M7 «Школа + детсад» и M8 «Центр семейного здоровья / поликлиника» для Нуры.

Этот пример иллюстрирует улучшение общего результата без решения всех проблем. Он не является оптимальным набором мер. Изменение выбора сбрасывает предыдущий результат.

Структура проекта

Hackalem/
|-- README.md
|-- backend/
|   |-- main.py, routes.py             # Приложение, CORS и endpoint'ы
|   |-- config.py, models.py           # Настройки и схемы API
|   |-- scenario_service.py           # Сборка результата сценария
|   |-- data.py, validator.py          # Каталог и правила выбора
|   |-- simulation.py, score.py        # Эффекты и рейтинг
|   |-- ai_service.py                 # Вызов OpenAI и резервный ответ
|   |-- analysis_facts.py              # Факты и подбор рекомендаций
|   |-- analysis_formatter.py          # Итоговые разделы анализа
|   |-- requirements.txt
|   `-- tests/                        # API, настройки, AI-интеграция
`-- frontend/
    |-- package.json, package-lock.json
    |-- index.html
    `-- src/
        |-- main.jsx, App.jsx         # Точка входа и компоновка
        |-- Results.jsx, CityMap.jsx  # Результаты и схема города
        |-- components/               # Каталог, сценарий, панели, справка
        |-- hooks/useScenario.js      # Состояние и запуск сценария
        |-- api/api.js                # Запросы, ошибки и таймауты
        |-- constants/, presentation.js
        |-- demoData.json, weights.json
        `-- *.css                     # Стили приложения

Новый endpoint добавляется в routes.py, схемы в models.py; расчёты остаются в соответствующих сервисных модулях. API-запросы frontend находятся в src/api/api.js, UI-компоненты в src/components/.

Проверка работоспособности

  • GET /api/health возвращает {"status":"ok"}; эта проверка не проверяет OpenAI.
  • В интерфейсе появляется «Сервер подключён», контрольный сценарий даёт Score 54.00.
  • Если backend запущен после frontend, нажмите «Повторить подключение». Успешное подключение сбросит текущий выбор.

Backend, из backend с активированным окружением:

python -m unittest discover -s tests -v

В проекте 11 тестов; вызовы OpenAI подменяются, настоящий ключ не требуется.

Frontend, из frontend:

npm run build
npm run preview -- --host 127.0.0.1 --port 4173

Preview показывает сборку из dist; backend всё равно нужен отдельно. Команды npm test нет.

Типичные проблемы запуска

ПроблемаЧто проверить
node/npm/python не найденыУстановку инструментов и PATH; после установки откройте терминал заново. На Linux/macOS используйте python3 до активации окружения
PowerShell блокирует активациюИз backend используйте .\.venv\Scripts\python.exe -m pip install -r requirements.txt и тот же Python для -m uvicorn main:app ...
PowerShell блокирует npm.ps1Используйте npm.cmd вместо npm
ModuleNotFoundError: fastapi/openai/dotenvАктивируйте нужный .venv, выполните python -m pip install -r requirements.txt; наличие папки окружения не означает наличие пакетов
Не найдены config/routes/modelsЗапускайте backend из папки backend
Деморежим или Failed to fetchПроверьте /api/health, /api/data, адрес VITE_API_URL и перезапустите Vite
CORS errorCORS уже разрешён в config.py; проверьте, что на выбранном порту запущен именно этот backend, и посмотрите его лог
400 или 422В браузере откройте Network и прочитайте detail: правила выбора или структура JSON
Порт занятОстановите свой старый процесс или смените порт; при смене backend-порта обновите VITE_API_URL
Ошибка .map после обновленияПерезапустите обе актуальные версии, обновите зависимости, сверяйте ответ API со схемой в /docs
AI недоступен при наличии ключаПроверьте путь .env, приоритет переменных процесса, доступ к модели и сообщение ошибки в backend-логе

Проверить загрузку ключа без вывода секрета, из backend:

python -c "from config import ENV_FILE, get_setting; print('env_exists:', ENV_FILE.is_file()); print('key_configured:', bool(get_setting('OPENAI_API_KEY')))"

True означает наличие значения, а не успешный доступ к OpenAI. API-клиент frontend ожидает загрузку данных до 5 секунд, симуляцию до 25 секунд.

Ограничения MVP

  • Фиксированные синтетические данные и горизонт расчёта; нет подключения к реальной городской статистике.
  • Нет аккаунтов, базы данных, сохранения или истории сценариев. Перезагрузка страницы сбрасывает выбор.
  • Без backend доступен только демонстрационный интерфейс; без OpenAI остаётся числовой расчёт.
  • Рекомендации не являются готовым новым сценарием: они не гарантируют соблюдение бюджета и совместимости.
  • Проверка AI-вывода основана на строковых правилах, а не на полной проверке истинности текста.
  • Демо-данные и часть frontend-правил дублируют backend и требуют синхронизации при изменениях модели.
  • CORS открыт, авторизации и ограничения частоты запросов нет. Публичный запуск с AI-ключом требует дополнительной защиты.
  • Нет готового production-развёртывания и frontend-автотестов. Часть node_modules и Python-кешей уже отслеживается Git; проверяйте состав коммита.
Звёзды
⭐ 0
Форки
0
Язык
JavaScript
Последний push
в прошлом месяце
Коммиты
12
Авторы
3
Файлы
47
Ветки
2

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

Mayrain 8MetaSundering 3eralyerbolatov 1

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

  • 2026-09-23 17:45docs: restore README and screenshots without interface changesMayrain
  • 2026-09-23 17:42revert: restore project before README and interface updatesMayrain
  • 2026-09-23 17:37Merge remote-tracking branch 'origin/main'Mayrain
  • 2026-09-23 17:36docs: add README screenshotsMayrain
  • 2026-09-23 17:33Update project interface and documentationeralyerbolatov
  • 2026-09-23 17:00Refactoring of archMayrain
  • 2026-09-23 16:35feat(frontend): add city dashboard design and interactive mapMetaSundering
  • 2026-09-23 16:12Tighten AI analysis promptMayrain
Все коммиты (12)
  • 2026-09-23 15:40Improve AI analysis and simulation resultsMayrain
  • 2026-09-23 15:31Add landmark miniatures and initiative previews to city mapMetaSundering
  • 2026-09-23 14:36Build city simulator frontend foundationMetaSundering
  • 2026-09-23 13:55Initial HackAlem simulator MVPMayrain
Структура репозитория (47 файлов)
.gitignore
README.md
backend/ai_service.py
backend/analysis_facts.py
backend/analysis_formatter.py
backend/config.py
backend/data.py
backend/main.py
backend/models.py
backend/requirements.txt
backend/routes.py
backend/scenario_service.py
backend/score.py
backend/simulation.py
backend/tests/test_ai_integration.py
backend/tests/test_app_contract.py
backend/validator.py
docs/screenshots/overview.png
docs/screenshots/results.png
docs/screenshots/scenario.png
frontend/.gitignore
frontend/MAP-SOURCES.md
frontend/README.md
frontend/index.html
frontend/package-lock.json
frontend/package.json
frontend/src/App.jsx
frontend/src/CityMap.css
frontend/src/CityMap.jsx
frontend/src/Icon.jsx
frontend/src/Results.jsx
frontend/src/api/api.js
frontend/src/components/CityIllustrations.jsx
frontend/src/components/Empty.jsx
frontend/src/components/HelpDialog.jsx
frontend/src/components/InitiativeCatalog.jsx
frontend/src/components/ScenarioPage.jsx
frontend/src/components/ScenarioPanel.jsx
frontend/src/components/Sidebar.jsx
frontend/src/constants/cityMap.js
frontend/src/demoData.json
frontend/src/hooks/useScenario.js
frontend/src/main.jsx
frontend/src/presentation.js
frontend/src/refinements.css
frontend/src/styles.css
frontend/src/weights.json
Ветки (2)

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

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

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