Skip to content

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

130 Commits

Folders and files

Repository files navigation

Космохакатон (Ростов): мониторинг вегетационной динамики

Веб-сервис для мониторинга состояния сельскохозяйственных полей по спутниковым данным: выбор региона и полигона на карте, автоматический сбор спутниковых и метеоданных, восстановление пропусков во временном ряде NDVI и детекция аномальных периодов вегетации.

Запуск всего продукта одной командой: docker compose up --build → http://localhost:8000/

Стек. Бэкенд: Python 3.14, FastAPI, LightGBM, PyTorch, odc-stac/rasterio, pandas. Интерфейс: React 19, Vite, TypeScript, MUI X Charts, AntV L7 (карта на тайлах OpenStreetMap), GSAP, TanStack Query. Окружение и зависимости: uv (pyproject.toml + uv.lock) и npm (web/package-lock.json), образ — Docker.

Быстрый старт для проверяющего

Ключи, регистрации и обучение моделей не нужны: веса лежат в репозитории, внешние каталоги данных открытые.

docker compose up --build                                   # 1. весь продукт → http://localhost:8000/
docker compose exec app uv run --no-sync python -m gapfill.predict_improved --output artifacts/submission.csv --device cpu   # 2. batch-инференс

Без Docker: uv sync --group service --group torch, затем cd web && npm ci && npm run build && cd .. и uv run uvicorn service.app:app --port 8000. Проверить метрику задачи 1 за доли секунды — uv run --no-sync python -m gapfill.metrics. Что смотреть по шагам: экран «Новая территория» (карта → контуры OSM или свой полигон → сбор данных), затем экран «Поле» (ряд NDVI, восстановленные точки, эпизоды и объяснения). Слайды защиты — presentation/index.html.

Документация

Полное ТЗ, критерии оценки, чек-лист сдачи и отчёты — в папке docs/. Рабочие заметки для команды и Claude Code — в CLAUDE.md.

Разведочный анализ (EDA)

uv sync                         # Python 3.14 + зависимости из pyproject.toml / uv.lock
uv run python -m eda.run_all    # графики в reports/eda/figures/, числа в reports/eda/summary.json

Выводы и графики — в docs/08-eda-report.md. Скрипты первого прохода анализа и сборка дашборда — в eda/v1/.

Интерактивный дашборд «NDVI-атлас полей»: reports/dashboard/dashboard.html (самодостаточный файл, открывается двойным кликом; пересборка uv run python eda/v1/build_dashboard_data.py).

Восстановление пропусков primary_ndvi (задача 1)

Пакет gapfill/: признаки по ряду полигона, циклам съёмки и «шуму дня» других полигонов, LightGBM + нейросеть по сезонной сетке (SeasonNet), смесь 0.5/0.5. Метод, валидация и результаты — в docs/12-gapfill-model.md, журнал экспериментов — в experiments/.

Входные данные. data/test_features_new.csv — вторая версия private_features.csv (обновление организаторов 2026-09-05: 20 полигонов, история 2010–2024, 2 323 контрольные точки); по ней считается метрика. Первая версия data/test_dataset.csv больше не оценивается и используется только как дополнительные известные точки (параметр --extra; чтобы отключить — --extra без значений). Сравнение версий — docs/03. Выход — submission.csv (anon_polygon_id,date,primary_ndvi_true — так требует платформа проверки, в ТЗ было primary_ndvi_pred, только строки is_synthetic_gap = True).

Инференс сданной модели (ничего обучать не нужно, веса models/improved/ лежат в репозитории):

uv sync --frozen --no-default-groups --group infer --group torch
uv run --no-sync python -m gapfill.predict_improved --output submission.csv --device cpu   # 2 323 строки
uv run --no-sync python -m gapfill.metrics                                                 # RMSE и GapScore за 0.05 с

Обучение финальной модели с нуля (exp-008; на RTX 5060 Ti около полутора часов, подробности и промежуточные проверки — docs/16):

UV_TORCH_BACKEND=cu130 uv sync --group ml --group dl
# 1. подбор и проверка на отложенной маске 777
uv run --no-sync python -m gapfill.research.train --val-seed 777 --n-masks 30 --modes all --kriging --rounds 8500 --threads 5 --out kriging30_777
uv run --no-sync python -m gapfill.research.nn --kind residual --epochs 260 --schedule-epochs 400 --fixed-epoch 260 --hidden 96 --val-seed 777 --out nn_residual777
uv run --no-sync python -m gapfill.research.uncertainty --model artifacts/research/kriging30_777/all/model.txt --n-masks 30 --val-seed 777 --kriging --out uncertainty_kriging777
uv run --no-sync python -m gapfill.research.blend --model artifacts/research/kriging30_777/all/model.txt --nn nn_residual777 --val-seed 777 --weights 0.6 --sigma 0.04 --calibration posterior --uncertainty artifacts/research/uncertainty_kriging777/model.txt --out posterior_blend777
# 2. обучение на всех известных точках и сборка пакета весов models/improved/
uv run --no-sync python -m gapfill.research.final --source artifacts/research/kriging30_777/all/result.json --n-masks 30 --rounds 8500 --seeds 42 137 --threads 5 --out final_kriging --uncertainty
uv run --no-sync python -m gapfill.research.nn --kind residual --epochs 260 --schedule-epochs 400 --hidden 96 --final --seed 0 --out final_nn_s0
uv run --no-sync python -m gapfill.research.package --lgb final_kriging --nn final_nn_s0 --calibration posterior

Предыдущая конфигурация exp-007 (смесь LightGBM + SeasonNet 0.5/0.5, веса в models/) обучается так:

uv run python -m gapfill.train --n-masks 20 --clip -0.1 1.0 --out lgb_v4     # валидация LightGBM (RMSE 0.055)
uv run python -m gapfill.nn_model --epochs 600 --dropout 0.25 --out nn_v4    # валидация SeasonNet (RMSE 0.059, GPU)
uv run python -m gapfill.ensemble lgb_v4 nn_v4                               # смесь на валидации (RMSE 0.054)
uv run python -m gapfill.predict --input data/test_features_new.csv --output submission.csv --n-masks 30 --rounds 5500 --seeds 0 1 2 --out final_lgb
for s in 0 1 2 3 4; do uv run python -m gapfill.nn_model --final --epochs 600 --dropout 0.25 --seed $s --out final_nn; done
uv run python -m gapfill.make_submission final_lgb:0.5 final_nn:0.5          # → submission.csv

Свой файл private_features.csv. Калибровка привязана к опубликованным организаторами историческим агрегатам и проверяет контрольные суммы исходных train/extra, поэтому на другом наборе данных запускайте инференс без неё, иначе будет ValueError:

uv run --no-sync python -m gapfill.predict_improved --no-calibration --input <ваш_файл>.csv --output submission.csv --device cpu

Seed 42 (LightGBM — дополнительно 137), гиперпараметры финала: LightGBM l2, lr 0.03, 63 листа, min_data_in_leaf 40, feature_fraction 0.6, 8 500 итераций, 427 признаков; ResidualSeasonNet — 96 каналов, 260 эпох, расписание на 400. Проверка окружения — uv run pytest tests -q; полный набор из 182 тестов проходит на окружении uv sync --group infer --group serve --group geo --group agent --group torch (без группы geo падают тесты сбора, без agent — тесты агента, без собранного web/dist — тест клиентских маршрутов).

Готовый submission.csv лежит в корне — это предсказания финальной модели exp-008 (для первой версии test — reports/gapfill/submission_v1_test_dataset.csv, предыдущая версия — reports/gapfill/submission_prev_exp007.csv).

Результаты на отложенной выборке (15 % известных точек скрыты как контрольные, 9 199 точек; это не приватный лидерборд):

Вариант RMSE GapScore
baseline «среднее двух соседей» (ТЗ) 0.093 2.1
ансамбль exp-007 (LightGBM + SeasonNet, 0.5/0.5) 0.0553 13.41
финал exp-008 (kriging-признаки + остаточная сеть + калибровка) 0.0437 16.88

Разброс по трём маскам (seed 2026 / 777 / 999) — 0.0036. Пересчёт метрики — uv run python -m gapfill.metrics.

Улучшенная модель — exp-008

Отдельный пакет models/improved/ содержит LightGBM с 427 признаками и ResidualSeasonNet. Исходные models/ и submission.csv сохранены; веб-сервис продолжает использовать прежние артефакты. Разбор обучения, контрольных масок и ограничений — отчёт.

uv sync --frozen --no-default-groups --group infer --group torch
uv run --no-sync python -m gapfill.predict_improved --output submission.csv --model-only-output submission_model.csv
# Только обычный ML, без опубликованных исторических агрегатов:
uv run --no-sync python -m gapfill.predict_improved --no-calibration --output submission_model.csv

Какой файл сдаётся. Конкурсный — submission.csv (с калибровкой, GapScore 16.88 на отложенной выборке). Рядом лежит submission_model.csv — тот же ансамбль без калибровки (13.41): его отправляем, если организаторы сочтут калибровку недопустимой. Калибровка использует опубликованные ими же исторические mean/std из первой версии data/test_dataset.csv; эти агрегаты несут информацию о скрытых значениях второй версии, поэтому приём калибровки — вопрос к организаторам (docs/15), а не техническое ограничение. На новые поля она не переносится: инференс на чужом файле запускается с --no-calibration.

Про «невозможные» значения. В калиброванном файле есть два предсказания вне диапазона [−0.1, 1] (минимум −0.15, максимум 2.03). Это не ошибка формата: в самих данных кейса есть истинные primary_ndvi до 1.84 и до −2.13 (артефакты съёмки, попавшие в разметку), а калибровка по агрегатам как раз и восстанавливает такие выбросы. Обрезка проверена на отложенной выборке и только ухудшает метрику: [−0.5, 1.0] даёт 16.80, [−0.1, 1.0] — 16.67 против 16.88 без обрезки, потому что рядом с этими прогнозами стоят такие же выбросы в ответах. Локальные метрики и контрольные прогнозы — в reports/gapfill/improvement/.

На macOS, если LightGBM не находит libomp.dylib, перед запуском:

export DYLD_LIBRARY_PATH="$PWD/.venv/lib/python3.14/site-packages/torch/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"

Детекция и интерпретация аномалий (задача 2)

Пакет anomaly/: гармонизированная кривая сезона, норма по истории полигона (или по культуре), эпизоды «устойчивого и/или сильного» отклонения (Z < −1), причина по правилам с погодой ERA5, фенологией и региональным контекстом, текст на русском по правилам. Интерфейс показывает эпизоды и объяснения по правилам. Метод и проверка — в docs/13-anomaly-detection.md, результаты — в reports/anomalies/ (episodes.csv, seasons.csv, figures/).

uv run python -m anomaly.run                                 # все полигоны → reports/anomalies/
uv run python -m anomaly.evaluate reports/anomalies          # прокси-метрики детектора
uv run python -m anomaly.plots AOI-0065:2024 AOI-0043:2019   # графики сезонов с эпизодами

Веб-сервис (обязательная точка запуска по ТЗ)

Запуск всего продукта одной командой

docker compose up --build      # соберёт интерфейс и бэкенд, поднимет http://localhost:8000/

Образ собирается в два этапа: node:24-alpine собирает интерфейс (web/), затем образ uv с Python 3.14 ставит зависимости (infer, geo, serve плюс CPU-сборка torch) и получает готовую статику. Поля, добавленные пользователем, лежат в именованном томе и переживают перезапуск. Нужен только интернет для внешних каталогов данных; ключи и регистрация не требуются.

Batch-инференс в том же контейнере:

docker compose exec app uv run --no-sync python -m gapfill.predict_improved   --output artifacts/submission.csv --device cpu

Вопросы о поле и отчёт

На экране поля есть панель «Спросить про поле»: вопрос своими словами, ответ — по тем же числам, что показаны на экране. С ключом OLLAMA_API_KEY (uv sync --group agent) отвечает языковая модель Ollama Cloud, у которой есть три инструмента: сводка сезона, периоды снижения и метод. Ключ кладётся в .env (см. .env.example), модель по умолчанию — gemma4:31b. Без ключа отвечает разбор по правилам — теми же фактами, только без связного текста. Модель не считает NDVI и не видит сырых данных.

Кнопка «Отчёт для печати» открывает GET /api/report/{pid} — самодостаточный HTML без внешних ссылок, с графиком сезона в SVG. Он свёрстан под печать, поэтому PDF получается прямо из браузера: «Печать» → «Сохранить как PDF».

Объяснения периодов снижения тоже пишет модель, но не в момент анализа: она отвечает десятками секунд на эпизод, поэтому анализ отдаёт текст по правилам сразу, а объяснения догружаются фоном (GET /api/explanations/{pid}?year=) и подменяются в карточках, когда готовы.

MCP-инструменты

Те же данные доступны любому клиенту MCP — например, чтобы спрашивать про поля прямо из Claude Code:

claude mcp add vegetation -- uv run --no-sync python -m mcp_server

Шесть инструментов: list_fields, field_summary, find_episodes, ask_about_field, field_report, solution_metrics. Сбор данных по новой территории в MCP намеренно не вынесен: он идёт минуты и требует сети, для него есть POST /api/analyze.

Пересчёт метрики задачи 1

uv run --no-sync python -m gapfill.metrics

Считает RMSE и GapScore по сохранённым предсказаниям отложенной выборки (reports/gapfill/improvement/validation_*.csv) — без обучения и инференса, за доли секунды. Обновляет reports/gapfill/validation.json, откуда числа берёт интерфейс. Заголовочная цифра — модель с калибровкой историческими агрегатами, рядом всегда показывается результат чистого ансамбля. Это отложенная выборка, а не приватный лидерборд.

Запуск без Docker

uv sync --group ml --group geo --group service --group torch   # FastAPI, STAC-клиенты, rasterio, Open-Meteo, torch (CPU)
uv run python -m anomaly.run                                # один раз: эпизоды для полигонов кейса
cd web && npm ci && npm run build && cd ..                  # интерфейс (Node 20+); без этого шага откроется резервный HTML
uv run uvicorn service.app:app --host 127.0.0.1 --port 8000

Открыть http://127.0.0.1:8000/. Для разработки интерфейса отдельно: cd web && npm run dev (порт 5173, запросы /api проксируются на 8000). Если сборки web/dist нет, сервис отдаёт простой резервный интерфейс на одном HTML-файле (он же всегда доступен по адресу /legacy).

Интерфейс

React 19 + Vite, графики — MUI X Charts, карта — AntV L7 (WebGL) на тайлах OpenStreetMap, анимации — GSAP. Иконочных шрифтов нет: все иллюстрации нарисованы в проекте (web/src/components/art/). Экраны:

Экран Что показывает
Обзор Метрики решения, причины угнетения по всем полям, список полей кейса с поиском и фильтром
Поле Сезон по годам: наблюдения по сенсорам, восстановленная кривая, норма ±1σ, восстановленные контрольные точки, Z-score, погода ERA5, эпизоды с объяснением
Новая территория Карта, поиск контуров OSM, рисование полигона, сбор данных, набор «Мои поля»
Аномалии Мои поля / поля кейса / все: сезон, поиск по имени, компактный список полей с раскрытием эпизодов и переходом к периоду на графике
Как это работает Пайплайн, метрики обеих задач, источники данных

Альтернативный запуск бэкенда — uv run --locked --group service python -m service: тот же сервер плюс проверка зависимостей автосбора при старте, готовность — GET /api/health. Повторный запуск без синхронизации — uv run --no-sync python -m service. Эпизоды полигонов кейса уже включены в репозиторий; пересчёт при необходимости — uv run --no-sync python -m anomaly.run.

Резервный интерфейс берёт графики из установленного пакета Plotly через /vendor/plotly.min.js. Поиск контуров OSM не импортирует спутниковый сборщик. Для Sentinel-2 выбираются публичные HTTPS COG; JP2-дубликаты и сцены, доступные только через S3 с авторизацией, исключаются до чтения. При отказе Overpass поиск переключается между публичными серверами VK Maps, Private.coffee и overpass-api.de; успешные ответы по рамке карты кэшируются до пяти минут. При слишком широком обзоре кнопка поиска сама приближает карту вокруг её центра до допустимого размера области.

Разбор отклонений

На /anomalies по умолчанию открываются Мои поля из «Новой территории» и последний доступный сезон. Один ряд — одно поле; внутри можно выбрать эпизод, прочитать возможную причину и рекомендацию. Числа и методика скрыты под раскрытием. «Показать период на графике» открывает правильный отчёт, год и временное окно. Источник, сезон, поиск и фильтр сохраняются в URL и восстанавливаются после «Назад». Поля без найденных отклонений остаются в списке; отсутствие сезона или оценки обозначается отдельно.

GET /api/anomaly-fields?source=mine|case|all&year=2025 возвращает {years, year, fields}. Год необязателен (последний доступный для источника), источник по умолчанию mine. Каждое поле содержит имя, pid, uid, источник, доступные годы, has_season, статус level и эпизоды выбранного года. Сохранённые отчёты читаются из того же набора, что карта, с удалением дублей по pid; спутниковый сбор и пересчёт модели не запускаются. Статусы: high, medium, data, context, unknown, clear. Историческая аномалия не означает текущую угрозу; причина остаётся гипотезой.

Поиск места на карте

В «Новая территория» введите город и улицу, полный адрес, населённый пункт, область, название объекта или координаты 47.22, 39.72 (широта, долгота), затем нажмите Enter или «Найти». Выберите один из результатов: карта покажет место и его окрестности, в адресе видны район и регион. Улица может состоять из нескольких участков и проходить через разные районы. После перехода выберите готовый контур поля или нарисуйте его. Найденный адрес не превращается в сельскохозяйственный полигон.

API: GET /api/places?q=..., от 2 до 200 символов. Ответ — список {id, label, center, bbox, address, kind}; center — [долгота, широта], bbox — [запад, юг, восток, север] или null для координат. Ошибки: 400/422 — некорректный ввод, 429 — повторить через две секунды, 502 — источник недоступен.

Поиск работает через Nominatim на бэкенде; координаты обрабатываются локально. Согласно правилам публичного Nominatim, запросы отправляются только по действию пользователя, без автодополнения; общий SQLite-лимит — один запрос за 1.1 секунды, кэш — до 512 запросов на 24 часа. Переменные NOMINATIM_URL (полный URL /search) и GEOCODING_USER_AGENT позволяют заменить провайдера и идентификацию без изменения кода; они также передаются через Docker Compose. GEOCODING_CACHE задаёт путь к SQLite, по умолчанию artifacts/service/geocoding.sqlite. Все процессы должны использовать один файл кэша; для нескольких машин нужен собственный провайдер или общий прокси с ограничением запросов. Проверки и запуск e2e — tests/e2e/README.md.

Два сценария из ТЗ:

  1. Готовый полигон: в «Новая территория» кнопка «Найти поля OSM в видимой области» запрашивает landuse=farmland из OpenStreetMap (Overpass API), клик по контуру выбирает поле.
  2. Произвольный полигон: контур рисуется на карте (@antv/l7-draw).

После выбора «Собрать данные и проанализировать» сервис сам получает Sentinel-2 L2A, Landsat 8/9 C2 L2 и MODIS MOD13Q1 (Planetary Computer STAC; для Sentinel-2 маска облаков по SCL, запасной каталог — Earth Search), ERA5 (Open-Meteo, по центроиду поля), строит ряд primary_ndvi по приоритету S2 → Landsat → MODIS, восстановленную кривую, норму по собственной истории поля, находит эпизоды угнетения и объясняет их. Четыре источника грузятся одновременно, внутри каждого — по восемь сезонов параллельно; из каталогов берутся сцены с облачностью до 60 % (более облачные всё равно не проходят порог чистых пикселей). GDAL настроен под чтение COG из облака (configure_rio(cloud_defaults=True): без листинга каталога на каждую сцену, с объединением диапазонов), это главное ограничение скорости: сотни HTTP-запросов к хранилищам в США. Ход сбора виден в интерфейсе: клиент задаёт идентификатор задания (job) и опрашивает GET /api/analyze/progress/{job}, где сервер отдаёт число загруженных сезонов и сцен по каждому источнику, этап и последние строки журнала (файл artifacts/service/progress/<job>.json, который пишет процесс сборщика). Если источник недоступен, ряд строится по остальным, а в интерфейсе выводится, что именно собрано.

Проанализированные поля попадают в набор «Мои поля» (artifacts/service/polygons/*.json, не в git): их можно открыть без повторного сбора данных и удалить; на карте контур раскрашен по состоянию последнего сезона (зелёный — норма, оранжевый — умеренное угнетение, красный — критическое), клик по контуру открывает поле. Полигоны кейса (78 анонимных AOI-xxxx, координат нет) показываются списком: исходные наблюдения по сенсорам, восстановленные контрольные точки из submission.csv (и из reports/gapfill/submission_*.csv для первой версии test), кривая, норма ±1σ, Z-score, погода ERA5 и эпизоды с причинами.

API: GET /api/health, GET /api/polygons, GET /api/polygon/{pid}, GET /api/episodes?year=&cause=&severity=, GET /api/summary, GET /api/meta, GET /api/fields?bbox=юг,запад,север,восток, POST /api/analyze ({"geometry": <GeoJSON Polygon>, "name": "...", "start_year": 2019, "end_year": 2025}), набор пользователя GET /api/user-polygons, GET|DELETE /api/user-polygons/{uid}. Объяснение эпизодов языковой моделью включается переменной окружения OLLAMA_API_KEY (uv sync --group agent); без неё текст формируется по правилам.

Простой язык для фермера

Экран поля начинается со «светофора»: зелёная, жёлтая или красная плашка с заголовком («Всё в норме», «Стоит присмотреться», «Поле отстаёт») и одной фразой-выводом, например «Поле сильно отстаёт с 15 авг по 28 сен: скорее всего из-за засухи или жары». Под ней три вопроса: «Как поле сейчас?», «Что было в этом сезоне?», «Что делать?». Карточки показателей пишут крупно фразу («Дождей за месяц вдвое меньше нормы», «Сезон опережает обычный примерно на 8 дней»), а число и отклонение от нормы — мелким шрифтом. Карточка эпизода начинается с фразы, сравнения с соседями словами и совета; Z, уверенность в процентах, аргументы и технический текст свёрнуты в «подробности для агронома». Все формулировки собираются на клиенте из уже посчитанных данных (web/src/lib/plain.ts, плашка — components/panels/FieldVerdict.tsx); детектор и модель не меняются. Уверенность переводится в слова: ≥ 0.8 «почти наверняка», ≥ 0.6 «скорее всего», иначе «возможно». Разница накопленного тепла переводится в дни делением на 20 °C·дней в сутки.

Дополнительные погодные графики

На экране поля слева — графики NDVI и погоды с переключателем, справа — периоды, когда поле отставало. Сверху — светофор поля, последнее наблюдение NDVI, осадки, накопленное тепло и сухой период. Панели прокручиваются внутри экрана; карта Sentinel-2 раскрывается под NDVI. Выбор контура и список сохранённых полей доступны на вкладке «Новая территория», включая поля прежнего интерфейса. Наведение связывает даты двух графиков и показывает значения в карточках. Кнопка в эпизоде приближает соответствующий период на обоих графиках; «Весь сезон» возвращает исходный масштаб.

Режимы погодного графика:

  • Осадки за 30 дней, мм, со сравнением по прошлым годам. Под графиком — самый длинный сухой период сезона.
  • Накопленное тепло: сумма max(Tср − 10 °C, 0) от 1 апреля, °C·дни. Это общий показатель тепла сезона, без автоматического определения культуры или даты посева. Форма не требуется.

Сухой период — дни подряд с осадками менее 1 мм; пропуски разрывают серию. Если собрана ET₀, первый график можно переключить на осадки минус испарение за 30 дней.

Серая линия — среднее предыдущих лет (до 30), диапазон — 10–90-й процентили. Текущий год исключён, сравнение идёт по календарным датам. Показатели не изменяют детектор аномалий и модель NDVI. ET₀ учитывает температуру, радиацию, влажность и ветер; водный баланс не равен влажности почвы или потребности в поливе.

Источник: ERA5 через Open-Meteo, полные годы, средняя/минимальная/максимальная температура, осадки и ET₀. Пропуски остаются неизвестными. Для анонимных AOI используются имеющиеся средняя температура и осадки. ET₀ и Tmin/Tmax не выдумываются. Если погоды нет вообще, погодная часть скрывается. Z-score, исходные спутниковые точки, контрольные восстановления модели и температура/осадки по дням доступны в «Подробности для агронома: данные и расчёт». Основные наблюдения NDVI приведены к шкале Sentinel-2; исходные значения доступны при наведении и в подробностях. Объяснения эпизодов свёрнуты и формулируются как гипотезы. Формулы и сам детектор не изменены. Собранные поля доступны в прежнем списке по названию; отчёты и параметры графиков сохранены в artifacts/fields/, погодный кэш — в artifacts/weather/.

API графиков: GET /api/polygon/{pid}/agro?year=2025, POST /api/polygon/{pid}/agro (year, profile, sowing_date, base), POST /api/polygon/{pid}/weather-refresh. Формулы и границы изменений — в плане дополнения графиков. E2E-проверки через Playwright: tests/e2e/README.md.

Презентация

Дек защиты — presentation/index.html: 14 слайдов (проблематика, данные и EDA, baseline и его ограничения, эксперименты, результат задачи 1, логика детекции аномалий, три показательных сезона, пользовательский путь, автосбор, архитектура, ограничения и итог). Открывается в браузере без сборки и без интернета, ходит стрелками/пробелом, P — печать в PDF (один слайд = одна страница A4-landscape).

uv run python -m http.server 8899   # затем http://127.0.0.1:8899/presentation/index.html

Числа на слайдах взяты из reports/gapfill/validation.json и reports/anomalies/; графики сезонов — копии из reports/anomalies/figures/ в presentation/img/.

Зависимости

Базовые зависимости ставятся uv sync. Остальное разбито на группы в pyproject.toml (все версии актуальны на сентябрь 2026 и имеют wheels под Python 3.14):

Группа Что внутри Команда
ml scikit-learn, LightGBM, CatBoost, XGBoost, statsmodels, whittaker-eilers, optuna, shap uv sync --group ml
infer только LightGBM и scikit-learn — минимум для инференса и детекции, эту группу берёт образ Docker uv sync --group infer
dl torch, PyPOTS, pygrinder, chronos-forecasting (эксперименты) uv sync --group dl
torch только torch — хватает для инференса SeasonNet из models/ uv sync --group torch
geo pystac-client, odc-stac, stackstac, planetary-computer, rasterio, rioxarray, xarray, geopandas, shapely, earthengine-api, openmeteo-requests, osmnx, overpy uv sync --group geo
openeo клиент Copernicus Data Space (конфликтует с geo по xarray) uv sync --group openeo
serve только веб-слой: FastAPI, uvicorn, pydantic, httpx, Plotly, duckdb — эта группа идёт в образ Docker uv sync --group serve
service всё для разработки сервиса: serve + geo + ml uv sync --group service
agent pydantic-ai, anthropic, mcp uv sync --group agent
dev ruff, pytest, pytest-cov, mypy ставится по умолчанию

Индекс сборки torch задаётся переменной UV_TORCH_BACKEND: UV_TORCH_BACKEND=cu130 uv sync --group dl — сборка под CUDA 13.0 (RTX 5070, для обучения нейросети), UV_TORCH_BACKEND=cpu uv sync --group torch — CPU. В образе Docker torch ставится из индекса CPU отдельной строкой, чтобы в контейнер не попадали пакеты nvidia-*.

Обзор моделей, источников данных и обоснование выбора — в docs/09-open-source-landscape.md.

Системные зависимости: только uv (сам ставит Python 3.14 по .python-version); GDAL/PROJ приходят внутри wheels rasterio/pyproj, отдельная установка не нужна. GPU не обязателен: инференс gapfill.predict_saved, сервис и детекция аномалий работают на CPU; обучение SeasonNet на GPU (CUDA 13.0) занимает ~8 минут на seed, на CPU — дольше. Внешние сервисы (Planetary Computer, Earth Search, Open-Meteo, Overpass) — без ключей; единственная необязательная переменная окружения — OLLAMA_API_KEY.

Структура репозитория

.
├── README.md
├── CLAUDE.md               # рабочие заметки по проекту, окружению и данным
├── data/
│   ├── train_dataset.csv        # обучающий датасет (99 955 строк)
│   ├── test_features_new.csv    # тестовый датасет = private_features.csv, вторая версия (49 190 строк, 2 323 контрольные точки)
│   └── test_dataset.csv         # первая версия test (57 185 строк): только дополнительные известные точки
├── docs/                   # ТЗ, критерии, чек-лист, отчёты 08–19, исходные PDF в source/
├── eda/                    # модули разведочного анализа (uv run python -m eda.run_all)
│   └── v1/                 # скрипты первого прохода EDA и сборка дашборда
├── gapfill/                # восстановление primary_ndvi: признаки, LightGBM, SeasonNet, смесь, submission
│                           карта модулей — gapfill/README.md
├── anomaly/                # детекция и интерпретация аномалий: кривые, нормы, эпизоды, погода, причины
│                           карта модулей — anomaly/README.md
├── service/                # бэкенд: FastAPI (app.py), данные (data.py), сбор (collect.py), набор полигонов (polygons.py), сводка (meta.py), резервный UI (static/)
│                           карта модулей — service/README.md
├── mcp_server/             # MCP-инструменты поверх service/facts.py (claude mcp add vegetation)
├── web/                    # интерфейс: React 19 + Vite, MUI X Charts, AntV L7, GSAP (src/pages, src/components)
├── presentation/           # дек защиты: index.html (14 слайдов), style.css, deck.js, img/
├── scripts/                # вспомогательные скрипты (замер скорости автосбора)
├── experiments/            # журнал экспериментов (exp-000…008 задача 1, exp-100…105 задача 2, сервис и интерфейс)
├── tests/                  # pytest: ядро gapfill, детектор аномалий, набор полигонов сервиса
├── models/                 # готовые веса (~103 МБ): LightGBM (gzip) + SeasonNet (.pt), инференс без обучения
│   └── improved/           # веса финальной модели exp-008 (gapfill.predict_improved)
├── Dockerfile              # образ всего продукта: сборка интерфейса + сервис (uv, CPU)
├── docker-compose.yml      # запуск одной командой: docker compose up --build
├── submission.csv          # предсказания контрольных точек test (задача 1, вторая версия test)
├── reports/
│   ├── eda/                # графики и summary.json, генерируются EDA
│   ├── dashboard/          # template.html + собранный dashboard.html
│   ├── gapfill/            # submission для первой версии test (сверка с ответами организаторов)
│   └── anomalies/          # эпизоды, фенометрики сезонов, графики (генерирует anomaly.run)
├── artifacts/              # модели и кэш признаков (не в git)
├── pyproject.toml          # зависимости по группам (uv)
└── uv.lock

Структура и статистика датасетов описаны в docs/03-data.md.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages