Ветропрогноз
Веб-приложение для почасового прогноза выработки ветряков на 48 часов: управление турбинами, 3D-карта, загрузка CSV с измерениями и автоматическое…
О проекте
Команда: KMG
Кейс HackAlem.ai: Энергетика — Самрук-Казына
Стадия: Рабочий MVP
Задача кейса: AI-агент, прогнозирующий выработку ветровой электростанции по погодным данным
Суть
Веб-приложение для почасового прогноза выработки ветряков на 48 часов: управление турбинами, 3D-карта, загрузка CSV с измерениями и автоматическое переобучение с новым прогнозом.
Как устроено
История мощности двух турбин объединяется с кэшированными архивными прогнозами погоды ECMWF (parquet по датам выдачи) и проверяется на корреляции часовых поясов. На этих данных обучается общая модель градиентного бустинга и сравнивается с базовыми прогнозами на holdout. Агент оркестрирует доступные прогоны погоды с fallback и защитой от утечки будущего, а прогнозы записываются (upsert) в MongoDB. При каждом импорте CSV воркеры Celery/Redis ставят в очередь переобучение. Для новых площадок модель строится по истории мощности и календарным признакам, без выдуманной погоды. Фронтенд на React/TS.
Ключевые решения
- gradient boosting
- ECMWF archived forecast runs
- leakage guards
- baseline comparison
- timezone correlation analysis
- Celery retraining queue
- model versioning
Особенности
Прогнозы погоды кэшируются по конкретным прогонам ECMWF, а выборка строится «как было известно на момент прогноза» с защитой от утечки. Проведён анализ сдвига часовых поясов данных до и после марта 2024. Импорт новых измерений автоматически запускает переобучение, а старые версии моделей сохраняются.
Стек
Python · React · TypeScript · Vite · MongoDB · Celery · Redis · Docker Compose · parquet · joblib
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-3d048659-kmg.
README
README.mdВетропрогноз — агентный почасовой прогноз выработки ВЭС на 48 часов
Windmill management, hourly power forecasting, and an interactive 3D map.
React/TypeScript frontend, Python API, MongoDB storage, and Celery workers with
Redis transport. Power is normalized to [0, 1]; all displayed times are UTC.
Run the complete application
From the repository root, with Docker Desktop / Docker Compose running:
docker compose up --build -d --wait
Open http://127.0.0.1:8091. MongoDB and Redis stay on the private container network. The first startup imports the bundled two-windmill archive into MongoDB; subsequent starts preserve uploaded data and models.
Windmills and measurements
Open Ветряки и данные to add a windmill with its name and coordinates. It immediately becomes selectable and appears on the 3D map, even without data. Upload hourly CSV measurements or correct an individual hour:
time,power
2026-03-01T00:00:00Z,0.42
2026-03-01T01:00:00Z,0.38
Imports update matching hours and preserve other measurements. Every changed import automatically queues retraining and a new 48-hour forecast. The page shows queued/running/completed/failed jobs, input revisions, and validation metrics. Previous model versions and forecasts remain available.
The new per-windmill model uses power history and calendar features, without inventing weather for new sites. Start with at least five consecutive days of hourly measurements. Insufficient data is reported explicitly and remains saved for the next upload. Forecasts begin relative to the latest measured hour; training on old data does not produce a forecast for today's date.
Bundled archive
The initial archive contains 29 forecast origins (January 30–February 27, 2026), 48 predictions per turbine per origin, and January measurements for two turbines. These original weather-based forecasts are retained. The original offline research/model scripts are documented in backend/README.md.
Frontend development
Run the backend, database, workers, and scheduler in containers while Vite runs on the host:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build -d backend worker scheduler
cd frontend
npm ci
npm run dev
Open http://127.0.0.1:5173. The development override publishes the API on loopback port 8000 for Vite's existing proxy. Use Node 22.12+ (Node 24 recommended). Stop any older API using port 8000 before this setup.
Documentation and checks
- Data architecture, collections, training and API
- Deployment, persistence and integration checks
- Frontend source boundaries and commands
cd frontend
npm test
npm run build
npm run docker:check
Backend unit tests run with python -m pytest -p no:cacheprovider from backend/
after installing requirements.txt. Real MongoDB integration tests are enabled
with MONGODB_TEST_URI; they create and remove isolated windfarm_test_*
databases. See deployment instructions for running them inside the stack.
Авторы коммитов
История коммитов
- 2026-09-23 17:54Update README.mdNeAdisa
- 2026-09-23 17:53Update README.mdNeAdisa
- 2026-09-23 17:52Merge pull request #3 from BAITC-Hacks/dev-adilzhanNeAdisa
- 2026-09-23 17:51Merge branch 'main' into dev-adilzhanNeAdisa
- 2026-09-23 17:50add database support adding new windmills and update actual dataadilzhan
- 2026-09-23 17:45docs: README — запуск без участия команды, метрики, агент, источникиGabYer
- 2026-09-23 17:36docs: README — запуск без участия команды, метрики, агент, источникиGabYer
- 2026-09-23 17:28Merge pull request #2 from BAITC-Hacks/dev-adilzhanNeAdisa
Все коммиты (59)
- 2026-09-23 17:163d map with windmillsadilzhan
- 2026-09-23 16:52Merge main into dev-adilzhanadilzhan
- 2026-09-23 16:48Integrate forecast dashboard with layered backend APIadilzhan
- 2026-09-23 16:30Move backend code and assets into dedicated backend directoryadilzhan
- 2026-09-23 16:22Merge origin/main and preserve frontend and Python ignore rulesadilzhan
- 2026-09-23 16:19created frontend directory and docker containeradilzhan
- 2026-09-23 16:10merge: as-of model, MAE recipe, fallback demoGabYer
- 2026-09-23 16:10Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 16:10feat(model): as-of model for first origin, December-selected MAE recipe, fallback demo, pinned depsGabYer
- 2026-09-23 15:43merge: offline reproduction and model evaluation documentationGabYer
- 2026-09-23 15:43Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:43docs: document offline reproduction leakage controls and January metricsGabYer
- 2026-09-23 15:40merge: agent fallback and forecast contract testsGabYer
- 2026-09-23 15:40Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:40test: cover availability fallback upsert and committed forecast contractsGabYer
- 2026-09-23 15:37merge: timezone correlation analysisGabYer
- 2026-09-23 15:37Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:37docs: report pre and post March 2024 timezone correlationsGabYer
- 2026-09-23 15:36merge: January run-aware evaluation and UI actualsGabYer
- 2026-09-23 15:36Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:36feat(eval): score January forecasts against archived single runsGabYer
- 2026-09-23 15:34merge: agent tools and cached February forecastsGabYer
- 2026-09-23 15:33Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:33feat(agent): orchestrate available runs with fallbacks and upsert February forecastsGabYer
- 2026-09-23 15:30merge: production model through January historyGabYer
- 2026-09-23 15:30Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:29feat(model): retain validation artifact and fit full-history production modelGabYer
- 2026-09-23 15:16merge: real 48-hour turbine forecastGabYer
- 2026-09-23 15:16Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:16feat(agent): generate 48-hour forecast from archived run with leakage guardsGabYer
- 2026-09-23 15:13merge: shared forecasting model and holdout metricsGabYer
- 2026-09-23 15:13Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:13feat(model): train shared gradient boosting and compare forecast baselinesGabYer
- 2026-09-23 15:11merge: weather join and timezone diagnosticsGabYer
- 2026-09-23 15:11Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:11feat(features): join hourly weather and check timezone correlationsGabYer
- 2026-09-23 15:10merge: archived weather client and cacheGabYer
- 2026-09-23 15:10Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:10feat(weather): cache shared historical forecasts and single ECMWF runGabYer
- 2026-09-23 15:08merge: hourly turbine data loaderGabYer
- 2026-09-23 15:08Merge remote-tracking branch 'origin/main' into feature/modelGabYer
- 2026-09-23 15:07feat(data): load turbine history as hourly UTC observationsGabYer
- 2026-09-23 15:05Removed unused labels in designadilzhan
- 2026-09-23 14:56chore: configure turbine coordinates and shared weather pointGabYer
- 2026-09-23 14:42docs: translate README into Russianasissemba
- 2026-09-23 14:33Merge pull request #1 from BAITC-Hacks/feature/modelGabdulla Yermekbayev
- 2026-09-23 14:31data: add raw turbine history CSV filesGabYer
- 2026-09-23 14:26chore: project skeleton, data inspection, output contractGabYer
- 2026-09-23 14:18gitignoreadilzhan
- 2026-09-23 14:14design previewadilzhan
- 2026-09-23 14:10feat: first design versionadilzhan
Структура репозитория (263 файлов)
.gitignore DATA_ARCHITECTURE.md DEPLOYMENT.md README.md backend/.dockerignore backend/.env.example backend/Dockerfile backend/README.md backend/app/.gitkeep backend/config.yaml backend/data/raw/.gitkeep backend/data/raw/turbine_1.csv backend/data/raw/turbine_2.csv backend/data/weather_cache/.gitkeep backend/data/weather_cache/history_2023_6abbea01d7704bae.json backend/data/weather_cache/history_2023_6abbea01d7704bae.parquet backend/data/weather_cache/history_2024_07c98a35ff7669ea.json backend/data/weather_cache/history_2024_07c98a35ff7669ea.parquet backend/data/weather_cache/history_2025_7d2bd870a5cc0d61.json backend/data/weather_cache/history_2025_7d2bd870a5cc0d61.parquet backend/data/weather_cache/history_2026_f7ad17fafb4ae79b.json backend/data/weather_cache/history_2026_f7ad17fafb4ae79b.parquet backend/data/weather_cache/run_20251231T1200_15631169ddf254bb.json backend/data/weather_cache/run_20251231T1200_15631169ddf254bb.parquet backend/data/weather_cache/run_20260101T1200_2bbcd7d3d23d9509.json backend/data/weather_cache/run_20260101T1200_2bbcd7d3d23d9509.parquet backend/data/weather_cache/run_20260102T1200_ee5ba2c1b8a68a09.json backend/data/weather_cache/run_20260102T1200_ee5ba2c1b8a68a09.parquet backend/data/weather_cache/run_20260103T1200_7ce61f789ed343b3.json backend/data/weather_cache/run_20260103T1200_7ce61f789ed343b3.parquet backend/data/weather_cache/run_20260104T1200_eee3225b4ca1069a.json backend/data/weather_cache/run_20260104T1200_eee3225b4ca1069a.parquet backend/data/weather_cache/run_20260105T1200_9006569f3f9c458b.json backend/data/weather_cache/run_20260105T1200_9006569f3f9c458b.parquet backend/data/weather_cache/run_20260106T1200_8af0fd30dc31f10f.json backend/data/weather_cache/run_20260106T1200_8af0fd30dc31f10f.parquet backend/data/weather_cache/run_20260107T1200_65c90bff565a01fe.json backend/data/weather_cache/run_20260107T1200_65c90bff565a01fe.parquet backend/data/weather_cache/run_20260108T1200_51b5ce85a5e52e7c.json backend/data/weather_cache/run_20260108T1200_51b5ce85a5e52e7c.parquet backend/data/weather_cache/run_20260109T1200_09f0c3d50e4495f2.json backend/data/weather_cache/run_20260109T1200_09f0c3d50e4495f2.parquet backend/data/weather_cache/run_20260110T1200_4eb985a425e15a1b.json backend/data/weather_cache/run_20260110T1200_4eb985a425e15a1b.parquet backend/data/weather_cache/run_20260111T1200_99e8c4894802b8fe.json backend/data/weather_cache/run_20260111T1200_99e8c4894802b8fe.parquet backend/data/weather_cache/run_20260112T1200_37ced339358943c0.json backend/data/weather_cache/run_20260112T1200_37ced339358943c0.parquet backend/data/weather_cache/run_20260113T1200_e00bb07e929a6d84.json backend/data/weather_cache/run_20260113T1200_e00bb07e929a6d84.parquet backend/data/weather_cache/run_20260114T1200_710283ea97ec5dbf.json backend/data/weather_cache/run_20260114T1200_710283ea97ec5dbf.parquet backend/data/weather_cache/run_20260115T1200_2751ca57a7285656.json backend/data/weather_cache/run_20260115T1200_2751ca57a7285656.parquet backend/data/weather_cache/run_20260116T1200_fe524f8b274a9a5e.json backend/data/weather_cache/run_20260116T1200_fe524f8b274a9a5e.parquet backend/data/weather_cache/run_20260117T1200_f51c5d89a9b45000.json backend/data/weather_cache/run_20260117T1200_f51c5d89a9b45000.parquet backend/data/weather_cache/run_20260118T1200_904a12979e089615.json backend/data/weather_cache/run_20260118T1200_904a12979e089615.parquet backend/data/weather_cache/run_20260119T1200_5281e5b1393172d0.json backend/data/weather_cache/run_20260119T1200_5281e5b1393172d0.parquet backend/data/weather_cache/run_20260120T1200_6d8f4e38a1761d06.json backend/data/weather_cache/run_20260120T1200_6d8f4e38a1761d06.parquet backend/data/weather_cache/run_20260121T1200_4b50b735b8700742.json backend/data/weather_cache/run_20260121T1200_4b50b735b8700742.parquet backend/data/weather_cache/run_20260122T1200_3140b9444786e692.json backend/data/weather_cache/run_20260122T1200_3140b9444786e692.parquet backend/data/weather_cache/run_20260123T1200_a78f64170bb53795.json backend/data/weather_cache/run_20260123T1200_a78f64170bb53795.parquet backend/data/weather_cache/run_20260124T1200_6e6d69531e8208e1.json backend/data/weather_cache/run_20260124T1200_6e6d69531e8208e1.parquet backend/data/weather_cache/run_20260125T1200_036f6fe23b244515.json backend/data/weather_cache/run_20260125T1200_036f6fe23b244515.parquet backend/data/weather_cache/run_20260126T1200_30fc90fe79237fad.json backend/data/weather_cache/run_20260126T1200_30fc90fe79237fad.parquet backend/data/weather_cache/run_20260127T1200_152736ce4477d760.json backend/data/weather_cache/run_20260127T1200_152736ce4477d760.parquet backend/data/weather_cache/run_20260128T1200_abc6ba6e65bd74cd.json backend/data/weather_cache/run_20260128T1200_abc6ba6e65bd74cd.parquet backend/data/weather_cache/run_20260129T1200_78f0776b7543a5c7.json backend/data/weather_cache/run_20260129T1200_78f0776b7543a5c7.parquet backend/data/weather_cache/run_20260130T1200_c7fb9abecf8e0881.json backend/data/weather_cache/run_20260130T1200_c7fb9abecf8e0881.parquet backend/data/weather_cache/run_20260131T0600_e3f2d2402b7b1d16.json backend/data/weather_cache/run_20260131T0600_e3f2d2402b7b1d16.parquet backend/data/weather_cache/run_20260131T1200_de83adf0dcddf735.json backend/data/weather_cache/run_20260131T1200_de83adf0dcddf735.parquet backend/data/weather_cache/run_20260201T1200_6f5d417a1e4756da.json backend/data/weather_cache/run_20260201T1200_6f5d417a1e4756da.parquet backend/data/weather_cache/run_20260202T1200_4199b8705f8d68f5.json backend/data/weather_cache/run_20260202T1200_4199b8705f8d68f5.parquet backend/data/weather_cache/run_20260203T1200_220c05f707529886.json backend/data/weather_cache/run_20260203T1200_220c05f707529886.parquet backend/data/weather_cache/run_20260204T1200_39677087733daaf2.json backend/data/weather_cache/run_20260204T1200_39677087733daaf2.parquet backend/data/weather_cache/run_20260205T1200_484e439779e82522.json backend/data/weather_cache/run_20260205T1200_484e439779e82522.parquet backend/data/weather_cache/run_20260206T1200_000bb767a28af915.json backend/data/weather_cache/run_20260206T1200_000bb767a28af915.parquet backend/data/weather_cache/run_20260207T1200_2d8973e0f45fe4f6.json backend/data/weather_cache/run_20260207T1200_2d8973e0f45fe4f6.parquet backend/data/weather_cache/run_20260208T1200_4874238d598f02b6.json backend/data/weather_cache/run_20260208T1200_4874238d598f02b6.parquet backend/data/weather_cache/run_20260209T1200_adb0865a328e8952.json backend/data/weather_cache/run_20260209T1200_adb0865a328e8952.parquet backend/data/weather_cache/run_20260210T1200_4ff4e0525abc2fcd.json backend/data/weather_cache/run_20260210T1200_4ff4e0525abc2fcd.parquet backend/data/weather_cache/run_20260211T1200_6a569e365f615411.json backend/data/weather_cache/run_20260211T1200_6a569e365f615411.parquet backend/data/weather_cache/run_20260212T1200_dec6c9abab102c68.json backend/data/weather_cache/run_20260212T1200_dec6c9abab102c68.parquet backend/data/weather_cache/run_20260213T1200_f7dd729ed288ca0e.json backend/data/weather_cache/run_20260213T1200_f7dd729ed288ca0e.parquet backend/data/weather_cache/run_20260214T1200_c6823bf133cefdf4.json backend/data/weather_cache/run_20260214T1200_c6823bf133cefdf4.parquet backend/data/weather_cache/run_20260215T1200_c774171e62f13d73.json backend/data/weather_cache/run_20260215T1200_c774171e62f13d73.parquet backend/data/weather_cache/run_20260216T1200_53efde1fc5511f4e.json backend/data/weather_cache/run_20260216T1200_53efde1fc5511f4e.parquet backend/data/weather_cache/run_20260217T1200_b16455f1c7b81da3.json backend/data/weather_cache/run_20260217T1200_b16455f1c7b81da3.parquet backend/data/weather_cache/run_20260218T1200_45fa95eefb095a7d.json backend/data/weather_cache/run_20260218T1200_45fa95eefb095a7d.parquet backend/data/weather_cache/run_20260219T1200_6dd2b76ca9e476a4.json backend/data/weather_cache/run_20260219T1200_6dd2b76ca9e476a4.parquet backend/data/weather_cache/run_20260220T1200_837a3233aef01fa7.json backend/data/weather_cache/run_20260220T1200_837a3233aef01fa7.parquet backend/data/weather_cache/run_20260221T1200_85f7956154523c5e.json backend/data/weather_cache/run_20260221T1200_85f7956154523c5e.parquet backend/data/weather_cache/run_20260222T1200_34caf73821fb198a.json backend/data/weather_cache/run_20260222T1200_34caf73821fb198a.parquet backend/data/weather_cache/run_20260223T1200_8c5deb8d43756d0d.json backend/data/weather_cache/run_20260223T1200_8c5deb8d43756d0d.parquet backend/data/weather_cache/run_20260224T1200_4db4c53d89832794.json backend/data/weather_cache/run_20260224T1200_4db4c53d89832794.parquet backend/data/weather_cache/run_20260225T1200_5a0595455910c5ae.json backend/data/weather_cache/run_20260225T1200_5a0595455910c5ae.parquet backend/data/weather_cache/run_20260226T1200_81b01e4a144d1f21.json backend/data/weather_cache/run_20260226T1200_81b01e4a144d1f21.parquet backend/data/weather_cache/run_20260227T1200_9052df5fd4ae99be.json backend/data/weather_cache/run_20260227T1200_9052df5fd4ae99be.parquet backend/outputs/actuals_jan.csv backend/outputs/agent_runs.jsonl backend/outputs/asof_model_metadata.json backend/outputs/backtest_manifest.json backend/outputs/data_inspection.md backend/outputs/data_profile.json backend/outputs/december_experiment.json backend/outputs/forecast_metadata.json backend/outputs/forecasts.csv backend/outputs/forecasts_fallback_demo.csv backend/outputs/forecasts_jan.csv backend/outputs/forecasts_mock.csv backend/outputs/hgb_asof_model.joblib backend/outputs/hgb_jan_eval_model.joblib backend/outputs/hgb_model.joblib backend/outputs/hgb_validation_model.joblib backend/outputs/jan_evaluation_rows.csv backend/outputs/jan_model_metadata.json backend/outputs/metrics.json backend/outputs/model_metadata.json backend/outputs/power_curve.csv backend/outputs/training_frame.parquet backend/outputs/turbines_hourly.parquet backend/outputs/validation_metrics.csv backend/outputs/validation_model_metadata.json backend/outputs/validation_predictions.parquet backend/outputs/wind_correlations.csv backend/requirements.txt backend/src/__init__.py backend/src/agent/__init__.py backend/src/agent/forecast.py backend/src/agent/run.py backend/src/agent/tools.py backend/src/api/__init__.py backend/src/api/__main__.py backend/src/api/application.py backend/src/api/domain.py backend/src/api/infrastructure.py backend/src/api/server.py backend/src/data/__init__.py backend/src/data/load.py backend/src/eval/__init__.py backend/src/eval/backtest_jan.py backend/src/features/__init__.py backend/src/features/build.py backend/src/features/join.py backend/src/fleet/__init__.py backend/src/fleet/repository.py backend/src/fleet/service.py backend/src/fleet/tasks.py backend/src/fleet/training.py backend/src/fleet/validation.py backend/src/model/__init__.py backend/src/model/experiment.py backend/src/model/train.py backend/src/weather/__init__.py backend/src/weather/client.py backend/tests/test_agent_tools.py backend/tests/test_api.py backend/tests/test_config.py backend/tests/test_data_load.py backend/tests/test_experiment.py backend/tests/test_fleet_mongo.py backend/tests/test_fleet_training.py backend/tests/test_forecast.py backend/tests/test_january_metrics.py backend/tests/test_join.py backend/tests/test_model.py backend/tests/test_output_contract.py backend/tests/test_weather_client.py docker-compose.dev.yml docker-compose.yml frontend/.dockerignore frontend/Dockerfile frontend/README.md frontend/docker/nginx.conf frontend/index.html frontend/package-lock.json frontend/package.json frontend/public/favicon.svg frontend/public/wind-hero.png frontend/scripts/check-container.mjs frontend/src/app/App.tsx frontend/src/app/navigation.ts frontend/src/app/useForecastWorkspace.ts frontend/src/components/ActualComparison.css frontend/src/components/ActualComparison.tsx frontend/src/components/ErrorPanel.tsx frontend/src/components/HorizonControl.tsx frontend/src/components/Metric.tsx frontend/src/components/UpdateDialog.tsx frontend/src/components/charts/DataTooltip.tsx frontend/src/components/charts/PowerChart.tsx frontend/src/components/charts/WeatherChart.tsx frontend/src/components/forecast/AgentSteps.tsx frontend/src/components/forecast/HourlyTable.tsx frontend/src/components/forecast/ReleaseComparison.tsx frontend/src/components/map/TurbineMap.tsx frontend/src/components/map/turbineLayer.ts frontend/src/data/actualsCsv.ts frontend/src/data/api.ts frontend/src/domain/forecast.ts frontend/src/domain/map.ts frontend/src/lib/format.ts frontend/src/main.tsx frontend/src/pages/ForecastPage.tsx frontend/src/pages/HistoryPage.tsx frontend/src/pages/MapPage.css frontend/src/pages/MapPage.tsx frontend/src/pages/TurbinesPage.css frontend/src/pages/TurbinesPage.tsx frontend/src/pages/WeatherPage.tsx frontend/src/styles/global.css frontend/src/vite-env.d.ts frontend/tests/api.test.mjs frontend/tests/fixtures.mjs frontend/tests/fleet.test.mjs frontend/tests/map.test.mjs frontend/tests/navigation.test.mjs frontend/tsconfig.json frontend/vite.config.ts
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.