API de Prediccion

Servicio de calculo predictivo con modelos matematicos intercambiables. No requiere login. No almacena datos. Solo calcula.

REST • JSON • Sin autenticacion

Que es esta API?

Un servicio que recibe datos historicos (fecha, talla, biomasa, densidad) y devuelve una serie de valores futuros calculados mediante modelos matematicos, Machine Learning o Deep Learning.

Endpoints

POST /predict

Endpoint principal. Recibe datos y devuelve predicciones.

GET /health

Verifica que la API esta funcionando. Retorna {"status": "ok"}.

GET /doc

Esta pagina de documentacion.

GET /config

Muestra la configuracion actual (modelo por defecto, filtros activos, rangos).

GET /config/models

Lista todos los modelos disponibles.

GET /config/filters

Lista todos los filtros de validacion disponibles.

Como usar POST /predict

1. Prepara tus datos

Necesitas un array de mediciones historicas. Cada medicion requiere fecha y talla. Los campos biomasa y densidad son opcionales.

2. Envia el request

{
  "datos": [
    {"fecha": "2024-01-01", "talla": 15.5, "biomasa": 120.0, "densidad": 0.8},
    {"fecha": "2024-01-15", "talla": 18.2, "biomasa": 150.0, "densidad": 0.85},
    {"fecha": "2024-02-01", "talla": 22.1, "biomasa": 200.0},
    {"fecha": "2024-02-15", "talla": 25.8},
    {"fecha": "2024-03-01", "talla": 28.3, "biomasa": 310.0, "densidad": 0.9}
  ],
  "modelo": "logistic_growth",  // opcional, si no se envia usa el modelo por defecto
  "config": {                    // opcional
    "horizon": 30,              // dias a proyectar (default: 30)
    "talla_objetivo": 45.0      // talla objetivo en mm (opcional)
  }
}

3. Recibe la respuesta

{
  "success": true,
  "modelo_usado": "logistic_growth",
  "predicciones": [
    {"fecha": "2024-03-02", "talla": 29.1},
    {"fecha": "2024-03-03", "talla": 29.8},
    // ... 30 dias de predicciones
  ],
  "parametros_modelo": {"L": 48.2, "k": 0.05, "x0": 42.1},
  "metricas": {"r_squared": 0.97, "rmse": 1.23},
  "incertidumbre": null,
  "warnings": []
}

Campos del request

datos (requerido)

CampoTipoRequeridoDescripcion
fechastring (YYYY-MM-DD)SiFecha de la medicion
tallanumberSiTalla en mm
biomasanumber | nullNoBiomasa
densidadnumber | nullNoDensidad

modelo (opcional)

Slug del modelo a utilizar. Si no se indica, se usa el modelo por defecto configurado en el servidor.

config (opcional)

CampoTipoDefaultDescripcion
horizoninteger30Dias futuros a proyectar
talla_objetivonumber | nullnullTalla objetivo para calcular dias estimados
parametrosobject{}Parametros especificos del modelo

Campos de la respuesta

CampoTipoDescripcion
successbooleanSi la prediccion fue exitosa
modelo_usadostringSlug del modelo que proceso los datos
prediccionesarraySerie de puntos futuros proyectados
parametros_modeloobjectParametros ajustados del modelo
metricasobjectMetricas de calidad (R², RMSE, etc.)
incertidumbreobject | nullBandas de incertidumbre si el modelo las genera
warningsarrayAdvertencias (pocos datos, ajuste pobre, etc.)

Modelos disponibles

Puedes elegir cualquiera enviando su slug en el campo modelo del request. Si no envias el campo, se usa el modelo por defecto configurado en el servidor.

Cargando modelos...

Como funciona la seleccion de modelo

  1. El cliente envia "modelo": "slug" en el request → se usa ese modelo.
  2. El cliente no envia el campo modelo → se usa el modelo por defecto (ACTIVE_MODEL en .env).
  3. El modelo solicitado no existe → error 404 con la lista de modelos disponibles.
Panel de configuracion (/): El panel HTML en la raiz de la API solo configura el modelo por defecto (el que se usa cuando el request no indica uno). El cliente siempre puede elegir cualquier modelo registrado enviando el campo modelo.

Interfaz de un modelo

Todo modelo implementa la clase BasePredictionModel con:

AtributoTipoDescripcion
slugstringIdentificador unico (e.g. "logistic_growth")
namestringNombre legible (e.g. "Crecimiento Logistico")
descriptionstringDescripcion breve del modelo

Y un unico metodo obligatorio:

predict(payload) → que recibe

Recibe un objeto PredictionInput con:

CampoTipoDescripcion
datoslist[Measurement]Array de mediciones historicas (fecha, talla, biomasa, densidad)
config.horizonintDias futuros a proyectar
config.talla_objetivofloat | nullTalla objetivo (opcional)
config.parametrosdictParametros adicionales libres para el modelo

predict(payload) → que debe devolver

Debe retornar un objeto PredictionOutput con:

CampoTipoRequeridoDescripcion
successboolSiSi la prediccion fue exitosa
modelo_usadostringSiEl slug del modelo (self.slug)
prediccioneslist[PredictionPoint]SiPuntos futuros: cada uno con fecha y opcionalmente talla, biomasa, densidad
parametros_modelodictNoParametros ajustados internos (e.g. coeficientes de la curva)
metricasdictNoMetricas de calidad del ajuste (R², RMSE, etc.)
incertidumbredict | nullNoBandas de incertidumbre si el modelo las genera
warningslist[string]NoAdvertencias (pocos datos, ajuste pobre, etc.)

Como crear un modelo nuevo

# app/models/mi_modelo.py

from app.models.base import BasePredictionModel
from app.schemas.prediction import PredictionInput, PredictionOutput, PredictionPoint
from datetime import timedelta

class MiModelo(BasePredictionModel):
    slug = "mi_modelo"
    name = "Mi Modelo Personalizado"
    description = "Descripcion de que hace este modelo."

    def predict(self, payload: PredictionInput) -> PredictionOutput:
        datos = payload.datos
        horizon = payload.config.horizon

        # ... tu logica de prediccion aqui ...

        predicciones = []
        ultima_fecha = datos[-1].fecha
        for i in range(1, horizon + 1):
            predicciones.append(PredictionPoint(
                fecha=ultima_fecha + timedelta(days=i),
                talla=42.0,  # tu calculo
            ))

        return PredictionOutput(
            success=True,
            modelo_usado=self.slug,
            predicciones=predicciones,
            parametros_modelo={"mi_param": 1.5},
            metricas={"r_squared": 0.95},
        )

Luego registrarlo en app/models/registry.py:

from app.models.mi_modelo import MiModelo

MODEL_REGISTRY = {
    # ... modelos existentes ...
    "mi_modelo": MiModelo(),
}

Listo. Ya se puede usar con "modelo": "mi_modelo" en el request.

Validaciones (filtros)

Antes de ejecutar el modelo, los datos pasan por los filtros activos en orden. Si algun filtro detecta errores, el request es rechazado con un 422 y detalles de que corregir. Los filtros activos se configuran en el panel HTML (/) o en .env.

Filtros activos actualmente

Cargando filtros...
Panel de configuracion (/): Desde el panel HTML puedes activar o desactivar filtros con checkboxes. Solo los filtros marcados como activos se ejecutan antes del modelo.

Interfaz de un filtro

Todo filtro implementa la clase BaseFilter con:

AtributoTipoDescripcion
slugstringIdentificador unico (e.g. "non_negative")
namestringNombre legible (e.g. "No Negativos")
descriptionstringDescripcion breve

Y un unico metodo obligatorio:

validate(payload) → que recibe

Recibe el mismo PredictionInput que el modelo (antes de que el modelo lo procese):

CampoTipoLo que puedes validar
payload.datoslist[Measurement]Iterar cada fila: fecha, talla, biomasa, densidad
payload.configPredictionConfigHorizon, talla_objetivo, parametros
payload.modelostring | nullModelo solicitado (si aplica)

validate(payload) → que debe devolver

Debe retornar un objeto FilterResult con:

CampoTipoDescripcion
validboolTrue si pasa la validacion, False si hay errores
errorslist[string]Lista de errores. Si hay errores, el request se rechaza con 422.
warningslist[string]Advertencias no bloqueantes. Se agregan al campo warnings de la respuesta.
Regla clave: Si valid = False, el request se detiene y se devuelve un error 422 con todos los mensajes de errors. Si valid = True pero hay warnings, el request continua y las advertencias se incluyen en la respuesta.

Como crear un filtro nuevo

# app/filters/mi_filtro.py

from app.filters.base import BaseFilter, FilterResult
from app.schemas.prediction import PredictionInput

class MiFiltro(BaseFilter):
    slug = "mi_filtro"
    name = "Mi Filtro Personalizado"
    description = "Valida que los datos cumplan mi regla."

    def validate(self, payload: PredictionInput) -> FilterResult:
        errors = []
        warnings = []

        for i, row in enumerate(payload.datos, start=1):
            # Ejemplo: validar que haya al menos biomasa o densidad
            if row.biomasa is None and row.densidad is None:
                warnings.append(
                    f"Fila {i}: sin biomasa ni densidad, prediccion menos precisa."
                )

        # Ejemplo: validar minimo de datos
        if len(payload.datos) < 3:
            errors.append("Se requieren al menos 3 mediciones.")

        return FilterResult(
            valid=len(errors) == 0,
            errors=errors,
            warnings=warnings,
        )

Luego registrarlo en app/filters/registry.py:

from app.filters.mi_filtro import MiFiltro

FILTER_REGISTRY = {
    # ... filtros existentes ...
    "mi_filtro": MiFiltro(),
}

Luego activarlo en .env o desde el panel HTML (/):

# .env
ACTIVE_FILTERS=required_fields,non_negative,range_validation,mi_filtro

Ejemplo rapido con curl

curl -X POST http://localhost:8000/predict \
  -H "Content-Type: application/json" \
  -d '{
    "datos": [
      {"fecha": "2024-01-01", "talla": 15.5, "biomasa": 120.0},
      {"fecha": "2024-01-15", "talla": 18.2, "biomasa": 150.0},
      {"fecha": "2024-02-01", "talla": 22.1},
      {"fecha": "2024-02-15", "talla": 25.8},
      {"fecha": "2024-03-01", "talla": 28.3}
    ],
    "config": {"horizon": 15}
  }'

Ejemplo con JavaScript / fetch

const response = await fetch("http://localhost:8000/predict", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    datos: [
      { fecha: "2024-01-01", talla: 15.5 },
      { fecha: "2024-01-15", talla: 18.2 },
      { fecha: "2024-02-01", talla: 22.1 },
      { fecha: "2024-02-15", talla: 25.8 },
      { fecha: "2024-03-01", talla: 28.3 }
    ],
    modelo: "von_bertalanffy",
    config: { horizon: 30, talla_objetivo: 45.0 }
  })
});

const data = await response.json();
console.log(data.predicciones);

Ejemplo con Python / requests

import requests

resp = requests.post("http://localhost:8000/predict", json={
    "datos": [
        {"fecha": "2024-01-01", "talla": 15.5},
        {"fecha": "2024-01-15", "talla": 18.2},
        {"fecha": "2024-02-01", "talla": 22.1},
        {"fecha": "2024-02-15", "talla": 25.8},
        {"fecha": "2024-03-01", "talla": 28.3},
    ],
    "modelo": "gompertz",
})

data = resp.json()
for p in data["predicciones"]:
    print(p["fecha"], p["talla"])

Notas importantes

Sin autenticacion: Esta API no requiere login, tokens ni API keys. Cualquier cliente puede enviar requests directamente.
Sin persistencia: No se guarda ningun dato. Cada request es independiente. Los datos entran, se calculan predicciones, y se devuelve el resultado.
Modelos intercambiables: Puedes usar modelos matematicos clasicos, algoritmos de ML o redes de Deep Learning. Todos reciben la misma entrada y devuelven la misma estructura de salida.