code rabbit review stuff

This commit is contained in:
Timon 2026-02-10 11:34:29 +01:00
parent 58d3e655ab
commit ede84679d7
11 changed files with 63 additions and 128 deletions

View file

@ -807,7 +807,7 @@ main <- function() {
total_acreage = sum(field_data$Acreage, na.rm = TRUE),
mean_ci = round(mean(field_data$Mean_CI, na.rm = TRUE), 2),
median_ci = round(median(field_data$Mean_CI, na.rm = TRUE), 2),
mean_cv = round(mean(field_data$CI_CV, na.rm = TRUE), 4),
mean_cv = round(mean(field_data$CV, na.rm = TRUE), 4),
week = current_week,
year = current_iso_year,
date = as.character(end_date)

View file

@ -610,45 +610,6 @@ if (exists("field_details_table") && !is.null(field_details_table)) {
}
```
```{r data, message=TRUE, warning=TRUE, include=FALSE}
# Load CI index data with error handling
tryCatch({
CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds"))
safe_log("Successfully loaded CI quadrant data")
}, error = function(e) {
stop("Error loading CI quadrant data: ", e$message)
})
# Get file paths for different weeks using the utility function
tryCatch({
path_to_week_current = get_week_path(weekly_CI_mosaic, today, 0)
path_to_week_minus_1 = get_week_path(weekly_CI_mosaic, today, -1)
path_to_week_minus_2 = get_week_path(weekly_CI_mosaic, today, -2)
path_to_week_minus_3 = get_week_path(weekly_CI_mosaic, today, -3)
# Log the calculated paths
safe_log("Required mosaic paths:")
safe_log(paste("Path to current week:", path_to_week_current))
safe_log(paste("Path to week minus 1:", path_to_week_minus_1))
safe_log(paste("Path to week minus 2:", path_to_week_minus_2))
safe_log(paste("Path to week minus 3:", path_to_week_minus_3))
# Validate that files exist
if (!file.exists(path_to_week_current)) warning("Current week mosaic file does not exist: ", path_to_week_current)
if (!file.exists(path_to_week_minus_1)) warning("Week minus 1 mosaic file does not exist: ", path_to_week_minus_1)
if (!file.exists(path_to_week_minus_2)) warning("Week minus 2 mosaic file does not exist: ", path_to_week_minus_2)
if (!file.exists(path_to_week_minus_3)) warning("Week minus 3 mosaic file does not exist: ", path_to_week_minus_3)
# Load raster data with terra functions
CI <- terra::rast(path_to_week_current)$CI
CI_m1 <- terra::rast(path_to_week_minus_1)$CI
CI_m2 <- terra::rast(path_to_week_minus_2)$CI
CI_m3 <- terra::rast(path_to_week_minus_3)$CI
}, error = function(e) {
stop("Error loading raster data: ", e$message)
})
```
```{r calculate_difference_rasters, message=TRUE, warning=TRUE, include=FALSE}
# Calculate difference rasters for comparisons

View file

@ -120,8 +120,8 @@ date_suffix <- format(as.Date(report_date), "%Y%m%d")
# Calculate current week from report_date using ISO 8601 week numbering
current_week <- as.numeric(format(as.Date(report_date), "%V"))
week_suffix <- paste0("week", current_week)
current_iso_year <- as.numeric(format(as.Date(report_date), "%G"))
week_suffix <- paste0("week", sprintf("%02d", current_week), "_", current_iso_year)
# Candidate filenames we expect (exact and common variants)
expected_summary_names <- c(
paste0(project_dir, "_kpi_summary_tables_", week_suffix, ".rds"),

View file

@ -80,7 +80,13 @@ load_weekly_ci_mosaic <- function(week_num, year, mosaic_dir) {
tryCatch({
mosaic_raster <- terra::rast(week_path)
ci_raster <- mosaic_raster[[5]] # CI is the 5th band
# Extract CI band by name or position
if ("CI" %in% names(mosaic_raster)) {
ci_raster <- mosaic_raster[["CI"]]
} else {
# Fallback: assume last band is CI (after Red, Green, Blue, NIR)
ci_raster <- mosaic_raster[[nlyr(mosaic_raster)]]
}
names(ci_raster) <- "CI"
safe_log(paste("Loaded weekly mosaic:", week_file))
return(ci_raster)
@ -125,7 +131,7 @@ calculate_field_uniformity_kpi <- function(ci_raster, field_boundaries) {
field_boundaries_vect <- field_boundaries
}
field_results <- data.frame()
field_results_list <- vector("list", nrow(field_boundaries))
for (i in seq_len(nrow(field_boundaries))) {
field_name <- field_boundaries$field[i]
@ -144,7 +150,7 @@ calculate_field_uniformity_kpi <- function(ci_raster, field_boundaries) {
# If all valid values are 0 (cloud), fill with NA row
if (length(valid_values) == 0 || all(valid_values == 0)) {
field_results <- rbind(field_results, data.frame(
field_results_list[[i]] <- data.frame(
field = field_name,
sub_field = sub_field_name,
field_id = field_id,
@ -152,7 +158,7 @@ calculate_field_uniformity_kpi <- function(ci_raster, field_boundaries) {
uniformity_level = NA_character_,
mean_ci = NA_real_,
std_ci = NA_real_
))
)
} else if (length(valid_values) > 1) {
# Calculate CV using existing function
cv_value <- calculate_cv(valid_values)
@ -165,7 +171,7 @@ calculate_field_uniformity_kpi <- function(ci_raster, field_boundaries) {
TRUE ~ "Poor"
)
field_results <- rbind(field_results, data.frame(
field_results_list[[i]] <- data.frame(
field = field_name,
sub_field = sub_field_name,
field_id = field_id,
@ -173,10 +179,10 @@ calculate_field_uniformity_kpi <- function(ci_raster, field_boundaries) {
uniformity_level = uniformity_level,
mean_ci = mean(valid_values),
std_ci = sd(valid_values)
))
)
} else {
# If only one valid value, fill with NA (not enough data for CV)
field_results <- rbind(field_results, data.frame(
field_results_list[[i]] <- data.frame(
field = field_name,
sub_field = sub_field_name,
field_id = field_id,
@ -184,10 +190,12 @@ calculate_field_uniformity_kpi <- function(ci_raster, field_boundaries) {
uniformity_level = NA_character_,
mean_ci = mean(valid_values),
std_ci = NA_real_
))
)
}
}
field_results <- do.call(rbind, field_results_list)
# Create summary
uniformity_summary <- field_results %>%
dplyr::group_by(uniformity_level) %>%
@ -671,8 +679,8 @@ calculate_weed_presence_kpi <- function(current_ci, previous_ci, field_boundarie
safe_log("Previous week data not available for weed analysis", "WARNING")
summary_result <- data.frame(
weed_risk_level = c("Low", "Moderate", "High"),
field_count = c(35, 8, 3),
percent = c(76.1, 17.4, 6.5)
field_count = c(0, 0, 0),
percent = c(NA_real_, NA_real_, NA_real_)
)
field_results <- data.frame(
field = character(0),
@ -682,8 +690,6 @@ calculate_weed_presence_kpi <- function(current_ci, previous_ci, field_boundarie
rapid_growth_pixels = numeric(0)
)
return(list(summary = summary_result, field_results = field_results))
}
# Handle both sf and SpatVector inputs
if (!inherits(field_boundaries, "SpatVector")) {
field_boundaries_vect <- terra::vect(field_boundaries)
@ -967,8 +973,8 @@ create_field_detail_table <- function(kpi_results, field_boundaries_sf = NULL) {
dplyr::rename(`Gap Score` = gap_score)
field_details <- dplyr::left_join(field_details, gap_data, by = c("Field" = "field"))
} else {
# Placeholder gap scores
field_details$`Gap Score` <- round(runif(nrow(field_details), 5, 25), 1)
# Gap score data unavailable - set to NA_real_ for deterministic handling
field_details$`Gap Score` <- NA_real_
}
# Add growth decline risk from growth decline field results (aggregate by field)
@ -979,9 +985,8 @@ create_field_detail_table <- function(kpi_results, field_boundaries_sf = NULL) {
dplyr::rename(`Decline Risk` = risk_level)
field_details <- dplyr::left_join(field_details, decline_data, by = c("Field" = "field"))
} else {
# Placeholder risk levels
field_details$`Decline Risk` <- sample(risk_levels, nrow(field_details),
prob = c(0.6, 0.25, 0.1, 0.05), replace = TRUE)
# Decline risk data unavailable - set to NA for deterministic handling
field_details$`Decline Risk` <- NA
}
# Add Moran's I spatial autocorrelation from growth decline field results (aggregate by field)
@ -992,9 +997,8 @@ create_field_detail_table <- function(kpi_results, field_boundaries_sf = NULL) {
dplyr::rename(`Moran's I` = morans_i)
field_details <- dplyr::left_join(field_details, moran_data, by = c("Field" = "field"))
} else {
# Placeholder Moran's I values (typically range from -1 to 1)
set.seed(123)
field_details$`Moran's I` <- round(runif(nrow(field_details), -0.3, 0.8), 3)
# Moran's I data unavailable - set to NA_real_ for deterministic handling
field_details$`Moran's I` <- NA_real_
}
# Add weed risk from weed presence field results (aggregate by field)
@ -1005,17 +1009,12 @@ create_field_detail_table <- function(kpi_results, field_boundaries_sf = NULL) {
dplyr::rename(`Weed Risk` = weed_risk_level)
field_details <- dplyr::left_join(field_details, weed_data, by = c("Field" = "field"))
} else {
# Placeholder weed levels
field_details$`Weed Risk` <- sample(weed_levels, nrow(field_details),
prob = c(0.7, 0.2, 0.1), replace = TRUE)
# Weed risk data unavailable - set to NA for deterministic handling
field_details$`Weed Risk` <- NA
}
# Fill any remaining NAs with defaults (but keep yield forecast as NA)
field_details$`Gap Score`[is.na(field_details$`Gap Score`)] <- 0.0
field_details$`Decline Risk`[is.na(field_details$`Decline Risk`)] <- sample(risk_levels, sum(is.na(field_details$`Decline Risk`)), replace = TRUE,
prob = c(0.6, 0.25, 0.1, 0.05))
field_details$`Weed Risk`[is.na(field_details$`Weed Risk`)] <- sample(weed_levels, sum(is.na(field_details$`Weed Risk`)), replace = TRUE,
prob = c(0.7, 0.2, 0.1))
# Keep any remaining NAs as-is for all fields (NA indicates data unavailable)
# Do not fill with random values - let downstream code handle NA values deterministically
# Reorder columns for better presentation
field_details <- field_details %>%
@ -1204,19 +1203,16 @@ calculate_all_kpis <- function(report_date = Sys.Date(),
# Calculate week numbers
weeks <- calculate_week_numbers(report_date)
weeks <- calculate_week_numbers(report_date)
weeks <- calculate_week_numbers(report_date)
safe_log(paste("Current week:", weeks$current_week, "Previous week:", weeks$previous_week))
# Load weekly mosaics
current_ci <- load_weekly_ci_mosaic(weeks$current_week, weeks$year, weekly_CI_mosaic)
previous_ci <- load_weekly_ci_mosaic(weeks$previous_week, weeks$year, weekly_CI_mosaic)
current_ci <- load_weekly_ci_mosaic(weeks$current_week, weeks$current_iso_year, weekly_CI_mosaic)
previous_ci <- load_weekly_ci_mosaic(weeks$previous_week, weeks$previous_iso_year, weekly_CI_mosaic)
if (is.null(current_ci)) {
stop("Current week CI mosaic is required but not found")
}
# Check if field boundaries are loaded
if (is.null(field_boundaries_sf)) {
stop("Field boundaries not loaded. Check parameters_project.R initialization.")
stop("Current week CI mosaic is required but not found") stop("Field boundaries not loaded. Check parameters_project.R initialization.")
}
# Calculate all KPIs
@ -1255,17 +1251,16 @@ calculate_all_kpis <- function(report_date = Sys.Date(),
kpi_results$gap_filling_field_results <- gap_filling_result$field_results
# Add metadata and field boundaries for later use
kpi_results$metadata <- list(
report_date = report_date,
kpi_results$metadata <- list(
report_date = report_date,
current_week = weeks$current_week,
previous_week = weeks$previous_week,
year = weeks$year,
year = weeks$current_iso_year,
calculation_time = Sys.time(),
total_fields = nrow(field_boundaries_sf)
)
# Store field_boundaries_sf for use in export_kpi_data
kpi_results$field_boundaries_sf <- field_boundaries_sf
) kpi_results$field_boundaries_sf <- field_boundaries_sf
# Save results if output directory specified
if (!is.null(output_dir)) {

View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SmartCane Data Validation Tool</title>
<link rel="stylesheet" href="../theme.css">
<link rel="icon" type="image/x-icon" href="../res/check.png">
<link rel="icon" type="image/png" href="../res/check.png">
<style>
* {
margin: 0;

View file

@ -394,7 +394,7 @@
<h1>📍 GeoJSON Viewer</h1>
<div class="header-controls">
<label class="map-toggle-wrapper" style="cursor: pointer;">
<input type="checkbox" id="mapToggle">
<input type="checkbox" id="mapToggle" aria-label="Toggle satellite view">
<span>🛰️ Satellite</span>
</label>
<label class="map-toggle-wrapper" style="cursor: pointer;">
@ -506,7 +506,7 @@
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">Feature Details</h2>
<button class="close-btn" onclick="closeFeatureModal()">&times;</button>
<button class="close-btn" id="modalCloseBtn">&times;</button>
</div>
<div class="modal-body" id="modalBody">
</div>
@ -516,7 +516,9 @@
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js" integrity="sha512-puJW3E/qXDqYp9IfhAI54BJEaWIfloJ7JWs7OeD5i6ruC9JZL1gERT1wjtwXFlh7CjE7ZJ+/vcRZRkIYIb6p4g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6.5.0/dist/turf.min.js"></script>
<script src="google-sheets-config.js"></script>
<script src="script.js"></script>
</body>

View file

@ -136,20 +136,13 @@
// Calculate area in square meters using Turf.js
function getFeatureArea(feature) {
try {
if (!feature.geometry) return 0;
if (!feature || !feature.geometry) return 0;
const geomType = feature.geometry.type;
if (geomType === 'Point') return 0;
if (geomType === 'LineString' || geomType === 'MultiLineString') return 0;
// Use Haversine formula for rough area calculation
const coords = feature.geometry.coordinates;
if (geomType === 'Polygon') {
return calculatePolygonArea(coords[0]);
} else if (geomType === 'MultiPolygon') {
return coords.reduce((total, polygon) => total + calculatePolygonArea(polygon[0]), 0);
if (geomType === 'Point' || geomType === 'LineString' || geomType === 'MultiLineString') return 0;
// Only Polygon and MultiPolygon are valid for area
if (geomType === 'Polygon' || geomType === 'MultiPolygon') {
// turf.area expects a GeoJSON Feature or Geometry
return turf.area(feature);
}
} catch (e) {
console.warn('Error calculating area:', e);
@ -158,27 +151,12 @@
return 0;
}
// Calculate polygon area using Shoelace formula
// Deprecated: Calculate polygon area using Shoelace formula
// Use Turf.js for area calculation instead
function calculatePolygonArea(coords) {
if (!coords || coords.length < 3) return 0;
let area = 0;
const R = 6371000; // Earth radius in meters
for (let i = 0; i < coords.length - 1; i++) {
const p1 = coords[i];
const p2 = coords[i + 1];
const lat1 = (p1[1] * Math.PI) / 180;
const lat2 = (p2[1] * Math.PI) / 180;
const dLng = ((p2[0] - p1[0]) * Math.PI) / 180;
area += Math.sin(lat1) * Math.cos(lat1) * dLng;
area += Math.sin(lat1) * Math.cos(lat2) * Math.sin(dLng);
}
area = Math.abs(area * R * R / 2);
return area;
// Accepts coordinates, but now returns 0 and logs warning
console.warn('calculatePolygonArea is deprecated. Use getFeatureArea(feature) with Turf.js instead.');
return 0;
}
// Convert square meters to hectares and acres

View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SmartCane Web Apps</title>
<link rel="stylesheet" href="theme.css">
<link rel="icon" type="image/x-icon" href="./res/main.png">
<link rel="icon" type="image/png" href="./res/main.png">
<style>
* {
margin: 0;
@ -227,7 +227,7 @@
<li>View feature properties</li>
<li>Download exports</li>
</ul>
<a href="./geojson_viewer" class="app-btn">Open Viewer</a>
<a href="./geojson_viewer/" class="app-btn">Open Viewer</a>
</div>
</div>

View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Polygon Analysis & Editor</title>
<link rel="stylesheet" href="../theme.css">
<link rel="icon" type="image/x-icon" href="../res/poly.png">
<link rel="icon" type="image/png" href="../res/poly.png">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

View file

@ -8,7 +8,7 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-measure@3.1.0/dist/leaflet-measure.css" />
<link rel="stylesheet" href="../theme.css">
<link rel="icon" type="image/x-icon" href="../res/map.png">
<link rel="icon" type="image/png" href="../res/map.png">
<style>
* {
margin: 0;

View file

@ -71,8 +71,7 @@ header {
transition: background 0.3s ease;
flex-shrink: 0;
margin-top: 0;
flex: None;
}
flex: none;}
.back-btn:hover {
background: rgba(255,255,255,0.3);