AAYMA
RU
Войти

Wind Power Forecasting

Развёрнутый сервис почасового прогноза мощности двух турбин на 48 часов по архивному прогнозу ECMWF.

Доведённый продукт
Команда «Health Core» 20💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: Health Core
Кейс HackAlem.ai: Энергетика — Самрук-Казына
Стадия: Доведённый продукт

Задача кейса: AI-агент, прогнозирующий выработку ветровой электростанции по погодным данным

Суть

Развёрнутый сервис почасового прогноза мощности двух турбин на 48 часов по архивному прогнозу ECMWF. Модели версионируются в PostgreSQL или Supabase, интерфейс на Vue показывает график, карту ветра на Google Maps и выгрузку. Реализован последовательный прогон 1–28 февраля.

Как устроено

CSV загружаются в БД без дублей с сохранением исходных строк и агрегируются почасово по каждой турбине. HistGradientBoostingRegressor обучается на 7 признаках: ветер, температура, sin/cos часа, sin/cos дня года, id турбины. Для прогноза на 00:00 UTC+5 берётся выпуск ECMWF IFS за 12:00 UTC предыдущего дня через Open-Meteo Single Runs API. Сервер отклоняет ревизию модели, если её обучающие данные не предшествуют моменту выпуска. Качество проверяется на временном holdout из последних 10% данных, прогнозы и погодные признаки сохраняются в БД.

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

  • HistGradientBoostingRegressor
  • циклическое кодирование времени
  • временной holdout
  • защита от утечки будущих данных
  • ECMWF IFS через Open-Meteo Single Runs
  • версионирование ревизий модели

Метрики

MAE 0.02637 (шкала 0–1) на историческом holdout против 0.32101 у прогноза постоянной средней; прогон февраля: 2688 точек без пропусков

Особенности

Строгая гигиена против утечки данных: выбор погодного выпуска, опубликованного до момента решения, и серверная проверка cutoff ревизии модели. Команда честно отделяет holdout-оценку от недоступной оценки февраля. Демо развёрнуто на healthcore.ych.kz.

Стек

Python · FastAPI · PostgreSQL · Supabase · SQLite · Vue 3 · Vite · Google Maps · nginx · systemd


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

README

README.md

Wind Power Forecasting — энергия ветра на 48 часов вперёд

Какой профиль мощности покажет каждая турбина завтра, если изменится ветер? Команда HEALTH CORE превращает архивный прогноз погоды для координат турбин в почасовой прогноз мощности в долях от номинала. Оператор видит не только результат, но и дату погодного выпуска, версию обученной модели и исходные данные расчёта.

Открыть работающее демо → · Трек «Энергетика» · Кейс HackAlem AI «Agentic AI для прогнозирования выработки ветровой электростанции (ВЭС)».

  • Практический результат: 48 почасовых значений для выбранных турбин, график, карта ветра и выгрузка; можно посмотреть первые 24 часа.
  • Воспроизводимость: исходные измерения сохраняются без потери полей, модель обучается из базы данных, каждая ревизия и каждый прогноз остаются доступными для проверки.
  • Проверенный масштаб: последовательный прогон 1–28 февраля для двух турбин создал 2 688 прогнозных точек без пропусков. Это проверка работы расчёта, не заявленная точность февраля: фактической выработки за этот период в выданных данных нет.

Обзор сохранённого 48-часового прогноза: показатели и почасовой график

Скриншот публичного демо: сохранённый прогноз, а не тестовый макет. Мощность показана в долях от номинала (0–1), не в мегаваттах.

Как продукт получает результат

  1. Загружает историю измерений турбин из конкурсных файлов в PostgreSQL и обучает версионируемую модель на данных, доступных до момента прогноза.
  2. Для координат каждой выбранной турбины берёт архивный прогноз погоды, выпущенный Европейским центром среднесрочных прогнозов погоды (ECMWF) до момента решения, а не фактическую погоду задним числом.
  3. Рассчитывает 48 часов, сохраняет погодные признаки и результат в базе данных, показывает их в интерфейсе и позволяет выгрузить. Новые измерения можно загрузить и обучить новую ревизию модели.

География турбин и архивный прогноз ветра на карте

Карта публичного демо: скорость и направление ветра показаны для точек турбин и выбранного часа; между турбинами сплошное поле ветра не моделируется.

Историческая проверка модели дала среднюю абсолютную ошибку 0,02637 на шкале мощности 0–1 против 0,32101 у простого прогноза постоянной средней. Это сравнение на отложенных исторических измерениях, не оценка качества прогноза за февраль: в феврале модель получает прогнозный ветер, а фактической выработки для сравнения нет. Методика и ограничения приведены ниже.

Подробности: соответствие кейсу · формулы и проверка · зависимости · установка · серверный деплой · словарь сокращений.

Соответствие кейсу

Требование кейсаСостояние
Модель по историческим данным ВЭСРеализовано; разбор CSV, почасовая агрегация, обучение из БД и внешний временной holdout.
Архивный прогноз погоды по координатам на дату решенияРеализовано через Open-Meteo Single Runs; фактическое время публикации выпуска API не подтверждает.
Почасовой прогноз на 24–48 часовРеализовано: расчёт 48 часов, просмотр и экспорт первых 24 либо всех 48.
Последовательный тестовый период 1–28 февраляРеализован возобновляемой CLI-командой для двух турбин; 28 × 2 × 48 точек. В интерфейсе нет кнопки общего запуска.
Автономный цикл и повторный расчёт при обновлении входных данныхЦикл запускается пользователем; после загрузки данных нужно вручную переобучить модель и повторить прогноз. Автоматическая очередь/триггер обновления пока не реализованы.
Оценка ошибки февральского прогнозаНевозможна: фактических значений февраля в выданных CSV нет; ниже приведён исторический holdout без утечки.

Что решаем

На каждый день с 31 января по 28 февраля 2026 года система должна воспроизвести решение, которое могло быть принято в тот день: получить доступный на тот момент прогноз погоды, подготовить признаки, применить обученную модель и сохранить результат на 24–48 часов. Отдельный расчёт запускается через API, а воспроизводимый последовательный прогон февраля — командой python -m backend.forecast_range. Оценка по фактическим данным февраля невозможна: их нет в предоставленных CSV.

Для расчёта на 00:00 времени Казахстана (UTC+5) используется выпуск ECMWF IFS за 12:00 UTC предыдущего календарного дня UTC. До момента решения остаётся около 7 часов на публикацию модели. Время выпуска, версия модели и входные погодные параметры сохраняются вместе с прогнозом. Прогнозная мощность — нормализованное значение 0–1 каждой турбины; перевод в МВт невозможен без номинальной мощности.

Исходные CSV из папки TZ/ содержат 291 859 строк с 11 марта 2023 года по 31 января 2026 года, хотя в имени файлов фигурирует 28 февраля. CSV входят в закрытый Git-репозиторий. Импорт сохраняет исходные поля каждой строки в measurements.original и отдельно числовые поля для вычислений. Обучение всегда читает БД, а не файлы CSV. По умолчанию оно исключает записи начиная с 31 января 2026 года, чтобы ревизию можно было использовать уже в прогнозе 31 января в 00:00. Для выпусков начиная с 1 февраля можно явно выбрать отсечение 1 февраля; тестовый февраль в обучение не допускается. Сервер отклоняет ревизию, если её последняя обучающая запись не предшествует моменту выпуска прогноза. Проверка модели использует последние 10% почасовых данных по времени. Фактическую ошибку февральского прогноза пока оценить нельзя. Модель обучается на измеренном ветре у турбины, а при прогнозе получает ветер ECMWF на высоте 100 м; эта разница распределений и неизвестное время публикации погодного выпуска ограничивают достоверность проверки.

Состав и архитектура

  • backend/app/db.py: PostgreSQL (включая Supabase) или локальная SQLite; исходные измерения, турбины, ревизии моделей, бинарные артефакты, запуски и точки прогноза. init-db создаёт таблицы, ограничения, индексы и включает RLS в PostgreSQL.
  • backend/app/pipeline.py: импорт без дублирования по (turbine_id, source_id), почасовая агрегация, обучение HistGradientBoostingRegressor, расчёт и простой анализ диапазона результата.
  • backend/app/weather.py: архив отдельного выпуска ECMWF IFS через Open-Meteo Single Runs API.
  • backend/app/main.py: FastAPI для просмотра и загрузки измерений, регистрации турбин, обучения, выбора ревизии и запуска прогноза.
  • frontend/: интерфейс на Vue 3 с графиком, таблицей, выбором турбин и ревизии, загрузкой CSV, обучением и экспортом прогноза.

Раздел «География ВЭС» показывает на Google Maps турбины и интерактивный точечный слой ветра. Для выбранной даты браузер отдельно запрашивает из Open-Meteo Single Runs API почасовые wind_speed_100m и wind_direction_100m того же архивного выпуска ECMWF IFS, который соответствует правилу выбора погоды для прогноза. Стрелки показывают, куда дует ветер; при выборе часа меняются значения у каждой турбины. Слой носит справочный характер: эти дополнительные данные не записываются в БД и не меняют обученную модель или сохранённый прогноз. Направление не содержится в исходных измерениях, а данные у двух турбин нельзя считать непрерывным полем ветра между ними.

Цикл агента в текущей версии выполняется по запросу: выбор доступного выпуска погоды → загрузка → подготовка признаков → модель → контроль допустимого диапазона → запись результата. Автоматический повторный расчёт при обновлении входных данных, журнал состояний и обработка сбоев — задачи следующего этапа; они пока не заявляются как готовые.

Как получаются данные, прогноз и показатели

Исходные измерения и почасовая выборка

Загрузка принимает CSV в UTF-8 (допускается BOM) ровно с пятью колонками из ТЗ: ID, статистическое время, средняя скорость ветра (м/с), нормализованная активная мощность и средняя температура воздуха (°C). Вся строка сохраняется без изменения значений в measurements.original; отдельно сохраняются её числовые поля и время для расчётов. Для пары (turbine_id, source_id) действует уникальность: повторная загрузка пропускает эту пару. В ответе rows — число прошедших разбор строк файла, imported — число новых строк в БД, skipped = rows - imported. Некорректный файл не импортируется частично.

Для обучения из БД выбираются измерения с observed_at < cutoff_date 00:00 по времени Казахстана. По умолчанию cutoff_date = 2026-01-31; позже 2026-02-01 задать нельзя. Значения каждого признака и цели усредняются отдельно для каждой турбины и каждого календарного часа:

wind_hour  = sum(wind_speed_ms в этом часе) / число имеющихся измерений ветра
temp_hour  = sum(temperature_c в этом часе) / число имеющихся измерений температуры
power_hour = sum(normalized_power в этом часе) / число имеющихся измерений мощности

Это среднее по имеющимся строкам, а не сумма выработанной энергии: если 10-минутные записи отсутствуют, час всё равно может участвовать. Часы без полного набора трёх агрегированных значений удаляются; отсутствующие часы не интерполируются. Цель модели — power_hour в шкале 0–1. Номинальная мощность турбины в МВт не предоставлена, поэтому МВт, МВт·ч и суммарная энергия не вычисляются.

Обучение и проверка модели

Для почасовой строки строится вектор из семи признаков в фиксированном порядке:

x = [wind_hour, temp_hour,
     sin(2π · hour/24), cos(2π · hour/24),
     sin(2π · day_of_year/365.25), cos(2π · day_of_year/365.25),
     turbine_id]

hour и day_of_year берутся непосредственно из метки времени CSV; её часовой пояс поставщик не указал. Циклическое кодирование делает соседними 23:00 и 00:00, а также конец и начало года. В прогнозе за 2026 год время явно переводится в UTC+5. До 1 марта 2024 года Алматы жил в UTC+6; CSV не содержат повторного часа при переводе часов назад, поэтому абсолютную временную привязку старых записей нельзя подтвердить без ответа поставщика. Обучается одна общая модель для турбин, где turbine_id — числовой признак, а не отдельная модель и не категориальная one-hot переменная. Используется HistGradientBoostingRegressor: ансамбль деревьев, обучаемый по квадратичной ошибке. Явные параметры: max_iter=100 (верхний предел итераций), max_leaf_nodes=20, l2_regularization=0.1, random_state=42. Параметры библиотеки, не заданные кодом, в зафиксированной версии scikit-learn 1.9.1 остаются по умолчанию, в том числе learning_rate=0.1, loss='squared_error' и early_stopping='auto'. При выборке более 10 000 строк автоматическая ранняя остановка использует внутренний проверочный срез; поэтому фактическое число итераций может быть меньше 100.

После почасовой агрегации все строки сортируются по времени. Пусть q90 — 90-й процентиль почасовых меток времени. Внешняя обучающая часть — строки с observed_at < q90, внешняя проверочная — с observed_at >= q90; одинаковые часы двух турбин остаются по одну сторону границы. Это примерно последние 10% часов, но не обязательно ровно 10% строк. Новая ревизия обучается заново на внешней обучающей части; это не инкрементальное обновление старой модели. При включённой автоматической ранней остановке scikit-learn дополнительно выделяет 10% из внешней обучающей части для своей внутренней проверки. Внешняя проверочная часть не передаётся в model.fit.

На внешней проверочной части вычисляется pred_i = min(1, max(0, model.predict(x_i))), затем MAE:

validation_mae = (1 / N_validation) · Σ |power_hour_i - pred_i|

training_rows и validation_rows — количества почасовых строк после агрегации во внешних частях, не исходных CSV-строк; training_rows включает строки, которые библиотека могла отложить для внутренней ранней остановки. training_max_at в API — последний час всей подготовленной выборки, включая внешнюю проверочную часть; это консервативная граница доступности данных ревизии, а не последний час, переданный в model.fit. Без явного revision_id сервер выбирает последнюю ревизию, у которой training_max_at < issued_at; явно выбранная несовместимая ревизия отклоняется. MAE выражена в единицах нормализованной мощности (0–1), а не в процентах или МВт; она проверяет модель на измеренном ветре, а не качество февральского прогноза с прогнозным ветром.

Каждое обучение создаёт новый revision_id и уникальную 12-символьную метку model_version. Метаданные и бинарный артефакт сохраняются в БД; локальный MODEL_DIR хранит копию. Выбор пустой ревизии при расчёте означает последнюю совместимую с датой выпуска по revision_id, а не «самую точную» по MAE. Архивные расчёты сохраняют использованную model_version.

Фактически проверенная точность

Команда python -m backend.evaluate 2 повторно оценивает сохранённую ревизию 301a08f545e8 на строго более позднем временном отрезке 21.10.2025 10:00 — 30.01.2026 23:00, который не передавался в model.fit. Это 4 873 почасовые строки двух турбин после агрегации. Базовый прогноз для сравнения — постоянная средняя мощность соответствующей турбины, вычисленная только по обучающей части. MAE и RMSE выражены в шкале нормализованной мощности 0–1:

ВыборкаЧасыMAE моделиRMSE моделиMAE базового прогноза
Обе турбины4 8730,026370,062880,32101
Турбина 12 4380,025750,055940,32115
Турбина 22 4350,026990,069130,32088

MAE модели ниже MAE этого простого baseline на 91,8%. Это не точность февральского прогнозирования: в holdout модель получает измеренный ветер, а при расчёте февраля — прогноз ECMWF. Фактические мощности за февраль отсутствуют; поэтому февральские MAE, RMSE и «процент точности» не заявляются.

Команда python -m backend.forecast_range --revision-id 1 --turbine-ids 1 2 рассчитала 1–28 февраля 2026 года без пропусков: 28 выпусков × 2 турбины × 48 часов = 2 688 прогнозных точек. Все сохранены в PostgreSQL, нормализованная мощность лежит в 0…0,98942. Повторный запуск пропускает уже имеющиеся полные выпуски той же версии и набора турбин; он не проверяет точность без фактической мощности. Ревизия №1 обучена на данных не позднее 31 января, то есть доступна для выпуска 1 февраля.

Погодный выпуск и прогноз для выбранных турбин

Для даты D момент решения issued_at = D 00:00 UTC+5, то есть 19:00 UTC предыдущего дня. Код выбирает отдельный выпуск ECMWF IFS, инициализированный в 12:00 UTC того же UTC-дня: между инициализацией и решением около семи часов. По сохранённым координатам каждой выбранной турбины Open-Meteo Single Runs API запрашивает run=<время выпуска>, wind_speed_100m (м/с) и temperature_2m (°C) с часовыми метками UTC. Это значения архивного прогноза погоды, а не измеренная или реанализированная погода. Если хотя бы один необходимый час отсутствует, расчёт завершается ошибкой; подмены фактической погодой нет. Open-Meteo не возвращает момент публикации выпуска, поэтому семичасовой запас — допущение, а не доказательство доступности прогноза в тот момент.

Для каждого выбранного turbine_id и k = 1…48 строится valid_at_UTC = issued_at_UTC + k часов. Погодные wind_speed_100m и temperature_2m занимают первые два места того же вектора x; час и день года для синусов и косинусов берутся из valid_at после перевода в UTC+5, а последнее место — ID турбины. Итоговая почасовая мощность:

forecast_power(turbine, k) = min(1, max(0, model_revision.predict(x_turbine,k)))

Это результат обученной модели, не формула пересчёта скорости ветра в энергию. Коэффициенты и деревья определяются обучением, поэтому результат на иных погодных данных получается повторным вычислением тех же семи признаков и вызовом predict, без добавления этих погодных данных в обучающую выборку. Прогнозные ветер и температура вместе с результатом сохраняются в forecast_points. На одну выбранную турбину сохраняется 48 точек; на T турбин — 48 × T. Переключатель 24/48 часа в интерфейсе меняет только отображение, не сам расчёт и не запись в БД.

Показатели интерфейса и результата API

В интерфейсе сначала выбираются точки одной просматриваемой турбины, сортируются по valid_at и ограничиваются первыми H строками, где H = 24 или 48. Если P — набор точек с конечным числовым значением normalized_power, а W — набор точек с конечной скоростью ветра, то:

ПоказательТочное вычислениеЕдиница и смысл
Средняя мощностьsum(p.normalized_power for p in P) / len(P)Нормализованная мощность 0–1 за показанный горизонт; не сумма энергии.
Пиковая мощность и времяПоследовательно сравниваются точки: следующая заменяет текущий максимум при next.power >= current.power; берётся её valid_atПоследнее максимальное значение при равенстве; время показывается в UTC+5.
Минимальная мощность и время в «Источниках данных»Следующая точка заменяет текущий минимум при next.power <= current.power; берётся её valid_atПоследнее минимальное значение при равенстве.
Средний ветерsum(p.wind_speed_ms for p in W) / len(W)м/с, среднее прогнозных погодных значений показанного горизонта.
Покрытие горизонтаПоказываются два числа: len(rows) из ожидаемых HНапример, 24 / 48 ч; это не вычисленный процент или результат деления.
График и таблицаКаждая сохранённая точка без дополнительного усреднения; переключение между normalized_power, wind_speed_ms, temperature_cМощность 0–1, ветер м/с, температура °C; в таблице значения только округляются для показа.
analysis.points48 × число выбранных турбинЧисло точек всего сохранённого расчёта.
analysis.min, analysis.max, analysis.meanmin, max, sum/N по всем предсказаниям расчёта до сохраненияНормализованная мощность за полные 48 часов и все выбранные турбины; может отличаться от карточек одной турбины и 24-часового просмотра.

При пустом наборе показатель не выдаётся за ноль: интерфейс показывает «—» или отсутствие результата. Проверки интерфейса отмечают неполные 24/48 часов, значения мощности вне 0–1, неверную последовательность часов и статус invalid_lookahead; при этих ошибках экспорт отключён. Индикатор «API подключён» проверяет только GET /health и соединение с БД, не точность модели. Проверка «погодный выпуск не позже решения» сравнивает weather_run_at <= issued_at, но сама по себе не доказывает фактическое время публикации погоды.

График мощности имеет шкалу 0–1. Для ветра и температуры нижняя граница шкалы — min(0, минимум показанных значений) - 1, верхняя — максимум показанных значений + 1; пять подписей оси делят этот диапазон на четыре равные части. Полоса мощности в таблице имеет ширину min(100, max(0, normalized_power × 100))%. Эти преобразования касаются только изображения: экспорт CSV отдаёт исходные сохранённые числа без экранного округления, с valid_at в UTC.

Словарь сокращений

СокращениеЗначение в проекте
ВЭС, ТЗ, БД, SQLВетровая электростанция; техническое задание; база данных; Structured Query Language — язык запросов к реляционной БД.
ECMWFEuropean Centre for Medium-Range Weather Forecasts — Европейский центр среднесрочных прогнозов погоды. Официальный словарь ECMWF.
IFSIntegrated Forecasting System — интегрированная система прогнозирования ECMWF. В коде Open-Meteo модель обозначена ecmwf_ifs. Описание ECMWF.
HRESHigh-Resolution Forecast — прежнее название детерминированного прогноза высокого разрешения ECMWF. В актуальной номенклатуре он называется IFS Medium-range Control forecast (formerly HRES); сокращение HRES не обозначает отдельную версию модели 2026 года. Каталог ECMWF.
AI, Agentic AIArtificial Intelligence — искусственный интеллект; «агентный ИИ» здесь означает последовательный цикл получения погоды, подготовки признаков, применения модели, проверки и сохранения результата. Автоматический перезапуск при обновлении данных пока не реализован.
CSV, UTF-8, BOMComma-Separated Values — текстовый табличный файл; Unicode Transformation Format, 8-bit — кодировка файла; Byte Order Mark — необязательная сигнатура в его начале.
API, CLI, URL, HTTPSApplication Programming Interface — программный интерфейс; Command-Line Interface — командная строка; Uniform Resource Locator — адрес ресурса; Hypertext Transfer Protocol Secure — защищённый протокол сайта.
UTCCoordinated Universal Time — всемирное координированное время. UTC+5 — местное время, на пять часов опережающее UTC.
MAE, RMSEMean Absolute Error — средняя абсолютная ошибка; Root Mean Squared Error — корень из средней квадратичной ошибки. Здесь обе измеряются в шкале нормализованной мощности 0–1.
RLS, DDL, JSON, IDRow Level Security — защита строк таблицы; Data Definition Language — команды изменения схемы БД; JavaScript Object Notation — формат структурированных данных; Identifier — идентификатор записи.
CC BY 4.0Creative Commons Attribution 4.0 International — лицензия данных с обязательным указанием источника.
MIT, BSD, LGPLКраткие названия лицензий программных компонентов: Massachusetts Institute of Technology License, Berkeley Software Distribution License и Lesser General Public License. Точные лицензии зависимостей перечислены ниже.
МВт, МВт·чМегаватт — единица мощности; мегаватт-час — единица энергии. Прогноз 0–1 нельзя перевести в них без номинальной мощности турбины и длительности периода.

Зависимости и внешние источники

Backend зависимости с точными версиями зафиксированы в requirements.txt: FastAPI, Uvicorn, SQLAlchemy, Psycopg 3, pandas, NumPy, scikit-learn, joblib, HTTPX, python-dotenv, python-multipart. Frontend зависимости находятся в frontend/package.json, а точные версии Vue 3, Vite и их транзитивных пакетов — в frontend/package-lock.json. Для локальной сборки нужны Python 3.11, Node.js 22 и npm; для серверного варианта — Debian 12, PostgreSQL 17 или Supabase PostgreSQL, nginx и Certbot. Прямые зависимости перечислены ниже; транзитивные Python-зависимости разрешаются pip из зафиксированного файла верхнего уровня, а транзитивные JS-версии закреплены lock-файлом.

Внешний погодный сервис: Open-Meteo Single Runs API, модель ecmwf_ifs, доступ к конкретным архивным выпускам. Это исторические прогнозы, не реанализ и не фактическая погода. Источник данных — Open-Meteo с данными ECMWF; лицензия API-данных CC BY 4.0, атрибуция обязательна. Бесплатный API разрешён для некоммерческого использования и имеет лимиты запросов; для коммерческого применения нужен соответствующий тариф. Две исходные координаты взяты из ссылок Google Maps в ТЗ; Google Maps также используется для раскрытия короткой ссылки при добавлении турбины. Supabase предоставляет PostgreSQL; приложение не использует его пользовательскую авторизацию или Storage.

Прямые зависимости и лицензии по метаданным установленных пакетов:

КомпонентИсточникЛицензия
FastAPIfastapi/fastapiMIT
Uvicornencode/uvicornBSD-3-Clause
SQLAlchemysqlalchemy/sqlalchemyMIT
Psycopg 3psycopg/psycopgLGPL-3.0-only
pandaspandas-dev/pandasBSD-3-Clause
NumPynumpy/numpyBSD-3-Clause и лицензии отдельных частей, см. пакет
scikit-learnscikit-learn/scikit-learnBSD-3-Clause
joblibjoblib/joblibBSD-3-Clause
HTTPXencode/httpxBSD-3-Clause
python-dotenvtheskumar/python-dotenvBSD-3-Clause
python-multipartKludex/python-multipartApache-2.0
Vue 3vuejs/coreMIT
Vitevitejs/viteMIT

Развёртывание с нуля на Windows

Требуются Git, Python 3.11+ (python --version), Node.js 22+ и npm (node --version, npm --version), доступ к Open-Meteo. Команды выполнять в PowerShell из корня проекта. Для рабочей установки задайте PostgreSQL в Supabase либо собственный PostgreSQL; SQLite допустима только для локальной проверки. Если репозитория ещё нет: git clone https://github.com/BAITC-Hacks/hack-ed58da2d-health-core.git, cd hack-ed58da2d-health-core, git switch dev.

python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
Copy-Item .env.example .env
python -m backend.cli init-db
python -m unittest discover -s backend/tests -v

Перед init-db при использовании Supabase замените DATABASE_URL в .env по инструкции ниже. Без этого параметра либо с примером из .env.example используется локальная SQLite; публичный production-запуск с SQLite запрещён. Команда init-db не удаляет записи и её можно запускать повторно. Инициализация схемы также выполняется при старте API, если DB_INIT_ON_STARTUP=true. В уже существующей БД create_all не меняет столбцы старых таблиц: последующие изменения схемы потребуют отдельной миграции.

Поместите оба конкурсных CSV в локальную папку TZ/ перед первым запуском API. Файлы должны заканчиваться на turbine 1.csv и turbine 2.csv; они не включаются в Git. При AUTO_BOOTSTRAP=true (значение по умолчанию) backend в фоне проверит их, сохранит исходные строки в выбранной БД и обучит модель с отсечением 31 января 2026 года. Интерфейс покажет «Загрузка данных», затем «Обучение модели» и после завершения — «Прогноз доступен»; при ошибке покажет причину. GET /readiness возвращает те же состояния. Уже существующая ревизия модели не обучается повторно при рестарте. Если файлы добавлены после запуска, перезапустите backend. Путь можно изменить через TZ_DIR в .env.

Ручные импорт и переобучение через интерфейс либо CLI остаются доступны, если нужно обновить данные:

python -m backend.cli import-csv 1 'TZ/Dataset HackAlemAI для участников 11.03.2023-28.02.2026 - turbine 1.csv'
python -m backend.cli import-csv 2 'TZ/Dataset HackAlemAI для участников 11.03.2023-28.02.2026 - turbine 2.csv'
python -m backend.cli train

Чтобы использовать весь январь для прогноза с 1 февраля, укажите python -m backend.cli train --cutoff-date 2026-02-01. Для прогноза, выпущенного 31 января в 00:00, такая ревизия не подходит и будет отклонена сервером.

Для проверки установки без конкурсных файлов доступен генератор явно синтетических данных: python -m backend.generate_demo_csv demo-data, затем python -m backend.cli import-csv 1 demo-data/demo-turbine-1.csv, python -m backend.cli import-csv 2 demo-data/demo-turbine-2.csv, python -m backend.cli train. Это только smoke test: его метрики не сопоставимы с конкурсным набором. Импорт проверяет весь файл до записи, отклоняет нечисловые и недопустимые значения и повторяем: существующие пары (turbine_id, source_id) пропускаются. Для каждой новой турбины сначала добавьте координаты, затем её CSV и обучите новую ревизию. SQLite и локальный кэш модели не коммитятся; бинарный артефакт каждой новой ревизии сохраняется также в общей БД.

Запустите API в первом терминале (с активированным .venv):

python -m uvicorn backend.app.main:app --host 127.0.0.1 --port 8000

Во втором терминале, из корня проекта:

cd frontend
npm ci
npm run dev

Открыть http://127.0.0.1:5173. Для проверки API доступны /docs, /health и /readiness на http://127.0.0.1:8000: /health должен содержать "database":"ready", а /readiness после подготовки — "can_forecast":true. До готовности кнопка расчёта заблокирована, причина видна в панели прогноза. Для проверки выберите 31 января 2026 года, запустите агента и убедитесь, что получено по 48 строк на каждую выбранную турбину. Прямой запрос для первой турбины с последней совместимой ревизией:

Invoke-RestMethod -Method Post http://127.0.0.1:8000/forecasts/2026-01-31 -ContentType 'application/json' -Body '{"turbine_ids":[1]}'

База данных и доступы

Supabase-проект и сама база postgres создаются в кабинете Supabase; приложение не создаёт облачный проект. python -m backend.cli init-db в существующей базе создаёт таблицы measurements, turbines, model_revisions, model_artifacts, forecast_runs, forecast_points, ограничения и индексы, а также идемпотентно регистрирует две турбины из ТЗ. В PostgreSQL для всех шести таблиц включается RLS без политик для anon/authenticated: фронтенд работает только через backend, прямой доступ через Supabase Data API закрыт. На общей БД после обновления кода запускайте init-db явно: DB_INIT_ON_STARTUP=false пропускает DDL при обычном старте API. Bootstrap не изменяет столбцы существующих таблиц.

В локальном .env рядом с .env.example укажите DATABASE_URL. Файл .env игнорируется Git. В панели Supabase откройте Connect → Connection string и выберите Direct connection для ПК/постоянного сервера с работающим IPv6 либо Session pooler (порт 5432) для IPv4. Если Direct-хост db.<project-ref>.supabase.co не подключается на ПК без IPv6, используйте Session pooler, не Transaction pooler на порту 6543. Замените схему URL postgresql:// на postgresql+psycopg:// (приложение также делает это автоматически). Пример без реальных реквизитов:

DATABASE_URL=postgresql+psycopg://USER:[скрыто]@HOST:5432/postgres?sslmode=require

Для повседневной работы используйте отдельную роль с минимальными правами на таблицы; первичная инициализация требует права создавать объекты в public и включать RLS. Пароль с @, :, / и другими служебными символами необходимо URL-кодировать. Не отправляйте строку подключения в чат или репозиторий. Supabase API key и URL проекта для текущей архитектуры не нужны: backend использует PostgreSQL-соединение. Проверьте подключение командами python -m backend.cli init-db и Invoke-RestMethod http://127.0.0.1:8000/health после запуска API. При ошибке соединения старт API завершится с ошибкой, а не переключится молча на SQLite. Текущая версия создаёт начальную схему, но не содержит версионированных миграций последующих изменений. Альтернатива Supabase — собственный PostgreSQL 17; команды установки, создания роли и БД даны в deploy/README.md.

Перенос существующей SQLite в Supabase

Остановите локальный backend перед переносом, чтобы новые записи не появились в SQLite после снимка. Фронтенд останавливать необязательно. Для сохранности сделайте копию локальной БД (файл игнорируется Git):

Copy-Item .\healthcore.sqlite3 .\healthcore-before-supabase.sqlite3

В .env добавьте SUPABASE_DATABASE_URL=postgresql+psycopg://... или замените существующий DATABASE_URL на полный URL из Connect → Connection string в Supabase. Если заданы обе переменные, SUPABASE_DATABASE_URL имеет приоритет. После успешного переноса и перезапуска backend приложение будет читать PostgreSQL. Не добавляйте URL в код, README или чат. Используйте роль с правами на создание таблиц и запись; целевые таблицы measurements, forecast_runs, forecast_points должны быть пустыми. Скрипт не смешивает данные с существующими строками и не удаляет их.

.\.venv\Scripts\python.exe -m backend.migrate_sqlite_to_postgres
.\.venv\Scripts\python.exe -m backend.cli init-db
.\.venv\Scripts\python.exe -m uvicorn backend.app.main:app --host 127.0.0.1 --port 8000

Скрипт копирует строки трёх исходных таблиц порциями, сохраняя ID, JSON и связи прогнозов; загрузка выполняется одной транзакцией. Он сверяет количество, максимальный ID и сумму ID каждой таблицы до и после коммита и обновляет PostgreSQL-последовательности. Новые таблицы турбин и моделей создаются bootstrap-командой, но скрипт не переносит в них ревизии из более новой SQLite. Если перенос не завершён, записи целевых таблиц откатываются и локальная SQLite остаётся нетронутой. Проверьте http://127.0.0.1:8000/health, затем откройте сохранённый прогноз по ID. Старый model.joblib — локальный артефакт; после переноса рекомендуется обучить новую ревизию, чтобы она сохранилась в model_artifacts и была доступна всей команде. Не удаляйте резервную SQLite, пока не проверите данные в Supabase.

API и пополнение данных

  • GET /turbines — список турбин и координат; POST /turbines — добавить {name, latitude, longitude} либо {name, maps_url}. Для коротких ссылок поддерживается только HTTPS Google Maps.
  • GET /measurements?turbine_id=1&limit=100&offset=0 — исходные строки для просмотра.
  • POST /measurements — добавить одно измерение с turbine_id, source_id, observed_at, wind_speed_ms, normalized_power, temperature_c.
  • POST /measurements/upload?turbine_id=1 — загрузить CSV multipart-полем file, максимум 50 МБ; ответ {rows, imported, skipped}.
  • POST /models/train — создать ревизию по данным БД до 31 января 2026 года (по умолчанию); тело {"cutoff_date":"2026-02-01"} разрешает использовать весь январь для выпусков не раньше 1 февраля. GET /models — список ревизий и метрик.
  • GET /readiness — готовность прогнозирования, стадия фонового импорта/обучения, причина недоступности (в том числе БД) и активная ревизия.
  • POST /forecasts/2026-01-31 с JSON {"turbine_ids":[1,2],"revision_id":1} — рассчитать 48 часов для указанных турбин выбранной ревизией. Без revision_id используется последняя ревизия, совместимая с датой выпуска.
  • GET /forecasts/{id} — результат, погодные признаки и анализ.
  • GET /forecasts — до 100 последних сохранённых расчётов для истории интерфейса.

Для Supabase Session Pooler количество соединений ограничено тарифом проекта. Backend по умолчанию держит не более двух соединений (DB_POOL_SIZE=2, DB_MAX_OVERFLOW=0); если одновременно запускаются локальный backend, сервер и CLI, сумма их пулов должна оставаться ниже лимита Supabase.

Ревизия модели содержит метаданные и бинарный артефакт в PostgreSQL; локальный файл в MODEL_DIR — кэш и резервная копия. Другой ПК с доступом к той же БД может выбрать ревизию без копирования файлов. Модель не предсказывает для турбины без обучающих строк; сначала импортируйте её CSV и переобучите модель. Все операции просмотра и записи доступны без ключа оператора как локально, так и на публичном сайте.

Публичная запись сейчас открыта намеренно: любой посетитель может добавить турбину, загрузить CSV, переобучить модель и создать прогноз. Это демо-режим; для долгосрочной эксплуатации нужны аутентификация, ограничения запросов, защита загрузок и отдельные права БД.

Серверный запуск

Публичная версия: healthcore.ych.kz. API размещается в /opt/healthcore/api, собранный frontend — в /opt/healthcore/ui. На Debian 12 устанавливаются Python 3.11, Node.js 22, nginx, Certbot и выбранная БД. Сервисный файл deploy/healthcore-api.service запускает Uvicorn от отдельного пользователя healthcore только на loopback; nginx проксирует /api/ и обслуживает статический сайт по HTTPS. Полный повторяемый процесс с выбором Supabase либо собственного PostgreSQL — в deploy/README.md.

Для Supabase на сервере строку подключения размещают только в /opt/healthcore/api/.env, заменяя DATABASE_URL; для собственного PostgreSQL указывают локальный URL. После переключения БД нужна повторная инициализация схемы и импорт данных. Содержимое .env нельзя отправлять в Git. Переключение с SQLite на PostgreSQL не переносит данные автоматически.

Ограничения

Нет февральских фактических значений для итоговой метрики. Нет автоматической очереди/периодического пересчёта через API, версионируемых миграций, калибровки смещения измеренного ветра к ECMWF. Полный февральский прогон доступен как возобновляемая CLI-команда, не как фоновая задача интерфейса. Open-Meteo возвращает выбранный архивный выпуск, но не подтверждает точный момент его публикации. Выбор выпуска за 12:00 UTC предыдущего дня оставляет около семи часов запаса до решения; для строгой проверки доступности требуется независимый журнал публикации. Метрика MAE на измеренном ветре не является оценкой точности реальных февральских прогнозов. Часовой пояс исторических CSV не подтверждён поставщиком.

В текущей общей БД ранее сохранённые прогоны за 31 января с прежними моделями помечены invalid_lookahead; их точки сохранены для аудита, но интерфейс запрещает экспорт как достоверного результата. Прогноз с новой ревизией, обученной строго до 31 января, сохранён отдельно.

Команда и вклад

HEALTH CORE — команда проекта Wind Power Forecasting.

  • Андрей: backend, БД, импорт, модель, погодный API, сервер, воспроизводимый запуск и README.
  • Михаил: UX/UI, frontend, интеграция с API, сценарий демонстрации и продуктовая проверка соответствия кейсу.

План команды — в PLAN.md, контракт интерфейса и сервера — в INTEGRATION.md. Исходный кейс и данные в TZ/, справочные материалы в docs/ включены в Git для воспроизводимости установки; репозиторий должен оставаться закрытым и доступным только команде и организаторам. Локальный .env, ключи и HACKATHON_TZ.md не публикуются.

Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
49
Авторы
2
Файлы
67
Ветки
5

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

Ussenko Andrey 35MG-Trener 14

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

  • 2026-09-23 17:55Объединить правки README и карту рельефаUssenko Andrey
  • 2026-09-23 17:54Показать рельеф и обе турбины на карте и в READMEUssenko Andrey
  • 2026-09-23 17:46docs: remove outdated Git note from READMEMG-Trener
  • 2026-09-23 17:43Обновить README для жюри и добавить скриншоты демоUssenko Andrey
  • 2026-09-23 17:25Упорядочить разделы бокового менюUssenko Andrey
  • 2026-09-23 17:14Добавить исходные данные и материалы хакатона в закрытый репозиторийUssenko Andrey
  • 2026-09-23 17:14Убрать ключ оператора из API и интерфейсаUssenko Andrey
  • 2026-09-23 17:06docs: добавить ссылку на демо в начало READMEUssenko Andrey
Все коммиты (49)
  • 2026-09-23 17:03fix: интерфейс получает причину недоступности PostgreSQLUssenko Andrey
  • 2026-09-23 17:02fix: повторная подготовка при неполной ревизии моделиUssenko Andrey
  • 2026-09-23 17:00merge: объединены автоматическая подготовка модели и карта ветраUssenko Andrey
  • 2026-09-23 16:59feat: интерфейс показывает готовность прогноза и причины блокировкиUssenko Andrey
  • 2026-09-23 16:59feat: автоматический импорт CSV и обучение модели при первом запускеUssenko Andrey
  • 2026-09-23 16:49Add interactive archived wind overlay to turbine mapMG-Trener
  • 2026-09-23 16:46perf: история прогнозов загружается одним запросомUssenko Andrey
  • 2026-09-23 16:44fix: ограничен пул соединений с SupabaseUssenko Andrey
  • 2026-09-23 16:41feat: история прогнозов сохраняется между сессиямиUssenko Andrey
  • 2026-09-23 16:35test: изолирована проверка API от серверного ключа записиUssenko Andrey
  • 2026-09-23 16:31docs: описаны тестовый диапазон и развёртывание двух вариантов PostgreSQLUssenko Andrey
  • 2026-09-23 16:23feat: добавлены проверка модели, прогон февраля и защита записиUssenko Andrey
  • 2026-09-23 16:16Brand site as Wind Power ForecastingMG-Trener
  • 2026-09-23 16:04Fix Google Maps initialization raceMG-Trener
  • 2026-09-23 15:59Fix geography page and document Maps setupMG-Trener
  • 2026-09-23 15:53Integrate geography view with current dev APIMG-Trener
  • 2026-09-23 15:49Merge remote-tracking branch 'origin/dev' into devMG-Trener
  • 2026-09-23 15:45Add Google Maps geography viewMG-Trener
  • 2026-09-23 15:45Описать формулы показателей и путь обучения моделиUssenko Andrey
  • 2026-09-23 15:39Исключена утечка будущих данных и обновлены инструкцииUssenko Andrey
  • 2026-09-23 15:27Добавлены интерфейс данных и общие ревизии моделейUssenko Andrey
  • 2026-09-23 15:18Добавлены загрузка CSV, турбины и ревизии модели в APIUssenko Andrey
  • 2026-09-23 15:00Согласовать запуск Supabase с обновлением интерфейсаUssenko Andrey
  • 2026-09-23 14:59fix: добавлен запуск API с готовой БД Supabase без повторного DDLMG-Trener
  • 2026-09-23 14:59Объединить интерфейс и улучшение запуска Supabase в devUssenko Andrey
  • 2026-09-23 14:58Ускорить повторный запуск БД и показать активный backendUssenko Andrey
  • 2026-09-23 14:53merge: обновлён интерфейс аналитики в общей ветке devMG-Trener
  • 2026-09-23 14:49feat: улучшена читаемость аналитики и добавлена сворачиваемая навигацияMG-Trener
  • 2026-09-23 14:32Уточнить подключение Supabase и проверку адреса БДUssenko Andrey
  • 2026-09-23 14:29Добавить проверяемый перенос SQLite в SupabaseUssenko Andrey
  • 2026-09-23 14:21Добавить повторяемую инициализацию БД и инструкцию локального запускаUssenko Andrey
  • 2026-09-23 14:14docs: уточнён статус продуктовой спецификацииUssenko Andrey
  • 2026-09-23 14:12merge: добавлена спецификация Михаила и согласован контрактUssenko Andrey
  • 2026-09-23 14:11merge: объединён интерфейс Михаила с backend АндреяUssenko Andrey
  • 2026-09-23 14:04test: проверены сценарии аналитики и добавлена инструкция запускаMG-Trener
  • 2026-09-23 14:04feat: добавлен интерактивный интерфейс аналитики ВЭСMG-Trener
  • 2026-09-23 14:03docs: указаны источники и лицензии компонентовUssenko Andrey
  • 2026-09-23 14:01ops: подготовлен серверный запуск API и инструкцииUssenko Andrey
  • 2026-09-23 13:53fix: исправлен импорт времени и закреплены зависимостиUssenko Andrey
  • 2026-09-23 13:50docs: добавлена продуктовая спецификация фронтенда ВЭСMG-Trener
  • 2026-09-23 13:49feat: добавлен каркас прогноза ВЭС и план разработкиUssenko Andrey
Структура репозитория (67 файлов)
.env.example
.gitignore
FRONTEND_PRODUCT_SPEC.md
INTEGRATION.md
PLAN.md
README.md
TZ/Dataset HackAlemAI для участников 11.03.2023-28.02.2026 - turbine 1.csv
TZ/Dataset HackAlemAI для участников 11.03.2023-28.02.2026 - turbine 2.csv
TZ/HackAlem AI_ Agentic AI для прогнозирования выработки ВЭС.docx
backend/__init__.py
backend/app/__init__.py
backend/app/bootstrap.py
backend/app/db.py
backend/app/main.py
backend/app/pipeline.py
backend/app/turbines.py
backend/app/weather.py
backend/cli.py
backend/evaluate.py
backend/forecast_range.py
backend/generate_demo_csv.py
backend/migrate_sqlite_to_postgres.py
backend/tests/__init__.py
backend/tests/test_api.py
backend/tests/test_bootstrap.py
backend/tests/test_db.py
backend/tests/test_migration.py
backend/tests/test_pipeline.py
backend/tests/test_startup.py
deploy/README.md
deploy/healthcore-api.service
deploy/nginx-healthcore.conf
deploy/server.env.example
docs/01-participation-eligibility-rules-ru.pdf
docs/02-participant-instructions-ru.pdf
docs/03-openai-nvidia-activation-ru.pdf
docs/04-equipment-preparation-ru.pdf
docs/05-cybersecurity-guidelines-ru.pdf
docs/06-asu-online-course-ru.pdf
docs/google-doc-index.md
docs/regulations-summary-2026-09-22.md
docs/research-notes-2026-09-22.md
docs/screenshots/forecast-dashboard.png
docs/screenshots/wind-map.png
docs/text/01-participation-eligibility-rules-ru.txt
docs/text/02-participant-instructions-ru.txt
docs/text/03-openai-nvidia-activation-ru.txt
docs/text/04-equipment-preparation-ru.txt
docs/text/05-cybersecurity-guidelines-ru.txt
docs/text/06-asu-online-course-ru.txt
docs/user-brief-2026-09-22.md
frontend/.env.example
frontend/README.md
frontend/index.html
frontend/package-lock.json
frontend/package.json
frontend/public/brand-mark.svg
frontend/src/GeographyMap.js
frontend/src/main.js
frontend/src/model.js
frontend/src/style.css
frontend/src/windWeather.js
frontend/tests/browser.cjs
frontend/tests/model.test.js
frontend/tests/windWeather.test.js
frontend/vite.config.js
requirements.txt
Ветки (5)

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

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

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