diff --git a/r_app/kpi_utils.R b/r_app/kpi_utils.R index 4519df7..6bfebbc 100644 --- a/r_app/kpi_utils.R +++ b/r_app/kpi_utils.R @@ -802,9 +802,11 @@ calculate_gap_filling_kpi <- function(ci_raster, field_boundaries) { valid_values <- ci_values[!is.na(ci_values) & is.finite(ci_values)] if (length(valid_values) > 1) { - # Placeholder gap score using lowest 25% as indicator - q25_threshold <- quantile(valid_values, 0.25) - low_ci_pixels <- sum(valid_values < q25_threshold) + # Gap score using 2σ below median to detect outliers + median_ci <- median(valid_values) + sd_ci <- sd(valid_values) + outlier_threshold <- median_ci - (2 * sd_ci) + low_ci_pixels <- sum(valid_values < outlier_threshold) total_pixels <- length(valid_values) gap_score <- (low_ci_pixels / total_pixels) * 100 @@ -821,7 +823,7 @@ calculate_gap_filling_kpi <- function(ci_raster, field_boundaries) { gap_level = gap_level, gap_score = gap_score, mean_ci = mean(valid_values), - q25_ci = q25_threshold + outlier_threshold = outlier_threshold )) } else { # Not enough valid data, fill with NA row @@ -831,7 +833,7 @@ calculate_gap_filling_kpi <- function(ci_raster, field_boundaries) { gap_level = NA_character_, gap_score = NA_real_, mean_ci = NA_real_, - q25_ci = NA_real_ + outlier_threshold = NA_real_ )) } }