Refactor risk assessment terminology in reports; replace 'Weed Risk' with 'Patchiness Risk' and update related calculations and alerts for consistency

This commit is contained in:
Timon 2026-02-17 22:15:33 +01:00
parent 253ff51ca2
commit f0a3afad52
2 changed files with 34 additions and 34 deletions

View file

@ -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

View file

@ -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:** 1025% (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.080.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.