diff --git a/r_app/80_utils_cane_supply.R b/r_app/80_utils_cane_supply.R index fa14f64..100d231 100644 --- a/r_app/80_utils_cane_supply.R +++ b/r_app/80_utils_cane_supply.R @@ -138,28 +138,28 @@ categorize_cv_trend_long_term <- function(cv_slope) { #' Determine status alert based on harvest probability and crop health #' Priority order: -#' 1. Ready for harvest-check (imminent + mature ≥12 months) -#' 2. Strong decline in crop health (drop ≥2 points but still >1.5) -#' 3. Harvested/bare (Mean CI < 1.5) +#' 1. harvest_ready (imminent + mature ≥12 months) +#' 2. decline_stress (drop ≥2 points but still >1.5) +#' 3. harvested_bare (Mean CI < 1.5) #' @param imminent_prob Numeric harvest probability #' @param age_week Numeric age in weeks #' @param weekly_ci_change Numeric weekly CI change #' @param mean_ci Numeric mean CI value -#' @return Character status alert or NA +#' @return Character status alert code or NA calculate_status_alert <- function(imminent_prob, age_week, weekly_ci_change, mean_ci) { # Priority 1: Ready for harvest-check if (!is.na(imminent_prob) && imminent_prob > 0.5 && !is.na(age_week) && age_week >= 52) { - return("Ready for harvest-check") + return("harvest_ready") } # Priority 2: Strong decline if (!is.na(weekly_ci_change) && weekly_ci_change <= -2.0 && !is.na(mean_ci) && mean_ci > 1.5) { - return("Strong decline in crop health") + return("decline_stress") } # Priority 3: Harvested/bare if (!is.na(mean_ci) && mean_ci < 1.5) { - return("Harvested/bare") + return("harvested_bare") } # Fallback: no alert diff --git a/r_app/90_CI_report_with_kpis_agronomic_support.Rmd b/r_app/90_CI_report_with_kpis_agronomic_support.Rmd index 4d873b2..216d722 100644 --- a/r_app/90_CI_report_with_kpis_agronomic_support.Rmd +++ b/r_app/90_CI_report_with_kpis_agronomic_support.Rmd @@ -199,7 +199,7 @@ if (dir.exists(kpi_data_dir)) { c("Gap_Score", "Gap_Score"), # Keep as-is c("Growth Uniformity", "Growth_Uniformity"), c("Decline Risk", "Decline_Risk"), - c("Weed Risk", "Weed_Risk"), + c("Patchiness Risk", "Patchiness_Risk"), c("Moran's I", "Morans_I") ) @@ -539,15 +539,15 @@ if (exists("summary_tables") && !is.null(summary_tables) && length(summary_table } } - # 4. Weed pressure insights - group by weed_pressure_risk - if (!is.null(summary_tables$weed_pressure) && nrow(summary_tables$weed_pressure) > 0) { - cat("\n**Weed/Pest Pressure Risk:**\n") - weed_counts <- summary_tables$weed_pressure %>% - dplyr::select(weed_pressure_risk, count = field_count) + # 4. Patchiness insights - group by patchiness_risk + if (!is.null(summary_tables$patchiness) && nrow(summary_tables$patchiness) > 0) { + cat("\n**Field Patchiness Risk:**\n") + patchiness_counts <- summary_tables$patchiness %>% + dplyr::select(patchiness_risk, count = field_count) - for (i in seq_len(nrow(weed_counts))) { - risk <- weed_counts$weed_pressure_risk[i] - count <- weed_counts$count[i] + for (i in seq_len(nrow(patchiness_counts))) { + risk <- patchiness_counts$patchiness_risk[i] + count <- patchiness_counts$count[i] if (!is.na(risk) && !is.na(count) && count > 0) { cat("- ", count, " field(s) at ", risk, " risk\n", sep="") } @@ -684,7 +684,7 @@ generate_field_alerts <- function(field_details_table) { # Check for required columns required_cols <- c("Field", "Field Size (acres)", "Growth Uniformity", "Yield Forecast (t/ha)", - "Gap Score", "Decline Risk", "Weed Risk", "Mean CI", "CV Value", "Moran's I") + "Gap Score", "Decline Risk", "Patchiness Risk", "Mean CI", "CV Value", "Moran's I") missing_cols <- setdiff(required_cols, colnames(field_details_table)) if (length(missing_cols) > 0) { @@ -714,10 +714,10 @@ generate_field_alerts <- function(field_details_table) { any(`Decline Risk` == "Low") ~ "Low", TRUE ~ "Unknown" ), - highest_weed_risk = case_when( - any(`Weed Risk` == "High") ~ "High", - any(`Weed Risk` == "Moderate") ~ "Moderate", - any(`Weed Risk` == "Low") ~ "Low", + highest_patchiness_risk = case_when( + any(`Patchiness Risk` == "High") ~ "High", + any(`Patchiness Risk` == "Medium") ~ "Medium", + any(`Patchiness Risk` == "Minimal") ~ "Minimal", TRUE ~ "Unknown" ), avg_mean_ci = mean(`Mean CI`, na.rm = TRUE), @@ -743,12 +743,12 @@ generate_field_alerts <- function(field_details_table) { } # Priority 3: No alert (no stress) - # Keep other alerts for decline risk, weed risk, gap score + # Keep other alerts for decline risk, patchiness risk, gap score if (field_summary$highest_decline_risk %in% c("High", "Very-high")) { field_alerts <- c(field_alerts, "Growth decline observed") } - if (field_summary$highest_weed_risk == "High") { - field_alerts <- c(field_alerts, "Increased weed presence") + if (field_summary$highest_patchiness_risk == "High") { + field_alerts <- c(field_alerts, "High patchiness detected - recommend scouting") } if (field_summary$max_gap_score > 20) { field_alerts <- c(field_alerts, "Gaps present - recommend review") @@ -859,7 +859,7 @@ if (!exists("field_details_table") || is.null(field_details_table)) { TCH_Forecasted = NA_real_, Gap_Score = NA_real_, Decline_Risk = NA_character_, - Weed_Risk = NA_character_, + Patchiness_Risk = NA_character_, Mean_CI = NA_real_, CV = NA_real_, Morans_I = NA_real_ @@ -1410,7 +1410,7 @@ tryCatch({ kpi_parts <- c( kpi_parts, sprintf("**Gap:** %.0f", field_kpi$Gap_Score), - sprintf("**Weed:** %s", field_kpi$Weed_Pressure_Risk), + sprintf("**Patchiness:** %s", field_kpi$Patchiness_Risk), sprintf("**Decline:** %s", field_kpi$Decline_Severity) ) @@ -1518,7 +1518,7 @@ if (!exists("field_details_table") || is.null(field_details_table) || nrow(field # Ensure all expected KPI columns exist; add as NA if missing expected_cols <- c("Field_id", "Mean_CI", "CV", "TCH_Forecasted", "Gap_Score", "Trend_Interpretation", "Weekly_CI_Change", "Uniformity_Interpretation", - "Decline_Severity", "Weed_Pressure_Risk") + "Decline_Severity", "Patchiness_Risk") for (col in expected_cols) { if (!col %in% names(field_details_table)) { field_details_table[[col]] <- NA @@ -1553,7 +1553,7 @@ if (!exists("field_details_table") || is.null(field_details_table) || nrow(field `Yield Forecast (t/ha)` = TCH_Forecasted, `Gap Score` = Gap_Score, `Decline Risk` = Decline_Severity, - `Weed Risk` = Weed_Pressure_Risk, + `Patchiness Risk` = Patchiness_Risk, `CV Value` = CV ) } else { @@ -1566,7 +1566,7 @@ if (!exists("field_details_table") || is.null(field_details_table) || nrow(field `Yield Forecast (t/ha)` = TCH_Forecasted, `Gap Score` = Gap_Score, `Decline Risk` = Decline_Severity, - `Weed Risk` = Weed_Pressure_Risk, + `Patchiness Risk` = Patchiness_Risk, `CV Value` = CV ) } @@ -1634,11 +1634,11 @@ CI values typically range from 0 (bare soil or severely stressed vegetation) to - **TCH Forecasted:** Provides yield predictions (tons cane per hectare) for mature fields (typically over 240 days old), using a machine learning model trained on historical CI and yield data. Helps plan harvest timing and logistics. - - **Weed Presence Score:** Detects rapid CI increases between weeks as a proxy for weed outbreaks in young fields (< 8 months old). After 8 months, canopy closure prevents weed growth. Risk levels based on percentage of pixels showing rapid growth (> 2.0 CI units increase): - - **Low:** < 10% of field area (minimal weed presence) - - **Moderate:** 10–25% (monitor and scout) - - **High:** > 25% (requires immediate intervention) - - **Note:** Mature fields (≥ 8 months) show "Canopy closed - Low weed risk" as the closed canopy suppresses weed growth. + - **Field Patchiness Score:** Measures field heterogeneity using the Gini coefficient, detecting spatial variation in crop health. High patchiness (Gini > 0.12) may indicate irrigation, pest, or fertility issues requiring targeted scouting: + - **Low:** Gini < 0.08 (excellent uniformity, minimal intervention needed) + - **Medium:** Gini 0.08–0.12 (acceptable variation, routine monitoring) + - **High:** Gini > 0.12 (poor uniformity, recommend field scouting) + - **Note:** Young crops (< 3 months) naturally show higher patchiness as they establish; this decreases with canopy closure. - **Gap Filling Score:** Indicates the proportion of a field with low CI values (lowest 25% of the distribution), highlighting areas with poor crop establishment or gaps that may need replanting.