agrovision / backend /api /ndvi.py
alexp97's picture
feat(ui): plataforma Shiny de 6 módulos + soporte backend para la UI (Fase 6)
820f460
Raw
History Blame Contribute Delete
3.36 kB
"""
Archivo: ndvi.py
Fecha de modificación: 03/06/2026
Autor: Equipo AgroVisión
Descripción:
Router de Teledetección (NDVI). `POST /api/ndvi` devuelve la serie mensual: si se pasa
`field_id`, la lee persistida (rápida); si se pasa `geojson`, la calcula en vivo vía
Sentinel Hub (default últimos 5 años). `POST /api/ndvi/raster` devuelve el heatmap NDVI
(PNG ~10 m/px) para pintar como capa en el mapa.
Estructura Interna:
- `POST /api/ndvi` (serie), `POST /api/ndvi/raster` (heatmap PNG).
Entradas / Dependencias:
- `backend.services.remote_sensing`, `backend.db.repositories`, `backend.api.deps`.
Ejemplo de Integración:
from backend.api.ndvi import router
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Response
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from backend.api.deps import UserKeys, get_db, get_user_keys
from backend.db import repositories as repo
from backend.services import remote_sensing
router = APIRouter(prefix="/api/ndvi", tags=["teledetección"])
class NdviRequest(BaseModel):
"""Petición de serie NDVI: por parcela (persistida) o por geometría (en vivo)."""
field_id: str | None = None
geojson: dict | None = None
start: str | None = None
end: str | None = None
class NdviRasterRequest(BaseModel):
"""Petición de heatmap NDVI (PNG) por parcela (`field_id`) o geometría directa."""
field_id: str | None = None
geojson: dict | None = None
start: str | None = None
end: str | None = None
def _require_copernicus(keys: UserKeys) -> None:
"""Lanza 400 si faltan las credenciales de Copernicus para una consulta en vivo."""
if not (keys.copernicus_id and keys.copernicus_secret):
raise HTTPException(
status_code=400, detail="Faltan credenciales de Copernicus (pestaña Credenciales)."
)
@router.post("")
async def ndvi_series(
body: NdviRequest,
keys: UserKeys = Depends(get_user_keys),
session: AsyncSession = Depends(get_db),
) -> dict:
"""Devuelve la serie NDVI mensual (persistida por parcela o calculada en vivo)."""
if body.field_id:
series = await repo.get_ndvi_series(session, body.field_id)
return {"series": series}
if body.geojson:
_require_copernicus(keys)
series = await remote_sensing.ndvi_series_monthly(
body.geojson, body.start, body.end, keys.copernicus_id, keys.copernicus_secret
)
return {"series": series}
raise HTTPException(status_code=422, detail="Indica 'field_id' o 'geojson'.")
@router.post("/raster")
async def ndvi_raster(
body: NdviRasterRequest,
keys: UserKeys = Depends(get_user_keys),
session: AsyncSession = Depends(get_db),
) -> Response:
"""Devuelve el heatmap NDVI (PNG ~10 m/px) por parcela (`field_id`) o geometría."""
_require_copernicus(keys)
geojson = body.geojson
if geojson is None and body.field_id:
geojson = await repo.get_field_geojson(session, body.field_id)
if geojson is None:
raise HTTPException(status_code=422, detail="Indica 'field_id' o 'geojson'.")
png = await remote_sensing.ndvi_heatmap_png(
geojson, keys.copernicus_id, keys.copernicus_secret, body.start, body.end
)
return Response(content=png, media_type="image/png")