# 06_CROP_MESSAGING.R # =================== # This script analyzes weekly CI mosaics to detect changes and generate automated messages # about crop conditions. It compares two weeks of data to assess: # - Field uniformity (high vs low variation) # - CI change trends (increase, stable, decrease) # - Generates contextual messages based on analysis # - Outputs results in multiple formats: WhatsApp/Word text, CSV, and .docx # # Usage: Rscript 06_crop_messaging.R [current_week] [previous_week] [estate_name] # - current_week: Current week number (e.g., 30) # - previous_week: Previous week number (e.g., 29) # - estate_name: Estate name (e.g., "simba", "chemba") # # Examples: # Rscript 06_crop_messaging.R 32 31 simba # Rscript 06_crop_messaging.R 30 29 chemba # # The script automatically: # 1. Loads the correct estate configuration # 2. Analyzes weekly mosaics # 3. Generates field-by-field analysis # 4. Creates output files in multiple formats # 5. Displays WhatsApp-ready text in console # # 1. Load required packages # ----------------------- suppressPackageStartupMessages({ library(sf) library(terra) library(tidyverse) library(lubridate) library(here) library(spdep) # For spatial statistics }) # 2. Main function to handle messaging workflow # --------------------------------------------- main <- function() { # Capture command line arguments args <- commandArgs(trailingOnly = TRUE) # Process arguments with defaults current_week <- if (length(args) >= 1 && !is.na(args[1])) { as.numeric(args[1]) } else { 39 # Default for proof of concept } previous_week <- if (length(args) >= 2 && !is.na(args[2])) { as.numeric(args[2]) } else { 38 # Default for proof of concept } estate_name <- if (length(args) >= 3 && !is.na(args[3])) { as.character(args[3]) } else { "aura" # Default estate } year <- 2025 # Current year - could be made dynamic # Make estate_name available globally so parameters_project.R can use it assign("project_dir", estate_name, envir = .GlobalEnv) # Initialize project configuration and load utility functions tryCatch({ source("parameters_project.R") source("crop_messaging_utils.R") }, error = function(e) { warning("Default source files not found. Attempting to source from 'r_app' directory.") tryCatch({ source(here::here("r_app", "parameters_project.R")) source(here::here("r_app", "crop_messaging_utils.R")) warning(paste("Successfully sourced files from 'r_app' directory.")) }, error = function(e) { stop("Failed to source required files from both default and 'r_app' directories.") }) }) log_message("Starting crop messaging analysis") # Run the modular analysis analysis_results <- run_estate_analysis(estate_name, current_week, previous_week, year) field_results <- analysis_results$field_results # Display detailed field-by-field analysis cat("=== FIELD-BY-FIELD ANALYSIS ===\n\n") for (field_id in names(field_results)) { field_info <- field_results[[field_id]] current_field <- field_info$current_stats previous_field <- field_info$previous_stats ci_change <- field_info$ci_change change_category <- field_info$change_category change_percentages <- field_info$change_percentages uniformity_category <- field_info$uniformity_category message_result <- field_info$message_result # Print enhanced field analysis cat("FIELD:", current_field$field, "-", current_field$sub_field, "\n") cat("- Field size:", round(current_field$field_area_ha, 1), "hectares\n") cat("- Week", previous_week, "CI:", round(previous_field$mean_ci, 3), "\n") cat("- Week", current_week, "CI:", round(current_field$mean_ci, 3), "\n") cat("- Terra stats: Mean =", round(current_field$mean_ci, 3), ", CV =", round(current_field$cv, 3), ", Range = [", round(current_field$min_ci, 2), "-", round(current_field$max_ci, 2), "]\n") cat("- Within acceptable range (Β±25% of mean):", round(current_field$acceptable_pct, 1), "%\n") # Display primary uniformity metrics (CV and Entropy) cat("- Field uniformity: CV =", round(current_field$cv, 3)) if (current_field$cv < 0.08) { cat(" (excellent)") } else if (current_field$cv < 0.15) { cat(" (good)") } else if (current_field$cv < 0.30) { cat(" (moderate)") } else if (current_field$cv < 0.50) { cat(" (high variation)") } else { cat(" (very high variation)") } # Add entropy information if (!is.na(current_field$entropy)) { cat(", Entropy =", round(current_field$entropy, 3)) # Entropy interpretation (higher = more heterogeneous) # Adjusted thresholds to better match CV patterns if (current_field$entropy < 1.3) { cat(" (very uniform)") } else if (current_field$entropy < 1.5) { cat(" (uniform)") } else if (current_field$entropy < 1.7) { cat(" (moderate heterogeneity)") } else { cat(" (high heterogeneity)") } } cat("\n") cat("- Change: Mean =", round(ci_change, 3), "(", change_category, ")") if (!is.na(change_percentages$positive_pct)) { # Calculate hectares for this field using field area from geojson field_hectares <- current_field$field_area_ha improving_hectares <- (change_percentages$positive_pct / 100) * field_hectares declining_hectares <- (change_percentages$negative_pct / 100) * field_hectares cat(", Areas: ", round(change_percentages$positive_pct, 1), "% (", round(improving_hectares, 1), " ha) improving, ", round(change_percentages$negative_pct, 1), "% (", round(declining_hectares, 1), " ha) declining\n") } else { cat("\n") } cat("- Spatial Pattern:", uniformity_category, "\n") # Add spatial details if available if (!is.na(current_field$spatial_autocorr$morans_i)) { cat("- Moran's I:", round(current_field$spatial_autocorr$morans_i, 3), "(", current_field$spatial_autocorr$interpretation, ")") # Add agricultural context explanation for Moran's I moran_val <- current_field$spatial_autocorr$morans_i if (moran_val >= 0.7 && moran_val < 0.85) { cat(" - normal field continuity") } else if (moran_val >= 0.85 && moran_val < 0.95) { cat(" - strong spatial pattern") } else if (moran_val >= 0.95) { cat(" - very strong clustering, monitor for management issues") } else if (moran_val < 0.7 && moran_val > 0.3) { cat(" - moderate spatial pattern") } else { cat(" - unusual spatial pattern for crop field") } cat("\n") } if (!is.na(current_field$extreme_percentages$hotspot_pct)) { cat("- Extreme areas: ", round(current_field$extreme_percentages$hotspot_pct, 1), "% hotspots (high-performing), ", round(current_field$extreme_percentages$coldspot_pct, 1), "% coldspots (underperforming)") # Show method used for extreme detection if (!is.null(current_field$extreme_percentages$method)) { if (current_field$extreme_percentages$method == "getis_ord_gi_star") { cat(" [Getis-Ord Gi*]") } else if (current_field$extreme_percentages$method == "simple_sd") { cat(" [Simple SD]") } } cat("\n") } cat("- Message:", message_result$message, "\n") cat("- Alert needed:", if(message_result$worth_sending) "YES 🚨" else "NO", "\n\n") } # Summary of alerts alert_fields <- sapply(field_results, function(x) x$message_result$worth_sending) total_alerts <- sum(alert_fields) cat("=== SUMMARY ===\n") cat("Total fields analyzed:", length(field_results), "\n") cat("Fields requiring alerts:", total_alerts, "\n") if (total_alerts > 0) { cat("\nFields needing attention:\n") for (field_id in names(field_results)[alert_fields]) { field_info <- field_results[[field_id]] cat("-", field_info$current_stats$field, "-", field_info$current_stats$sub_field, ":", field_info$message_result$message, "\n") } } # Farm-wide analysis summary table cat("\n=== FARM-WIDE ANALYSIS SUMMARY ===\n") # Field uniformity statistics with detailed categories excellent_fields <- sapply(field_results, function(x) x$current_stats$cv <= 0.08) good_fields <- sapply(field_results, function(x) x$current_stats$cv > 0.08 & x$current_stats$cv <= 0.15) moderate_fields <- sapply(field_results, function(x) x$current_stats$cv > 0.15 & x$current_stats$cv <= 0.30) poor_fields <- sapply(field_results, function(x) x$current_stats$cv > 0.30) n_excellent <- sum(excellent_fields) n_good <- sum(good_fields) n_moderate <- sum(moderate_fields) n_poor <- sum(poor_fields) n_uniform_total <- n_excellent + n_good # Total uniform fields (CV ≀ 0.20) # Calculate farm-wide area statistics total_hectares <- sum(sapply(field_results, function(x) x$current_stats$field_area_ha), na.rm = TRUE) total_improving_hectares <- sum(sapply(field_results, function(x) { if (!is.na(x$change_percentages$positive_pct)) { (x$change_percentages$positive_pct / 100) * x$current_stats$field_area_ha } else 0 }), na.rm = TRUE) total_declining_hectares <- sum(sapply(field_results, function(x) { if (!is.na(x$change_percentages$negative_pct)) { (x$change_percentages$negative_pct / 100) * x$current_stats$field_area_ha } else 0 }), na.rm = TRUE) # Calculate farm-wide percentages farm_improving_pct <- (total_improving_hectares / total_hectares) * 100 farm_declining_pct <- (total_declining_hectares / total_hectares) * 100 # Display summary table cat("\nFIELD UNIFORMITY SUMMARY:\n") cat("β”‚ Uniformity Level β”‚ Count β”‚ Percent β”‚\n") cat(sprintf("β”‚ Excellent (CV≀0.08) β”‚ %5d β”‚ %6.1f%% β”‚\n", n_excellent, (n_excellent/length(field_results))*100)) cat(sprintf("β”‚ Good (CV 0.08-0.15) β”‚ %5d β”‚ %6.1f%% β”‚\n", n_good, (n_good/length(field_results))*100)) cat(sprintf("β”‚ Moderate (CV 0.15-0.30) β”‚ %5d β”‚ %6.1f%% β”‚\n", n_moderate, (n_moderate/length(field_results))*100)) cat(sprintf("β”‚ Poor (CV>0.30) β”‚ %5d β”‚ %6.1f%% β”‚\n", n_poor, (n_poor/length(field_results))*100)) cat(sprintf("β”‚ Total fields β”‚ %5d β”‚ %6.1f%% β”‚\n", length(field_results), 100.0)) cat("\nFARM-WIDE AREA CHANGE SUMMARY:\n") cat("β”‚ Change Type β”‚ Hectaresβ”‚ Percent β”‚\n") cat(sprintf("β”‚ Improving areas β”‚ %7.1f β”‚ %6.1f%% β”‚\n", total_improving_hectares, farm_improving_pct)) cat(sprintf("β”‚ Declining areas β”‚ %7.1f β”‚ %6.1f%% β”‚\n", total_declining_hectares, farm_declining_pct)) cat(sprintf("β”‚ Total area β”‚ %7.1f β”‚ %6.1f%% β”‚\n", total_hectares, 100.0)) # Additional insights cat("\nKEY INSIGHTS:\n") cat(sprintf("β€’ %d%% of fields have good uniformity (CV ≀ 0.15)\n", round((n_uniform_total/length(field_results))*100))) cat(sprintf("β€’ %d%% of fields have excellent uniformity (CV ≀ 0.08)\n", round((n_excellent/length(field_results))*100))) cat(sprintf("β€’ %.1f hectares (%.1f%%) of farm area is improving week-over-week\n", total_improving_hectares, farm_improving_pct)) cat(sprintf("β€’ %.1f hectares (%.1f%%) of farm area is declining week-over-week\n", total_declining_hectares, farm_declining_pct)) cat(sprintf("β€’ Total farm area analyzed: %.1f hectares\n", total_hectares)) if (farm_improving_pct > farm_declining_pct) { cat(sprintf("β€’ Overall trend: POSITIVE (%.1f%% more area improving than declining)\n", farm_improving_pct - farm_declining_pct)) } else if (farm_declining_pct > farm_improving_pct) { cat(sprintf("β€’ Overall trend: NEGATIVE (%.1f%% more area declining than improving)\n", farm_declining_pct - farm_improving_pct)) } else { cat("β€’ Overall trend: BALANCED (equal improvement and decline)\n") } # Generate and save multiple output formats saved_files <- save_analysis_outputs(analysis_results) # Analysis complete cat("\n=== ANALYSIS COMPLETE ===\n") cat("All field analysis results, farm-wide summary, and output files created.\n") # Return results for potential further processing invisible(analysis_results) } if (sys.nframe() == 0) { main() }