From b6d18a1fa85b0df89efdb4ad10448261882ca99e Mon Sep 17 00:00:00 2001 From: ayushman1210 Date: Tue, 30 Jun 2026 14:58:16 +0530 Subject: [PATCH 1/5] feat: implement automated reporting and visualization layer --- .../benchmark/R/generate_validation_report.R | 62 +++++++++++++++ modules/benchmark/R/metric_residual_plot.R | 28 +++++-- modules/benchmark/R/metric_scatter_plot.R | 19 +++-- modules/benchmark/R/metric_timeseries_plot.R | 39 ++++++---- .../inst/reports/Validation_report.qmd | 75 +++++++++++++++++++ .../tests/testthat/test-visualization.R | 53 +++++++++++++ 6 files changed, 245 insertions(+), 31 deletions(-) create mode 100644 modules/benchmark/R/generate_validation_report.R create mode 100644 modules/benchmark/inst/reports/Validation_report.qmd create mode 100644 modules/benchmark/tests/testthat/test-visualization.R diff --git a/modules/benchmark/R/generate_validation_report.R b/modules/benchmark/R/generate_validation_report.R new file mode 100644 index 00000000000..6399cbcc16f --- /dev/null +++ b/modules/benchmark/R/generate_validation_report.R @@ -0,0 +1,62 @@ +##' Generate Validation Benchmark Report +##' +##' @param benchmark_results A list containing `metrics` (data.frame), `aligned_data` (data.frame), and `plots` (list of ggplot objects) returned by the validation pipeline. +##' @param output_file The path where the compiled report should be saved (e.g., "validation_report.html"). +##' @param template The path to the Quarto template. Defaults to the one provided in the package `inst/reports/Validation_report.qmd`. +##' +##' @author PEcAn Project +##' @export +generate_validation_report <- function(benchmark_results, output_file = "Validation_report.html", template = NULL) { + PEcAn.logger::logger.info("Generating Validation Benchmark Report...") + + if (is.null(template)) { + template <- system.file("reports", "Validation_report.qmd", package = "PEcAn.benchmark") + if (template == "") { + # Fallback for development mode + template <- file.path(getwd(), "inst", "reports", "Validation_report.qmd") + } + } + + if (!file.exists(template)) { + PEcAn.logger::logger.severe("Template file not found:", template) + stop("Quarto template not found.") + } + + if (!requireNamespace("quarto", quietly = TRUE)) { + PEcAn.logger::logger.severe("The 'quarto' package is required to generate the report.") + stop("Please install the 'quarto' R package.") + } + + # Ensure absolute paths + output_file <- normalizePath(output_file, mustWork = FALSE) + output_dir <- dirname(output_file) + + if (!dir.exists(output_dir)) { + dir.create(output_dir, recursive = TRUE) + } + + # Copy template to output directory to avoid permission issues in system folders + temp_qmd <- file.path(output_dir, basename(template)) + file.copy(template, temp_qmd, overwrite = TRUE) + + # Render the document + tryCatch({ + quarto::quarto_render( + input = temp_qmd, + output_file = basename(output_file), + execute_params = list(benchmark_results = benchmark_results) + ) + + PEcAn.logger::logger.info("Validation report successfully generated at:", output_file) + }, error = function(e) { + PEcAn.logger::logger.severe("Failed to render validation report:", e$message) + stop(e) + }, finally = { + # Clean up the temporary template file + if (file.exists(temp_qmd)) { + file.remove(temp_qmd) + } + }) + + return(invisible(output_file)) +} diff --git a/modules/benchmark/R/metric_residual_plot.R b/modules/benchmark/R/metric_residual_plot.R index 38a20a65125..4678d29903b 100644 --- a/modules/benchmark/R/metric_residual_plot.R +++ b/modules/benchmark/R/metric_residual_plot.R @@ -10,22 +10,34 @@ metric_residual_plot <- function(metric_dat, var, filename = NA, draw.plot = is.na(filename)) { PEcAn.logger::logger.info("Metric: Residual Plot") - metric_dat$time <- lubridate::year(as.Date(as.character(metric_dat$time), format = "%Y")) - metric_dat$diff <- abs(metric_dat$model - metric_dat$obvs) - metric_dat$zeros <- rep(0, length(metric_dat$time)) + metric_dat <- as.data.frame(metric_dat) - p <- ggplot2::ggplot(data = metric_dat, ggplot2::aes(x = .data$time)) - p <- p + ggplot2::geom_path(ggplot2::aes(y = .data$zeros), colour = "#666666", size = 2, linetype = 2, lineend = "round") - p <- p + ggplot2::geom_point(ggplot2::aes(y = .data$diff), size = 4, colour = "#619CFF") - p <- p + ggplot2::labs(title = var, x = "years", y = "abs(model - observation)") + if (!"time" %in% colnames(metric_dat)) { + metric_dat$time <- seq_len(nrow(metric_dat)) + } else { + date.time <- try(as.Date(as.character(metric_dat$time)), silent = TRUE) + if (!inherits(date.time, "try-error") && !all(is.na(date.time))) { + metric_dat$time <- date.time + } + } + + # Calculate residuals (Model - Observation) + metric_dat$diff <- metric_dat$model - metric_dat$obvs + + p <- ggplot2::ggplot(data = metric_dat, ggplot2::aes(x = .data$time, y = .data$diff)) + + ggplot2::geom_hline(yintercept = 0, colour = "#666666", linewidth = 1, linetype = 2) + + ggplot2::geom_point(size = 2, alpha = 0.7, colour = "#619CFF") + + ggplot2::labs(title = var, x = "Time", y = "Residual (Model - Obs)") + + ggplot2::theme_minimal(base_size = 14) if (!is.na(filename)) { grDevices::pdf(filename, width = 10, height = 6) - plot(p) + print(p) grDevices::dev.off() } if (draw.plot) { return(p) } + invisible(p) } # metric_residual_plot \ No newline at end of file diff --git a/modules/benchmark/R/metric_scatter_plot.R b/modules/benchmark/R/metric_scatter_plot.R index 751d08e53b4..82e63b1903d 100644 --- a/modules/benchmark/R/metric_scatter_plot.R +++ b/modules/benchmark/R/metric_scatter_plot.R @@ -1,29 +1,32 @@ ##' Scatter Plot ##' ##' @param metric_dat dataframe to plot, with at least columns `model` and `obvs` -##' @param var ignored +##' @param var title for the plot ##' @param filename path to save plot, or NA to not save ##' @param draw.plot logical: Return the plot object? ##' ##' @author Betsy Cowdery ##' @export - metric_scatter_plot <- function(metric_dat, var, filename = NA, draw.plot = is.na(filename)) { PEcAn.logger::logger.info("Metric: Scatter Plot") - p <- ggplot2::ggplot(data = metric_dat) - p <- p + ggplot2::geom_point(ggplot2::aes(x = .data$model, y = .data$obvs), size = 4) - p <- p + ggplot2::geom_abline(slope = 1, intercept = 0, colour = "#666666", - size = 2, linetype = 2) + metric_dat <- as.data.frame(metric_dat) + + p <- ggplot2::ggplot(data = metric_dat, ggplot2::aes(x = .data$model, y = .data$obvs)) + + ggplot2::geom_point(size = 2, alpha = 0.7, colour = "#619CFF") + + ggplot2::geom_abline(slope = 1, intercept = 0, colour = "#666666", + linewidth = 1, linetype = 2) + + ggplot2::labs(title = var, x = "Modeled", y = "Observed") + + ggplot2::theme_minimal(base_size = 14) if (!is.na(filename)) { grDevices::pdf(filename, width = 10, height = 6) - plot(p) + print(p) grDevices::dev.off() } if (draw.plot) { return(p) } - + invisible(p) } # metric_scatter_plot diff --git a/modules/benchmark/R/metric_timeseries_plot.R b/modules/benchmark/R/metric_timeseries_plot.R index ca879b95648..93049f8e8f5 100644 --- a/modules/benchmark/R/metric_timeseries_plot.R +++ b/modules/benchmark/R/metric_timeseries_plot.R @@ -6,34 +6,43 @@ ##' @param draw.plot logical: Return the plot object? ##' ##' @author Betsy Cowdery -##' @importFrom ggplot2 ggplot labs geom_path geom_point ##' @export - metric_timeseries_plot <- function(metric_dat, var, filename = NA, draw.plot = is.na(filename)) { PEcAn.logger::logger.info("Metric: Timeseries Plot") - # Attempt at getting around the fact that time can be annual and thus as.Date won't work - date.time <- try(as.Date(metric_dat$time), silent = TRUE) - if (inherits(date.time, "try-error")) { - PEcAn.logger::logger.warn("Can't coerce time column to Date format, attempting plot anyway") - }else{ - metric_dat$time <- date.time + # Ensure metric_dat is a data.frame for ggplot2 + metric_dat <- as.data.frame(metric_dat) + + if (!"time" %in% colnames(metric_dat)) { + PEcAn.logger::logger.warn("Missing 'time' column in metric_dat, using row index instead.") + metric_dat$time <- seq_len(nrow(metric_dat)) + } else { + date.time <- try(as.Date(as.character(metric_dat$time)), silent = TRUE) + if (!inherits(date.time, "try-error") && !all(is.na(date.time))) { + metric_dat$time <- date.time + } else { + PEcAn.logger::logger.warn("Can't coerce time column to Date format, using original format.") + } } - p <- ggplot(data = metric_dat, ggplot2::aes(x = .data$time)) - p <- p + labs(title = var, y = "") - p <- p + geom_path(ggplot2::aes(y = .data$model, colour = "Model"), size = 2) - p <- p + geom_point(ggplot2::aes(y = .data$model, colour = "Model"), size = 4) - p <- p + geom_path(ggplot2::aes(y = .data$obvs, colour = "Observed"), size = 2) - p <- p + geom_point(ggplot2::aes(y = .data$obvs, colour = "Observed"), size = 4) + p <- ggplot2::ggplot(data = metric_dat, ggplot2::aes(x = .data$time)) + + ggplot2::geom_line(ggplot2::aes(y = .data$model, colour = "Model"), linewidth = 1) + + ggplot2::geom_point(ggplot2::aes(y = .data$model, colour = "Model"), size = 2, alpha = 0.7) + + ggplot2::geom_line(ggplot2::aes(y = .data$obvs, colour = "Observed"), linewidth = 1) + + ggplot2::geom_point(ggplot2::aes(y = .data$obvs, colour = "Observed"), size = 2, alpha = 0.7) + + ggplot2::scale_colour_manual(values = c("Model" = "#619CFF", "Observed" = "#F8766D")) + + ggplot2::labs(title = var, x = "Time", y = "Value", color = "Source") + + ggplot2::theme_minimal(base_size = 14) + + ggplot2::theme(legend.position = "bottom") if (!is.na(filename)) { grDevices::pdf(filename, width = 10, height = 6) - plot(p) + print(p) grDevices::dev.off() } if (draw.plot) { return(p) } + invisible(p) } # metric_timeseries_plot diff --git a/modules/benchmark/inst/reports/Validation_report.qmd b/modules/benchmark/inst/reports/Validation_report.qmd new file mode 100644 index 00000000000..d8b08067185 --- /dev/null +++ b/modules/benchmark/inst/reports/Validation_report.qmd @@ -0,0 +1,75 @@ +--- +title: "PEcAn Validation Benchmark Report" +author: "PEcAn Validation Toolkit" +format: + html: + theme: cosmo + toc: true + toc-depth: 3 + embed-resources: true +params: + benchmark_results: null +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE) +library(ggplot2) +``` + +## Executive Summary + +The following scorecard summarizes the validation metrics computed during the benchmark run. + +```{r scorecard} +results <- params$benchmark_results + +if (!is.null(results) && !is.null(results$metrics)) { + # Check if knitr is available to use kable + if (requireNamespace("knitr", quietly = TRUE)) { + knitr::kable(results$metrics, digits = 3) + } else { + print(results$metrics) + } +} else { + cat("No metrics data provided.\n") +} +``` + +## Visual Diagnostics + +```{r plots, results='asis', fig.width=10, fig.height=6} +if (!is.null(results) && !is.null(results$plots)) { + plots <- results$plots + + # if plots is a flat list of ggplot objects + if (inherits(plots[[1]], "ggplot")) { + for (p_name in names(plots)) { + cat(sprintf("\n### %s\n\n", p_name)) + print(plots[[p_name]]) + cat("\n\n") + } + } else { + # If plots are nested by variable: plots[[var_name]]$timeseries + for (var in names(plots)) { + cat(sprintf("\n### Variable: %s\n\n", var)) + + var_plots <- plots[[var]] + + if (!is.null(var_plots$timeseries)) { + print(var_plots$timeseries) + cat("\n\n") + } + if (!is.null(var_plots$scatter)) { + print(var_plots$scatter) + cat("\n\n") + } + if (!is.null(var_plots$residual)) { + print(var_plots$residual) + cat("\n\n") + } + } + } +} else { + cat("No plots provided.\n") +} +``` diff --git a/modules/benchmark/tests/testthat/test-visualization.R b/modules/benchmark/tests/testthat/test-visualization.R new file mode 100644 index 00000000000..b8fac28f3b2 --- /dev/null +++ b/modules/benchmark/tests/testthat/test-visualization.R @@ -0,0 +1,53 @@ +test_that("metric_timeseries_plot generates a ggplot", { + mock_data <- data.frame( + time = as.Date("2020-01-01") + 0:9, + model = runif(10, 10, 20), + obvs = runif(10, 10, 20) + ) + + p <- metric_timeseries_plot(mock_data, var = "Test Variable", draw.plot = TRUE) + + expect_true(inherits(p, "ggplot")) +}) + +test_that("metric_scatter_plot generates a ggplot", { + mock_data <- data.frame( + model = runif(10, 10, 20), + obvs = runif(10, 10, 20) + ) + + p <- metric_scatter_plot(mock_data, var = "Test Variable", draw.plot = TRUE) + + expect_true(inherits(p, "ggplot")) +}) + +test_that("metric_residual_plot generates a ggplot", { + mock_data <- data.frame( + time = as.Date("2020-01-01") + 0:9, + model = runif(10, 10, 20), + obvs = runif(10, 10, 20) + ) + + p <- metric_residual_plot(mock_data, var = "Test Variable", draw.plot = TRUE) + + expect_true(inherits(p, "ggplot")) +}) + +test_that("generate_validation_report fails gracefully if no template", { + # Mock benchmark results + mock_results <- list( + metrics = data.frame(Metric = "RMSE", Value = 1.2), + plots = list( + "Var1" = list( + timeseries = metric_timeseries_plot( + data.frame(time = 1:5, model = 1:5, obvs = 1:5), "Var1", draw.plot = TRUE + ) + ) + ) + ) + + expect_error( + generate_validation_report(mock_results, template = "non_existent.qmd"), + "Quarto template not found" + ) +}) From 1f2609fbfc7b58b2bd28e85553c294a054b56e20 Mon Sep 17 00:00:00 2001 From: ayushman1210 Date: Fri, 3 Jul 2026 10:53:43 +0530 Subject: [PATCH 2/5] Fix: Serialize benchmark_results to RDS to prevent Quarto YAML parsing crash --- modules/benchmark/R/generate_validation_report.R | 7 ++++++- modules/benchmark/inst/reports/Validation_report.qmd | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/benchmark/R/generate_validation_report.R b/modules/benchmark/R/generate_validation_report.R index 6399cbcc16f..838722846ce 100644 --- a/modules/benchmark/R/generate_validation_report.R +++ b/modules/benchmark/R/generate_validation_report.R @@ -39,12 +39,17 @@ generate_validation_report <- function(benchmark_results, output_file = "Validat temp_qmd <- file.path(output_dir, basename(template)) file.copy(template, temp_qmd, overwrite = TRUE) + # Quarto execute_params are converted to YAML. Complex R objects like ggplots + # cannot be passed via YAML. We must save them to an RDS and pass the path. + results_rds <- file.path(output_dir, "benchmark_results.rds") + saveRDS(benchmark_results, results_rds) + # Render the document tryCatch({ quarto::quarto_render( input = temp_qmd, output_file = basename(output_file), - execute_params = list(benchmark_results = benchmark_results) + execute_params = list(benchmark_results = results_rds) ) PEcAn.logger::logger.info("Validation report successfully generated at:", output_file) diff --git a/modules/benchmark/inst/reports/Validation_report.qmd b/modules/benchmark/inst/reports/Validation_report.qmd index d8b08067185..36dc039d1cc 100644 --- a/modules/benchmark/inst/reports/Validation_report.qmd +++ b/modules/benchmark/inst/reports/Validation_report.qmd @@ -21,7 +21,12 @@ library(ggplot2) The following scorecard summarizes the validation metrics computed during the benchmark run. ```{r scorecard} -results <- params$benchmark_results +# Quarto execute_params passes strings. If it's a file path, load the RDS. +if (is.character(params$benchmark_results) && file.exists(params$benchmark_results)) { + results <- readRDS(params$benchmark_results) +} else { + results <- params$benchmark_results +} if (!is.null(results) && !is.null(results$metrics)) { # Check if knitr is available to use kable From 4da8557455b5153c5f9e0df2011b76d20148abfc Mon Sep 17 00:00:00 2001 From: ayushman1210 Date: Mon, 6 Jul 2026 14:09:51 +0530 Subject: [PATCH 3/5] Fix CI: Add quarto dependency, update tests, and sync Roxygen docs --- modules/benchmark/DESCRIPTION | 1 + modules/benchmark/NAMESPACE | 5 +--- .../man/generate_validation_report.Rd | 25 +++++++++++++++++++ modules/benchmark/man/metric_scatter_plot.Rd | 2 +- .../tests/testthat/test-visualization.R | 2 +- 5 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 modules/benchmark/man/generate_validation_report.Rd diff --git a/modules/benchmark/DESCRIPTION b/modules/benchmark/DESCRIPTION index f8ada65d1d7..7606f26d1dd 100644 --- a/modules/benchmark/DESCRIPTION +++ b/modules/benchmark/DESCRIPTION @@ -46,6 +46,7 @@ Imports: zoo, yaml Suggests: + quarto, PEcAn.data.land, testthat (>= 2.0.0) License: BSD_3_clause + file LICENSE diff --git a/modules/benchmark/NAMESPACE b/modules/benchmark/NAMESPACE index 07067c6c4c0..24c97d902b2 100644 --- a/modules/benchmark/NAMESPACE +++ b/modules/benchmark/NAMESPACE @@ -13,6 +13,7 @@ export(clean_settings_BRR) export(create_BRR) export(define_benchmark) export(format_wide2long) +export(generate_validation_report) export(load_and_map_data) export(load_csv) export(load_data) @@ -42,10 +43,6 @@ export(read_settings_BRR) export(register_metric) export(run_benchmark) importFrom(dplyr,rename) -importFrom(ggplot2,geom_path) -importFrom(ggplot2,geom_point) -importFrom(ggplot2,ggplot) -importFrom(ggplot2,labs) importFrom(magrittr,"%>%") importFrom(rlang,.data) importFrom(yaml,read_yaml) diff --git a/modules/benchmark/man/generate_validation_report.Rd b/modules/benchmark/man/generate_validation_report.Rd new file mode 100644 index 00000000000..d7a8b767cf5 --- /dev/null +++ b/modules/benchmark/man/generate_validation_report.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/generate_validation_report.R +\name{generate_validation_report} +\alias{generate_validation_report} +\title{Generate Validation Benchmark Report} +\usage{ +generate_validation_report( + benchmark_results, + output_file = "Validation_report.html", + template = NULL +) +} +\arguments{ +\item{benchmark_results}{A list containing `metrics` (data.frame), `aligned_data` (data.frame), and `plots` (list of ggplot objects) returned by the validation pipeline.} + +\item{output_file}{The path where the compiled report should be saved (e.g., "validation_report.html").} + +\item{template}{The path to the Quarto template. Defaults to the one provided in the package `inst/reports/Validation_report.qmd`.} +} +\description{ +Generate Validation Benchmark Report +} +\author{ +PEcAn Project +} diff --git a/modules/benchmark/man/metric_scatter_plot.Rd b/modules/benchmark/man/metric_scatter_plot.Rd index 93da2a3db5e..cc1b94749bf 100644 --- a/modules/benchmark/man/metric_scatter_plot.Rd +++ b/modules/benchmark/man/metric_scatter_plot.Rd @@ -14,7 +14,7 @@ metric_scatter_plot( \arguments{ \item{metric_dat}{dataframe to plot, with at least columns `model` and `obvs`} -\item{var}{ignored} +\item{var}{title for the plot} \item{filename}{path to save plot, or NA to not save} diff --git a/modules/benchmark/tests/testthat/test-visualization.R b/modules/benchmark/tests/testthat/test-visualization.R index b8fac28f3b2..e738bb7dd51 100644 --- a/modules/benchmark/tests/testthat/test-visualization.R +++ b/modules/benchmark/tests/testthat/test-visualization.R @@ -48,6 +48,6 @@ test_that("generate_validation_report fails gracefully if no template", { expect_error( generate_validation_report(mock_results, template = "non_existent.qmd"), - "Quarto template not found" + "Template file not found: non_existent.qmd" ) }) From 8efa2108faa7eff39f81dbd9b1a429c5fdf0cb13 Mon Sep 17 00:00:00 2001 From: ayushman1210 Date: Mon, 6 Jul 2026 14:24:35 +0530 Subject: [PATCH 4/5] Feat: Add uncertainty ribbon and error bars to timeseries plot --- modules/benchmark/R/metric_timeseries_plot.R | 28 +++++-- test_ameri.R | 83 ++++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 test_ameri.R diff --git a/modules/benchmark/R/metric_timeseries_plot.R b/modules/benchmark/R/metric_timeseries_plot.R index 93049f8e8f5..0e4966f8c56 100644 --- a/modules/benchmark/R/metric_timeseries_plot.R +++ b/modules/benchmark/R/metric_timeseries_plot.R @@ -25,12 +25,30 @@ metric_timeseries_plot <- function(metric_dat, var, filename = NA, draw.plot = i } } - p <- ggplot2::ggplot(data = metric_dat, ggplot2::aes(x = .data$time)) + - ggplot2::geom_line(ggplot2::aes(y = .data$model, colour = "Model"), linewidth = 1) + - ggplot2::geom_point(ggplot2::aes(y = .data$model, colour = "Model"), size = 2, alpha = 0.7) + - ggplot2::geom_line(ggplot2::aes(y = .data$obvs, colour = "Observed"), linewidth = 1) + - ggplot2::geom_point(ggplot2::aes(y = .data$obvs, colour = "Observed"), size = 2, alpha = 0.7) + + p <- ggplot2::ggplot(data = metric_dat, ggplot2::aes(x = .data$time)) + + # 1. Model Ribbon (if available) + if (all(c("model_q05", "model_q95") %in% colnames(metric_dat))) { + p <- p + ggplot2::geom_ribbon(ggplot2::aes(ymin = .data$model_q05, ymax = .data$model_q95, fill = "Model 90% CI"), alpha = 0.3) + } + + # 2. Model Mean Line + p <- p + ggplot2::geom_line(ggplot2::aes(y = .data$model, colour = "Model"), linewidth = 1) + + # 3. Observational Points & Error Bars + if ("obvs_sd" %in% colnames(metric_dat)) { + p <- p + ggplot2::geom_pointrange( + ggplot2::aes(y = .data$obvs, ymin = .data$obvs - .data$obvs_sd, ymax = .data$obvs + .data$obvs_sd, colour = "Observed"), + size = 0.5, alpha = 0.7 + ) + } else { + p <- p + ggplot2::geom_point(ggplot2::aes(y = .data$obvs, colour = "Observed"), size = 2, alpha = 0.7) + } + + # Adjust Scales + p <- p + ggplot2::scale_colour_manual(values = c("Model" = "#619CFF", "Observed" = "#F8766D")) + + ggplot2::scale_fill_manual(values = c("Model 90% CI" = "#619CFF"), name = "Intervals") + ggplot2::labs(title = var, x = "Time", y = "Value", color = "Source") + ggplot2::theme_minimal(base_size = 14) + ggplot2::theme(legend.position = "bottom") diff --git a/test_ameri.R b/test_ameri.R new file mode 100644 index 00000000000..4d86ea66221 --- /dev/null +++ b/test_ameri.R @@ -0,0 +1,83 @@ +# --- DUMMY LOGGER TO BYPASS DEPENDENCIES --- +PEcAn.logger <- new.env() +PEcAn.logger$logger.info <- function(...) cat("INFO:", ..., "\n") +PEcAn.logger$logger.warn <- function(...) cat("WARN:", ..., "\n") +PEcAn.logger$logger.severe <- function(...) stop("SEVERE:", ...) +`::` <- function(pkg, name) { + pkg_name <- as.character(substitute(pkg)) + name_str <- as.character(substitute(name)) + if (pkg_name == "PEcAn.logger") return(get(name_str, envir = PEcAn.logger)) + + # Safely call original :: + orig_colon <- get("::", envir = asNamespace("base")) + eval(substitute(orig_colon(pkg, name))) +} +# ------------------------------------------- + +# Source all R files in the benchmark module locally +lapply(list.files("modules/benchmark/R", pattern="\\.R$", full.names=TRUE), source) + +# 1. LOAD YOUR CSV +csv_path <- "/home/ayushman1210/Downloads/observations.csv" # Replace with your actual path! + +# Load the raw CSV +raw_df <- read.csv(csv_path, check.names = FALSE) + +# Fix the unnamed 7th column (which holds the variable name) +names(raw_df)[7] <- "variable" + +# 2. EXTRACT AND FORMAT THE OBSERVATIONS +obvs_df <- raw_df[raw_df$variable == "NEE", ] +obvs_df$time <- as.POSIXct(as.Date(obvs_df$max_date, format="%m/%d/%Y")) +obvs_df$obvs <- as.numeric(obvs_df$value) +obvs_df <- obvs_df[, c("time", "obvs")] + +# 3. CREATE A FAKE "MODEL" DATASET (For testing purposes today) +set.seed(42) +model_df <- obvs_df +model_df$model <- model_df$obvs + rnorm(nrow(model_df), mean = 0, sd = 0.5) + +# 4. ALIGN THE DATA +# Bypassing align_data to avoid lubridate requirement +aligned_df <- data.frame( + time = obvs_df$time, + model = model_df$model, + obvs = obvs_df$obvs, + model_q05 = model_df$model - runif(nrow(model_df), 0.5, 1.5), + model_q95 = model_df$model + runif(nrow(model_df), 0.5, 1.5), + obvs_sd = runif(nrow(obvs_df), 0.2, 0.8) +) + +# 5. CALCULATE METRICS +metrics_list <- list( + RMSE = metric_RMSE(aligned_df), + R2 = metric_R2(aligned_df), + MAE = metric_MAE(aligned_df) +) + +metrics_df <- data.frame( + Metric = names(metrics_list), + Value = as.numeric(metrics_list) +) + +# 6. GENERATE PLOTS +p_timeseries <- metric_timeseries_plot(aligned_df, var = "NEE") +p_scatter <- metric_scatter_plot(aligned_df, var = "NEE") +p_residual <- metric_residual_plot(aligned_df, var = "NEE") + +# 7. BUNDLE AND RENDER REPORT +benchmark_results <- list( + metrics = metrics_df, + aligned_data = aligned_df, + plots = list( + "NEE Timeseries" = p_timeseries, + "NEE Scatter" = p_scatter, + "NEE Residuals" = p_residual + ) +) + +generate_validation_report( + benchmark_results = benchmark_results, + output_file = "AmeriFlux_Validation_Report.html", + template = "modules/benchmark/inst/reports/Validation_report.qmd" +) From 9635b362f88fb7ad1757ebf4602371efd332b74c Mon Sep 17 00:00:00 2001 From: ayushman1210 Date: Mon, 6 Jul 2026 14:28:19 +0530 Subject: [PATCH 5/5] chore: remove local scratch script from tracking --- test_ameri.R | 83 ---------------------------------------------------- 1 file changed, 83 deletions(-) delete mode 100644 test_ameri.R diff --git a/test_ameri.R b/test_ameri.R deleted file mode 100644 index 4d86ea66221..00000000000 --- a/test_ameri.R +++ /dev/null @@ -1,83 +0,0 @@ -# --- DUMMY LOGGER TO BYPASS DEPENDENCIES --- -PEcAn.logger <- new.env() -PEcAn.logger$logger.info <- function(...) cat("INFO:", ..., "\n") -PEcAn.logger$logger.warn <- function(...) cat("WARN:", ..., "\n") -PEcAn.logger$logger.severe <- function(...) stop("SEVERE:", ...) -`::` <- function(pkg, name) { - pkg_name <- as.character(substitute(pkg)) - name_str <- as.character(substitute(name)) - if (pkg_name == "PEcAn.logger") return(get(name_str, envir = PEcAn.logger)) - - # Safely call original :: - orig_colon <- get("::", envir = asNamespace("base")) - eval(substitute(orig_colon(pkg, name))) -} -# ------------------------------------------- - -# Source all R files in the benchmark module locally -lapply(list.files("modules/benchmark/R", pattern="\\.R$", full.names=TRUE), source) - -# 1. LOAD YOUR CSV -csv_path <- "/home/ayushman1210/Downloads/observations.csv" # Replace with your actual path! - -# Load the raw CSV -raw_df <- read.csv(csv_path, check.names = FALSE) - -# Fix the unnamed 7th column (which holds the variable name) -names(raw_df)[7] <- "variable" - -# 2. EXTRACT AND FORMAT THE OBSERVATIONS -obvs_df <- raw_df[raw_df$variable == "NEE", ] -obvs_df$time <- as.POSIXct(as.Date(obvs_df$max_date, format="%m/%d/%Y")) -obvs_df$obvs <- as.numeric(obvs_df$value) -obvs_df <- obvs_df[, c("time", "obvs")] - -# 3. CREATE A FAKE "MODEL" DATASET (For testing purposes today) -set.seed(42) -model_df <- obvs_df -model_df$model <- model_df$obvs + rnorm(nrow(model_df), mean = 0, sd = 0.5) - -# 4. ALIGN THE DATA -# Bypassing align_data to avoid lubridate requirement -aligned_df <- data.frame( - time = obvs_df$time, - model = model_df$model, - obvs = obvs_df$obvs, - model_q05 = model_df$model - runif(nrow(model_df), 0.5, 1.5), - model_q95 = model_df$model + runif(nrow(model_df), 0.5, 1.5), - obvs_sd = runif(nrow(obvs_df), 0.2, 0.8) -) - -# 5. CALCULATE METRICS -metrics_list <- list( - RMSE = metric_RMSE(aligned_df), - R2 = metric_R2(aligned_df), - MAE = metric_MAE(aligned_df) -) - -metrics_df <- data.frame( - Metric = names(metrics_list), - Value = as.numeric(metrics_list) -) - -# 6. GENERATE PLOTS -p_timeseries <- metric_timeseries_plot(aligned_df, var = "NEE") -p_scatter <- metric_scatter_plot(aligned_df, var = "NEE") -p_residual <- metric_residual_plot(aligned_df, var = "NEE") - -# 7. BUNDLE AND RENDER REPORT -benchmark_results <- list( - metrics = metrics_df, - aligned_data = aligned_df, - plots = list( - "NEE Timeseries" = p_timeseries, - "NEE Scatter" = p_scatter, - "NEE Residuals" = p_residual - ) -) - -generate_validation_report( - benchmark_results = benchmark_results, - output_file = "AmeriFlux_Validation_Report.html", - template = "modules/benchmark/inst/reports/Validation_report.qmd" -)