Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sdarionicolas-boop/AgroIA-RAG/llms.txt

Use this file to discover all available pages before exploring further.

The src/pipeline/agro_math.py module contains the core scoring mathematics for AgroIA. It defines the CONFIG dict that parameterises every crop, the calcular_score() function that translates satellite and climate measurements into a 0–100 score, and two NDVI validation helpers that guard against implausible Sentinel-2 readings before they reach the scoring layer. All functions in this module are pure Python and have no external side effects, making them easy to test in isolation.

CONFIG

The CONFIG dict holds the agronomic parameters for each supported crop. Every key used by the pipeline (calcular_score, validar_ndvi, get_gee_ndvi_validado) is read from here, so modifying a value automatically propagates to all downstream calculations.
from src.pipeline.agro_math import CONFIG

print(CONFIG["maiz"]["mes_critico"])  # 1
print(CONFIG["soja"]["umbral_clima"]) # 35
Current values from source:
CONFIG = {
    "maiz": {
        "tbase":        10,
        "umbral_calor": 35,
        "umbral_clima": 40,
        "mes_critico":  1,
        "pesos": {10: 0.2, 11: 0.5, 12: 1.0, 1: 1.0, 2: 0.6, 3: 0.2},
        "color":        "#2D6A4F",
        "biblio":       "INTA Marcos Juárez / Univ. Nebraska",
        "ndvi_min":     0.25,
        "ndvi_max":     0.92,
    },
    "soja": {
        "tbase":        10,
        "umbral_calor": 35,
        "umbral_clima": 35,
        "mes_critico":  2,
        "pesos": {11: 0.3, 12: 0.7, 1: 1.0, 2: 1.0, 3: 0.5, 4: 0.2},
        "color":        "#40916C",
        "biblio":       "INTA Marcos Juárez",
        "ndvi_min":     0.25,
        "ndvi_max":     0.90,
    },
    "trigo": {
        "tbase":        0,
        "umbral_calor": 30,
        "umbral_clima": 30,
        "mes_critico":  10,
        "pesos": {6: 0.1, 7: 0.2, 8: 0.4, 9: 1.0, 10: 1.0, 11: 0.5},
        "color":        "#C29B0C",
        "biblio":       "INTA Pergamino",
        "ndvi_min":     0.20,
        "ndvi_max":     0.88,
    },
}
Each crop entry contains the following fields:
FieldTypeDescription
tbaseintBase temperature (°C) for heat-degree accumulation.
umbral_calorintTemperature threshold (°C) above which heat stress is counted.
umbral_climaintMaximum expected heat-stress hours; used to normalise the Climate score component.
mes_criticointMonth (1–12) of peak biomass. Sentinel-2 NDVI is retrieved for this month.
pesosdict[int, float]Monthly weight multipliers for phenological relevance (informational; not used in calcular_score directly).
colorstrHex color for the crop in report visualisations.
bibliostrBibliographic source for the agronomic parameters.
ndvi_minfloatMinimum plausible NDVI for this crop. Values below this are flagged as sospechoso_bajo.
ndvi_maxfloatMaximum plausible NDVI. Values above this are flagged as sospechoso_alto.

calcular_score

Calculates the AgroIA Score (0–100) from a single NDVI observation, a climate stress value, and the full NDVI historical series.
from src.pipeline.agro_math import calcular_score

result = calcular_score(
    ndvi_critico=0.72,
    horas_calor=18.5,
    ndvi_historico=[0.65, 0.68, 0.71, 0.72],
    umbral_clima=40,
)
# {'total': 74, 'vigor': 32.0, 'estabilidad': 27.3, 'limpieza': 10.0, 'clima': 5.4}

Parameters

ndvi_critico
number
required
NDVI value for the current year’s critical month (0.0–1.0 range). Normalised against 0.9 to compute the Vigor component.
horas_calor
number
required
Accumulated heat-stress hours for the current year from NASA POWER. Used to compute the Climate component, normalised against umbral_clima.
ndvi_historico
number[]
required
List of NDVI values for the historical series including the current year. Used to compute Stability (requires ≥ 3 values) and Cleanliness via Isolation Forest (requires ≥ 4 values). Shorter lists receive partial scores: 15.0 for Stability and 10.0 for Cleanliness.
umbral_clima
number
default:"40"
Maximum expected heat-stress hours for the crop. Taken from CONFIG[cultivo]["umbral_clima"]. A value of 40 means that 40 or more hours of heat stress yields a Climate component of 0.

Return value

result
object
A dict with five keys. All float values are rounded to one decimal place; total is an integer.

Full source

def calcular_score(ndvi_critico, horas_calor, ndvi_historico, umbral_clima=40):
    """Calcula el Score AgroIA (0-100)."""
    vigor = np.clip(ndvi_critico / 0.9, 0.0, 1.0) * 40.0
    if len(ndvi_historico) >= 3:
        arr = np.array(ndvi_historico)
        cv  = np.std(arr) / np.mean(arr) if np.mean(arr) > 0 else 1.0
        estabilidad = np.clip(1.0 - cv / 0.45, 0.0, 1.0) * 30.0
    else: estabilidad = 15.0

    if len(ndvi_historico) >= 4:
        iso = IsolationForest(contamination=0.2, random_state=42)
        labels = iso.fit_predict(np.array(ndvi_historico).reshape(-1, 1))
        limpieza = np.clip(1.0 - (labels == -1).sum() / len(labels) / 0.40, 0.0, 1.0) * 20.0
    else: limpieza = 10.0

    clima = np.clip(1.0 - horas_calor / umbral_clima, 0.0, 1.0) * 10.0
    total = int(round(vigor + estabilidad + limpieza + clima))

    return {"total": total, "vigor": round(vigor, 1), "estabilidad": round(estabilidad, 1),
            "limpieza": round(limpieza, 1), "clima": round(clima, 1)}
The Stability component caps the CV denominator at 0.45. This means a series with CV ≥ 0.45 (highly variable) scores 0 on Stability, while a perfectly stable series (CV = 0) scores the maximum 30 points.

validar_ndvi

Checks whether an NDVI value is within the agronomically plausible range for a given crop, year, and month. Called by get_gee_ndvi_validado before any value is used in scoring.
from src.pipeline.agro_math import validar_ndvi

status, msg = validar_ndvi(ndvi_val=0.15, cultivo="maiz", year=2024, mes=1)
# status = 'sospechoso_bajo'
# msg    = '⚠ 2024/01: NDVI=0.150 bajo mínimo.'

Parameters

ndvi_val
number
required
The NDVI value to validate (expected range 0.0–1.0). Pass None or float('nan') for null readings.
cultivo
string
required
Crop key used to look up ndvi_min and ndvi_max from CONFIG. Falls back to CONFIG["maiz"] if the key is not found.
year
number
required
Year of the observation. Used only for the human-readable message.
mes
number
required
Month of the observation (1–12). Used only for the human-readable message.

Return value

status
string
One of four string values:
  • "ok" — value is within [ndvi_min, ndvi_max] for the crop.
  • "sospechoso_bajo" — value is below ndvi_min; excluded from scoring by get_gee_ndvi_validado.
  • "sospechoso_alto" — value exceeds ndvi_max; returned but treated with caution.
  • "nulo" — value is None or NaN.
message
string
Human-readable diagnostic string including the year, month, and NDVI value.

get_gee_ndvi_validado

Fetches NDVI from Google Earth Engine for a polygon and immediately validates it. Returns a three-tuple so the pipeline can branch on the status without additional checks.
from src.pipeline.agro_math import get_gee_ndvi_validado
import ee

geom = ee.Geometry.Polygon([[[-62.1, -33.2], [-62.0, -33.2],
                              [-62.0, -33.1], [-62.1, -33.1],
                              [-62.1, -33.2]]])

ndvi, status, msg = get_gee_ndvi_validado(geom, year=2024, month=1, cultivo="maiz")
if ndvi is not None:
    print(f"Valid NDVI: {ndvi:.3f}")
else:
    print(f"Rejected — {status}: {msg}")

Parameters

geom_ee
ee.Geometry
required
A Google Earth Engine Geometry.Polygon representing the field boundary in WGS-84.
year
number
required
Year of the requested observation.
month
number
required
Month (1–12) of the requested observation. Should be CONFIG[cultivo]["mes_critico"] in normal usage.
cultivo
string
required
Crop key passed to validar_ndvi to apply the correct plausibility bounds.

Return value

A tuple[float | None, str, str]:
ndvi_value
number | None
The validated NDVI value, or None when the status is "sospechoso_bajo", "nulo", or "error".
status
string
One of "ok", "sospechoso_bajo", "sospechoso_alto", "nulo", or "error".
message
string
Human-readable diagnostic from validar_ndvi, or an error description if the GEE call raised an exception.
On any exception from the GEE call (network timeout, invalid geometry, quota exceeded), the function returns (None, 'error', 'Error GEE: <message>') rather than raising. The pipeline handles this by falling back to get_gee_ndvi_ventana.

Usage example: calculating score for a lot

from src.pipeline.agro_math import CONFIG, calcular_score, validar_ndvi

cultivo = "soja"
conf = CONFIG[cultivo]

# Simulated data for a field
ndvi_series = [0.68, 0.71, 0.74, 0.70, 0.73]
horas_calor = 22.0   # from NASA POWER

# Validate the most recent reading before scoring
mes_critico = conf["mes_critico"]
status, msg = validar_ndvi(ndvi_series[-1], cultivo, year=2025, mes=mes_critico)
print(f"Validation: {status}{msg}")

if status in ("ok", "sospechoso_alto"):
    score = calcular_score(
        ndvi_critico=ndvi_series[-1],
        horas_calor=horas_calor,
        ndvi_historico=ndvi_series,
        umbral_clima=conf["umbral_clima"],
    )
    print(f"Score total : {score['total']}/100")
    print(f"  Vigor     : {score['vigor']}/40")
    print(f"  Estabilidad: {score['estabilidad']}/30")
    print(f"  Limpieza  : {score['limpieza']}/20")
    print(f"  Clima     : {score['clima']}/10")

Score AgroIA concepts

Conceptual explanation of the four score components.

Pipeline module

How agro_math integrates into the full analysis pipeline.

Build docs developers (and LLMs) love