From 99399e8004d99ce4727e96ee8d5da3860b211e03 Mon Sep 17 00:00:00 2001 From: DominiqueMakowski Date: Mon, 27 Apr 2026 13:27:32 +0100 Subject: [PATCH 01/11] init --- DESCRIPTION | 3 +- NAMESPACE | 7 + NEWS.md | 10 ++ R/report.R | 11 +- R/report_ai.R | 289 ++++++++++++++++++++++++++++++++ man/report.Rd | 8 +- man/report_ai.Rd | 45 +++++ tests/testthat/test-report_ai.R | 144 ++++++++++++++++ vignettes/report_ai.Rmd | 103 ++++++++++++ 9 files changed, 617 insertions(+), 3 deletions(-) create mode 100644 R/report_ai.R create mode 100644 man/report_ai.Rd create mode 100644 tests/testthat/test-report_ai.R create mode 100644 vignettes/report_ai.Rmd diff --git a/DESCRIPTION b/DESCRIPTION index cca54e64f..9bbf9215a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: report Type: Package Title: Automated Reporting of Results and Statistical Models -Version: 0.6.3 +Version: 0.6.3.1 Authors@R: c(person(given = "Dominique", family = "Makowski", @@ -135,6 +135,7 @@ Collate: 'report.survreg.R' 'report.test_performance.R' 'report.zeroinfl.R' + 'report_ai.R' 'report_effectsize.R' 'report_htest_chi2.R' 'report_htest_cor.R' diff --git a/NAMESPACE b/NAMESPACE index b835971fd..3447bd881 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -13,6 +13,7 @@ S3method(format_model,character) S3method(format_model,default) S3method(print,cite_easystats) S3method(print,report) +S3method(print,report_ai) S3method(print,report_effectsize) S3method(print,report_info) S3method(print,report_intercept) @@ -60,6 +61,11 @@ S3method(report,stanreg) S3method(report,survreg) S3method(report,test_performance) S3method(report,zeroinfl) +S3method(report_ai,default) +S3method(report_ai,glm) +S3method(report_ai,glmmTMB) +S3method(report_ai,lm) +S3method(report_ai,merMod) S3method(report_effectsize,MixMod) S3method(report_effectsize,anova) S3method(report_effectsize,aov) @@ -299,6 +305,7 @@ export(is.report) export(print_html) export(print_md) export(report) +export(report_ai) export(report_date) export(report_effectsize) export(report_info) diff --git a/NEWS.md b/NEWS.md index 56826b154..794fe0902 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,13 @@ +# report 0.6.x + +New features + +* `report_ai()`: add support for `glm`, `merMod` (lme4), and `glmmTMB` model classes. +* `report_ai()`: `## Model` section now includes a CI / degrees-of-freedom estimation line (e.g., `Inference: 95% CI [Satterthwaite df]`) when the information is available from `parameters::model_parameters()`. +* `report_ai.default()`: instead of stopping with an error, now emits a warning and falls back to the standard `report()` output so that documents continue to render for unsupported model classes. +* `report()`: new `audience` argument (`"humans"` (default) or `"ai"`). When `"ai"`, `report()` delegates to `report_ai()`. The default can be set globally via `options(report_audience = "ai")`. +* New vignette: *AI-Optimized Reports* — explains `report_ai()`, the `audience` argument, and how to convert an entire Quarto document with a single option. + # report 0.6.3 Bug fixes diff --git a/R/report.R b/R/report.R index 70b8fe313..1c63d36cd 100644 --- a/R/report.R +++ b/R/report.R @@ -20,6 +20,11 @@ #' #' @param x The R object that you want to report (see list of of supported #' objects above). +#' @param audience The intended audience for the report. `"humans"` (default) +#' produces the standard formatted text report. `"ai"` produces a compact, +#' structured output optimised for consumption by a Large Language Model (LLM) +#' or AI agent via [report_ai()]. The default can be changed globally with +#' `options(report_audience = "ai")`. #' @param ... Arguments passed to or from other methods. #' #' @details @@ -97,7 +102,11 @@ #' summary(as.data.frame(r)) #' #' @export -report <- function(x, ...) { +report <- function(x, ..., audience = getOption("report_audience", "humans")) { + audience <- match.arg(audience, c("humans", "ai")) + if (audience == "ai") { + return(report_ai(x, ...)) + } UseMethod("report") } diff --git a/R/report_ai.R b/R/report_ai.R new file mode 100644 index 000000000..5a37801b6 --- /dev/null +++ b/R/report_ai.R @@ -0,0 +1,289 @@ +#' Generate AI-optimized reports +#' +#' This function is designed to produce AI-optimized output for statistical models. +#' It strikes a careful balance between comprehensiveness, specificity, and compactness. +#' The primary goal is to provide a Large Language Model (LLM) or AI agent with the +#' clearest and most relevant analytical information at the lowest possible token cost. +#' +#' @param x A statistical model. +#' @param ... Arguments passed to other functions, like \code{parameters::model_parameters()}, +#' \code{performance::model_performance()} or \code{insight::format_table()}. +#' @return A character vector of class `report_ai` containing the formatted text. +#' +#' @examples +#' m <- lm(mpg ~ wt + hp, data = mtcars) +#' report_ai(m) +#' @export +report_ai <- function(x, ...) { + UseMethod("report_ai") +} + +#' @export +report_ai.default <- function(x, ...) { + warning( + sprintf( + "AI-optimized reports are not yet available for objects of class '%s'. Falling back to report().", + class(x)[1] + ), + call. = FALSE + ) + report(x, ...) +} + +#' @export +report_ai.lm <- function(x, ...) { + .report_ai_models(x, ...) +} + +#' @export +report_ai.glm <- report_ai.lm + +#' @rdname report_ai +#' @examplesIf requireNamespace("lme4", quietly = TRUE) +#' \donttest{ +#' m <- lme4::lmer(Reaction ~ Days + (1 | Subject), data = lme4::sleepstudy) +#' report_ai(m) +#' } +#' @export +report_ai.merMod <- function(x, ...) { + .report_ai_models(x, ...) +} + +#' @rdname report_ai +#' @examplesIf requireNamespace("glmmTMB", quietly = TRUE) +#' \donttest{ +#' m <- glmmTMB::glmmTMB(count ~ mined + (1 | site), family = poisson(), data = glmmTMB::Salamanders) +#' report_ai(m) +#' } +#' @export +report_ai.glmmTMB <- function(x, ...) { + .report_ai_models(x, ...) +} + + +# --- Internal Workhorse Function --- +.report_ai_models <- function(x, ...) { + mi <- insight::model_info(x) + dat <- insight::get_data(x) + n_obs <- insight::n_obs(x) + form <- insight::find_formula(x) + + func_name <- tryCatch( + as.character(insight::get_call(x)[[1]]), + error = function(e) class(x)[1] + ) + mod_family <- if (!is.null(mi$family)) mi$family else "Unknown" + + model_vars_list <- insight::find_variables(x) + # Use only response + conditional (fixed) variables for descriptives; + # random grouping variables (e.g. Subject) are excluded. + fixed_var_comps <- intersect( + c("response", "conditional"), + names(model_vars_list) + ) + fixed_vars <- unique(unlist( + model_vars_list[fixed_var_comps], + use.names = FALSE + )) + fixed_vars <- fixed_vars[fixed_vars %in% colnames(dat)] + + if (length(fixed_vars) > 0) { + desc_report <- suppressWarnings(summary(report::report( + dat[, fixed_vars, drop = FALSE] + ))) + desc_lines <- unlist(strsplit(as.character(desc_report), "\n")) + + if (length(desc_lines) > 1) { + # Use trimws() to kill the spaces that cause nested bullets + clean_lines <- trimws(desc_lines[-1]) + desc_str <- paste0(clean_lines, collapse = "\n") + } else { + desc_str <- paste0(trimws(desc_lines), collapse = "\n") + } + } else { + desc_str <- "- No variables found." + } + + params <- parameters::model_parameters(x, ...) + + # Separate fixed and random effects to avoid duplicated table headers + # (model_parameters returns both in one table for mixed models) + has_random <- "Effects" %in% + names(params) && + any(!is.na(params$Effects) & params$Effects != "fixed") + + if (has_random) { + fixed_params <- params[ + !is.na(params$Effects) & params$Effects == "fixed", + , + drop = FALSE + ] + random_params <- params[ + !is.na(params$Effects) & params$Effects != "fixed", + , + drop = FALSE + ] + } else { + fixed_params <- params + random_params <- NULL + } + + param_str <- insight::format_table(fixed_params) |> + insight::export_table(format = "markdown") |> + paste0(collapse = "\n") + + # Format random effect variances as metadata bullet points + random_str <- NULL + if (!is.null(random_params) && nrow(random_params) > 0) { + coef_col <- intersect( + c("Coefficient", "Estimate", "SD"), + names(random_params) + )[1] + random_str <- paste( + vapply( + seq_len(nrow(random_params)), + function(i) { + row <- random_params[i, , drop = FALSE] + param_name <- if ("Parameter" %in% names(row)) { + as.character(row$Parameter) + } else { + "?" + } + group_tag <- if ( + "Group" %in% + names(row) && + !is.na(row$Group) && + nchar(as.character(row$Group)) > 0 + ) { + paste0(" [", row$Group, "]") + } else { + "" + } + val <- if (!is.na(coef_col) && coef_col %in% names(row)) { + sprintf("%.3f", as.numeric(row[[coef_col]])) + } else { + "?" + } + paste0("- ", param_name, group_tag, ": ", val) + }, + character(1) + ), + collapse = "\n" + ) + } + + perf <- performance::model_performance(x, ...) + perf_str <- insight::format_table(perf) |> + insight::export_table(format = "markdown") |> + paste0(collapse = "\n") + + if ("p" %in% names(fixed_params) && "Parameter" %in% names(fixed_params)) { + sig_effects <- fixed_params$Parameter[ + !is.na(fixed_params$p) & + fixed_params$p < 0.05 & + fixed_params$Parameter != "(Intercept)" + ] + highlights_str <- if (length(sig_effects) == 0) { + "- Significant effects: None" + } else { + sprintf( + "- Significant effects (p < 0.05): %s", + paste(sig_effects, collapse = ", ") + ) + } + } else { + highlights_str <- "- Significant effects: Could not be determined." + } + + formula_str <- if (is.list(form)) { + Reduce(paste, deparse(form$conditional)) + } else { + Reduce(paste, deparse(form)) + } + + # CI / degrees-of-freedom estimation method + ci_level <- attr(params, "ci") + ci_method <- attr(params, "ci_method") + if (!is.null(ci_level) && !is.null(ci_method)) { + ci_pct <- sprintf("%.0f%%", ci_level * 100) + ci_label <- .ci_method_label(ci_method) + inference_str <- paste0("- Inference: ", ci_pct, " CI [", ci_label, "]") + } else if (!is.null(ci_level)) { + inference_str <- paste0( + "- Inference: ", + sprintf("%.0f%%", ci_level * 100), + " CI" + ) + } else { + inference_str <- NULL + } + + param_section <- if (!is.null(random_str)) { + paste0("## Parameters\n", param_str, "\n\n### Random Effects\n", random_str) + } else { + paste0("## Parameters\n", param_str) + } + + model_section <- paste0( + "## Model\n", + "- Call: ", + func_name, + "\n", + "- Formula: ", + formula_str, + "\n", + "- Family: ", + mod_family, + "\n", + "- N: ", + n_obs, + if (!is.null(inference_str)) paste0("\n", inference_str) else "" + ) + + res <- paste0( + model_section, + "\n\n", + "## Variables\n", + desc_str, + "\n\n", + param_section, + "\n\n", + "## Performance\n", + perf_str, + "\n\n", + "## Highlights\n", + highlights_str + ) + + class(res) <- c("report_ai", "character") + return(res) +} + +# Helper: human-readable CI / df-method label +.ci_method_label <- function(method) { + labels <- c( + wald = "Wald", + residual = "Residual df (t/F)", + satterthwaite = "Satterthwaite df", + kenward = "Kenward-Roger df", + normal = "Normal (z)", + profile = "Profile likelihood", + boot = "Bootstrap", + uniroot = "Uniroot", + hdi = "HDI", + eti = "ETI", + si = "SI" + ) + lab <- labels[tolower(as.character(method))] + if (is.na(lab)) { + tools::toTitleCase(tolower(as.character(method))) + } else { + unname(lab) + } +} + +#' @export +print.report_ai <- function(x, ...) { + cat(x, "\n") + invisible(x) +} diff --git a/man/report.Rd b/man/report.Rd index c795eee64..181175af8 100644 --- a/man/report.Rd +++ b/man/report.Rd @@ -4,13 +4,19 @@ \alias{report} \title{Automatic reporting of R objects} \usage{ -report(x, ...) +report(x, ..., audience = getOption("report_audience", "humans")) } \arguments{ \item{x}{The R object that you want to report (see list of of supported objects above).} \item{...}{Arguments passed to or from other methods.} + +\item{audience}{The intended audience for the report. \code{"humans"} (default) +produces the standard formatted text report. \code{"ai"} produces a compact, +structured output optimised for consumption by a Large Language Model (LLM) +or AI agent via \code{\link[=report_ai]{report_ai()}}. The default can be changed globally with +\code{options(report_audience = "ai")}.} } \value{ A list-object of class \code{report}, which contains further diff --git a/man/report_ai.Rd b/man/report_ai.Rd new file mode 100644 index 000000000..f2488289b --- /dev/null +++ b/man/report_ai.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/report_ai.R +\name{report_ai} +\alias{report_ai} +\alias{report_ai.merMod} +\alias{report_ai.glmmTMB} +\title{Generate AI-optimized reports} +\usage{ +report_ai(x, ...) + +\method{report_ai}{merMod}(x, ...) + +\method{report_ai}{glmmTMB}(x, ...) +} +\arguments{ +\item{x}{A statistical model.} + +\item{...}{Arguments passed to other functions, like \code{parameters::model_parameters()}, +\code{performance::model_performance()} or \code{insight::format_table()}.} +} +\value{ +A character vector of class \code{report_ai} containing the formatted text. +} +\description{ +This function is designed to produce AI-optimized output for statistical models. +It strikes a careful balance between comprehensiveness, specificity, and compactness. +The primary goal is to provide a Large Language Model (LLM) or AI agent with the +clearest and most relevant analytical information at the lowest possible token cost. +} +\examples{ +m <- lm(mpg ~ wt + hp, data = mtcars) +report_ai(m) +\dontshow{if (requireNamespace("lme4", quietly = TRUE)) withAutoprint(\{ # examplesIf} +\donttest{ +m <- lme4::lmer(Reaction ~ Days + (1 | Subject), data = lme4::sleepstudy) +report_ai(m) +} +\dontshow{\}) # examplesIf} +\dontshow{if (requireNamespace("glmmTMB", quietly = TRUE)) withAutoprint(\{ # examplesIf} +\donttest{ +m <- glmmTMB::glmmTMB(count ~ mined + (1 | site), family = poisson(), data = glmmTMB::Salamanders) +report_ai(m) +} +\dontshow{\}) # examplesIf} +} diff --git a/tests/testthat/test-report_ai.R b/tests/testthat/test-report_ai.R new file mode 100644 index 000000000..24b9178f2 --- /dev/null +++ b/tests/testthat/test-report_ai.R @@ -0,0 +1,144 @@ +test_that("report_ai.lm - basic structure", { + m <- lm(mpg ~ wt + hp, data = mtcars) + result <- report_ai(m) + + expect_s3_class(result, "report_ai") + expect_s3_class(result, "character") + expect_match(result, "## Model", fixed = TRUE) + expect_match(result, "## Variables", fixed = TRUE) + expect_match(result, "## Parameters", fixed = TRUE) + expect_match(result, "## Performance", fixed = TRUE) + expect_match(result, "## Highlights", fixed = TRUE) + # lm has no random effects section + expect_false(grepl("### Random Effects", result, fixed = TRUE)) + # CI / df method line + expect_match(result, "Inference:", fixed = TRUE) +}) + +test_that("report_ai.lm - model metadata", { + m <- lm(mpg ~ wt + hp, data = mtcars) + result <- report_ai(m) + + expect_match(result, "Call: lm", fixed = TRUE) + expect_match(result, "N: 32", fixed = TRUE) + expect_match(result, "gaussian", fixed = TRUE) + # significant predictors + expect_match(result, "wt", fixed = TRUE) +}) + +test_that("report_ai.lm - print method", { + m <- lm(mpg ~ wt + hp, data = mtcars) + result <- report_ai(m) + expect_output(print(result)) +}) + +test_that("report_ai.default - warns and falls back to report()", { + # htest has report() support but no dedicated report_ai() method + ht <- t.test(mtcars$mpg ~ mtcars$am) + expect_warning(result <- report_ai(ht), "not yet available") + expect_s3_class(result, "report") +}) + +test_that("report() audience argument dispatches to report_ai", { + m <- lm(mpg ~ wt + hp, data = mtcars) + result_ai <- report(m, audience = "ai") + result_human <- report(m, audience = "humans") + + expect_s3_class(result_ai, "report_ai") + expect_s3_class(result_human, "report") +}) + +test_that("report() respects report_audience option", { + m <- lm(mpg ~ wt + hp, data = mtcars) + old <- getOption("report_audience") + on.exit(options(report_audience = old)) + + options(report_audience = "ai") + expect_s3_class(report(m), "report_ai") + + options(report_audience = "humans") + expect_s3_class(report(m), "report") +}) + +test_that("report_ai.glm - binomial family", { + m <- glm(vs ~ mpg + hp, data = mtcars, family = binomial()) + result <- report_ai(m) + + expect_s3_class(result, "report_ai") + expect_match(result, "## Model", fixed = TRUE) + expect_match(result, "## Parameters", fixed = TRUE) + expect_match(result, "## Performance", fixed = TRUE) + expect_match(result, "binomial", fixed = TRUE) + expect_match(result, "Call: glm", fixed = TRUE) +}) + +test_that("report_ai.glm - poisson family", { + m <- glm(gear ~ mpg + hp, data = mtcars, family = poisson()) + result <- report_ai(m) + + expect_s3_class(result, "report_ai") + expect_match(result, "poisson", fixed = TRUE) +}) + +test_that("report_ai.merMod - lmer", { + skip_if_not_installed("lme4") + skip_on_cran() + + m <- lme4::lmer(Reaction ~ Days + (1 | Subject), data = lme4::sleepstudy) + result <- report_ai(m) + + expect_s3_class(result, "report_ai") + expect_match(result, "## Model", fixed = TRUE) + expect_match(result, "## Parameters", fixed = TRUE) + expect_match(result, "## Performance", fixed = TRUE) + expect_match(result, "Call: lmer", fixed = TRUE) + expect_match(result, "Days", fixed = TRUE) + # Random effects should appear as bullet points, not in the fixed-effects table + expect_match(result, "### Random Effects", fixed = TRUE) + expect_match(result, "[Subject]", fixed = TRUE) + # Subject (grouping variable) should NOT appear in the Variables section + expect_false(grepl( + "Subject", + strsplit(result, "## Variables\n")[[1]][2] |> + strsplit("## Parameters")[[1]][1] + )) +}) + +test_that("report_ai.merMod - glmer", { + skip_if_not_installed("lme4") + skip_on_cran() + + set.seed(123) + m <- lme4::glmer( + cbind(incidence, size - incidence) ~ period + (1 | herd), + data = lme4::cbpp, + family = binomial() + ) + result <- report_ai(m) + + expect_s3_class(result, "report_ai") + expect_match(result, "binomial", fixed = TRUE) + expect_match(result, "Call: glmer", fixed = TRUE) +}) + +test_that("report_ai.glmmTMB - poisson with random effect", { + skip_if_not_installed("glmmTMB") + skip_on_cran() + + set.seed(123) + m <- suppressWarnings(glmmTMB::glmmTMB( + count ~ mined + (1 | site), + family = poisson(), + data = glmmTMB::Salamanders + )) + result <- report_ai(m) + + expect_s3_class(result, "report_ai") + expect_match(result, "## Model", fixed = TRUE) + expect_match(result, "## Parameters", fixed = TRUE) + expect_match(result, "## Performance", fixed = TRUE) + expect_match(result, "Call: glmmTMB", fixed = TRUE) + expect_match(result, "poisson", fixed = TRUE) + # Random effects section should be present + expect_match(result, "### Random Effects", fixed = TRUE) +}) diff --git a/vignettes/report_ai.Rmd b/vignettes/report_ai.Rmd new file mode 100644 index 000000000..7d0bc108c --- /dev/null +++ b/vignettes/report_ai.Rmd @@ -0,0 +1,103 @@ +--- +title: "AI-Optimized Reports" +output: + rmarkdown::html_vignette: + toc: true + fig_width: 10.08 + fig_height: 6 +tags: [r, report, AI, LLM] +vignette: > + %\VignetteIndexEntry{AI-Optimized Reports} + \usepackage[utf8]{inputenc} + %\VignetteEngine{knitr::rmarkdown} +editor_options: + chunk_output_type: console +--- + +```{r setup, echo=FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + warning = FALSE, + message = FALSE +) +``` + +## How to Use `report()` and get AI-Optimized Output + +### The *"last statistical mile"* + +The **report** package was originally designed in a pre-AI era with one overarching goal: to produce human-readable prose. It was built to bridge the scientist's *"last statistical mile"*: that final, often tedious transition from the raw output of statistical software to the polished, written sentences of a manuscript. To achieve this, we leveraged the power **easystats**'s ecosystem to automatically and flexibly extract relevant information to engineer text with very specific characteristics: the output had to be **deterministic**, perfectly **consistent**, and adhere to fixed **reporting norms** (such as APA style). By automating this translation, our aim was to facilitate **fully reproducible research** (enabling reproducible *manuscripts*) and drastically reduce human error. Ultimately, this allowed us to shift the focus of **statistics education** away from memorizing where to find the right numbers and how to format them, empowering researchers to focus entirely on how to interpret and use those numbers to answer their research questions. + +### *easystats* in the Age of AI + +However, the analytical landscape has fundamentally changed. Today, researchers increasingly rely on AI agents and Large Language Models (LLMs) to help interpret results, summarize findings, and draft manuscripts, often by simply **pasting raw outputs directly into a chat window**. This shift prompted us to ask: should the scope of the *report* package be expanded to **support** this new workflow, rather than pretending it doesn't exist? And it wasn't even a matter of staying relevant, it was a matter of empowering our users to get the best possible results from their new AI assistants. + +While our standard narrative outputs are excellent for humans, they are remarkably suboptimal for AI: verbose prose forces an LLM to waste valuable context tokens re-parsing implicit structures, wasting tokens and context memory. The answer to this challenge is `report_ai()`, a function that can be triggered via an argument from the main `report()` function. By stripping away the narrative bloat in favour of highly structured, token-efficient formats, `report_ai()` bridges the gap between R and the context window, giving your AI assistant exactly the information it needs in the most effective way possible. + +## Basic Usage + +```{r} +library(report) + +m <- lm(mpg ~ wt + hp, data = mtcars) + +# Human-readable report (default) +report(m) +``` + +```{r} +# AI-optimized report +report(m, audience = "ai") +``` + +The AI output is a single character vector of class `report_ai` that you can +pass directly to any LLM API, embed in a system prompt, or include in a +context window. + +## Setting the Option Globally + +For a single analysis session or a whole document you can flip **all** `report()` +calls at once with one option: + +```{r eval=FALSE} +options(report_audience = "ai") + +# Every subsequent report() call now returns an AI-optimized output +report(m) +report(lm(mpg ~ am, data = mtcars)) +``` + +Reset to the default at any time: + +```{r eval=FALSE} +options(report_audience = "humans") +``` + +## Converting a Quarto Document + +If you have an existing Quarto (`.qmd`) or R Markdown (`.Rmd`) document that +already uses `report()` throughout, converting it to produce AI-optimized +output requires only **one line** added to the `setup` chunk: + +````{verbatim} +--- +title: "My Analysis" +format: html +--- + +```{r setup, include=FALSE} +library(report) + +# One line — all report() calls in this document become AI-optimised +options(report_audience = "ai") +``` + +```{r model} +m <- lm(mpg ~ wt + hp, data = mtcars) +report(m) # automatically AI-optimised because of the global option +``` +```` + +To make the option permanent across an entire Quarto project, add it to the +project-level `_quarto.yml` execute block or to your `.Rprofile`. From 570389c5a70198057014b51892337de8f201b6fe Mon Sep 17 00:00:00 2001 From: Dominique Makowski Date: Mon, 27 Apr 2026 14:12:35 +0100 Subject: [PATCH 02/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- R/report_ai.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/report_ai.R b/R/report_ai.R index 5a37801b6..192865604 100644 --- a/R/report_ai.R +++ b/R/report_ai.R @@ -27,7 +27,7 @@ report_ai.default <- function(x, ...) { ), call. = FALSE ) - report(x, ...) + report(x, ..., audience = "humans") } #' @export From 655f2d33d3c7b5bddf301d9b1198c83fd7506a19 Mon Sep 17 00:00:00 2001 From: Dominique Makowski Date: Mon, 27 Apr 2026 14:12:46 +0100 Subject: [PATCH 03/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- R/report_ai.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/report_ai.R b/R/report_ai.R index 192865604..e0f7f00a4 100644 --- a/R/report_ai.R +++ b/R/report_ai.R @@ -89,7 +89,8 @@ report_ai.glmmTMB <- function(x, ...) { if (length(fixed_vars) > 0) { desc_report <- suppressWarnings(summary(report::report( - dat[, fixed_vars, drop = FALSE] + dat[, fixed_vars, drop = FALSE], + audience = "humans" ))) desc_lines <- unlist(strsplit(as.character(desc_report), "\n")) From 73b351ca5495941d886318fbe20ad5ba4f3103c5 Mon Sep 17 00:00:00 2001 From: Dominique Makowski Date: Mon, 27 Apr 2026 14:13:04 +0100 Subject: [PATCH 04/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/testthat/test-report_ai.R | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/testthat/test-report_ai.R b/tests/testthat/test-report_ai.R index 24b9178f2..e6e870d87 100644 --- a/tests/testthat/test-report_ai.R +++ b/tests/testthat/test-report_ai.R @@ -39,6 +39,19 @@ test_that("report_ai.default - warns and falls back to report()", { expect_s3_class(result, "report") }) +test_that("report_ai.default - falls back to human report() when report_audience option is ai", { + # Regression test: fallback from report_ai.default() must not recurse back + # into AI routing when the global audience option is set to "ai". + ht <- t.test(mtcars$mpg ~ mtcars$am) + old <- getOption("report_audience") + on.exit(options(report_audience = old)) + + options(report_audience = "ai") + expect_warning(result <- report_ai(ht), "not yet available") + expect_s3_class(result, "report") + expect_false(inherits(result, "report_ai")) +}) + test_that("report() audience argument dispatches to report_ai", { m <- lm(mpg ~ wt + hp, data = mtcars) result_ai <- report(m, audience = "ai") From 84006e2f7e0a3486e4d85819a6c728dd6533bf51 Mon Sep 17 00:00:00 2001 From: DominiqueMakowski Date: Wed, 29 Apr 2026 12:06:01 +0100 Subject: [PATCH 05/11] fixes --- R/report_ai.R | 26 +++++++++++++------------- tests/testthat/test-report_ai.R | 5 +++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/R/report_ai.R b/R/report_ai.R index e0f7f00a4..8fb205e81 100644 --- a/R/report_ai.R +++ b/R/report_ai.R @@ -20,12 +20,12 @@ report_ai <- function(x, ...) { #' @export report_ai.default <- function(x, ...) { - warning( - sprintf( - "AI-optimized reports are not yet available for objects of class '%s'. Falling back to report().", - class(x)[1] - ), - call. = FALSE + insight::format_warning( + paste0( + "AI-optimized reports are not yet available for objects of class '", + class(x)[1], + "'. Falling back to report()." + ) ) report(x, ..., audience = "humans") } @@ -69,7 +69,7 @@ report_ai.glmmTMB <- function(x, ...) { form <- insight::find_formula(x) func_name <- tryCatch( - as.character(insight::get_call(x)[[1]]), + insight::safe_deparse(insight::get_call(x)[[1]]), error = function(e) class(x)[1] ) mod_family <- if (!is.null(mi$family)) mi$family else "Unknown" @@ -129,9 +129,9 @@ report_ai.glmmTMB <- function(x, ...) { random_params <- NULL } - param_str <- insight::format_table(fixed_params) |> - insight::export_table(format = "markdown") |> - paste0(collapse = "\n") + param_table <- insight::format_table(fixed_params) + param_markdown <- insight::export_table(param_table, format = "markdown") + param_str <- paste0(param_markdown, collapse = "\n") # Format random effect variances as metadata bullet points random_str <- NULL @@ -174,9 +174,9 @@ report_ai.glmmTMB <- function(x, ...) { } perf <- performance::model_performance(x, ...) - perf_str <- insight::format_table(perf) |> - insight::export_table(format = "markdown") |> - paste0(collapse = "\n") + perf_table <- insight::format_table(perf) + perf_markdown <- insight::export_table(perf_table, format = "markdown") + perf_str <- paste0(perf_markdown, collapse = "\n") if ("p" %in% names(fixed_params) && "Parameter" %in% names(fixed_params)) { sig_effects <- fixed_params$Parameter[ diff --git a/tests/testthat/test-report_ai.R b/tests/testthat/test-report_ai.R index e6e870d87..f72878e62 100644 --- a/tests/testthat/test-report_ai.R +++ b/tests/testthat/test-report_ai.R @@ -112,8 +112,9 @@ test_that("report_ai.merMod - lmer", { # Subject (grouping variable) should NOT appear in the Variables section expect_false(grepl( "Subject", - strsplit(result, "## Variables\n")[[1]][2] |> - strsplit("## Parameters")[[1]][1] + strsplit(strsplit(result, "## Variables\n")[[1]][2], "## Parameters")[[1]][ + 1 + ] )) }) From ffea685c71e42c8f23034d7ac3b7d6e4e938c676 Mon Sep 17 00:00:00 2001 From: DominiqueMakowski Date: Wed, 29 Apr 2026 12:54:52 +0100 Subject: [PATCH 06/11] fix stuff --- R/report_ai.R | 61 ++++++----- pkgdown/_pkgdown.yml | 183 ++++++++++++++++---------------- tests/testthat/test-report_ai.R | 13 ++- 3 files changed, 132 insertions(+), 125 deletions(-) diff --git a/R/report_ai.R b/R/report_ai.R index 8fb205e81..1e5fd3a93 100644 --- a/R/report_ai.R +++ b/R/report_ai.R @@ -69,10 +69,13 @@ report_ai.glmmTMB <- function(x, ...) { form <- insight::find_formula(x) func_name <- tryCatch( - insight::safe_deparse(insight::get_call(x)[[1]]), + { + dep <- insight::safe_deparse(insight::get_call(x)[[1]]) + sub(".*::", "", dep) + }, error = function(e) class(x)[1] ) - mod_family <- if (!is.null(mi$family)) mi$family else "Unknown" + mod_family <- if (is.null(mi$family)) "Unknown" else mi$family model_vars_list <- insight::find_variables(x) # Use only response + conditional (fixed) variables for descriptives; @@ -92,14 +95,18 @@ report_ai.glmmTMB <- function(x, ...) { dat[, fixed_vars, drop = FALSE], audience = "humans" ))) - desc_lines <- unlist(strsplit(as.character(desc_report), "\n")) + desc_lines <- unlist(strsplit( + as.character(desc_report), + "\n", + fixed = TRUE + )) if (length(desc_lines) > 1) { # Use trimws() to kill the spaces that cause nested bullets clean_lines <- trimws(desc_lines[-1]) - desc_str <- paste0(clean_lines, collapse = "\n") + desc_str <- paste(clean_lines, collapse = "\n") } else { - desc_str <- paste0(trimws(desc_lines), collapse = "\n") + desc_str <- paste(trimws(desc_lines), collapse = "\n") } } else { desc_str <- "- No variables found." @@ -131,7 +138,7 @@ report_ai.glmmTMB <- function(x, ...) { param_table <- insight::format_table(fixed_params) param_markdown <- insight::export_table(param_table, format = "markdown") - param_str <- paste0(param_markdown, collapse = "\n") + param_str <- paste(param_markdown, collapse = "\n") # Format random effect variances as metadata bullet points random_str <- NULL @@ -144,24 +151,24 @@ report_ai.glmmTMB <- function(x, ...) { vapply( seq_len(nrow(random_params)), function(i) { - row <- random_params[i, , drop = FALSE] - param_name <- if ("Parameter" %in% names(row)) { - as.character(row$Parameter) + param_row <- random_params[i, , drop = FALSE] + param_name <- if ("Parameter" %in% names(param_row)) { + as.character(param_row$Parameter) } else { "?" } group_tag <- if ( "Group" %in% - names(row) && - !is.na(row$Group) && - nchar(as.character(row$Group)) > 0 + names(param_row) && + !is.na(param_row$Group) && + nzchar(as.character(param_row$Group)) ) { - paste0(" [", row$Group, "]") + paste0(" [", param_row$Group, "]") } else { "" } - val <- if (!is.na(coef_col) && coef_col %in% names(row)) { - sprintf("%.3f", as.numeric(row[[coef_col]])) + val <- if (!is.na(coef_col) && coef_col %in% names(param_row)) { + sprintf("%.3f", as.numeric(param_row[[coef_col]])) } else { "?" } @@ -176,7 +183,7 @@ report_ai.glmmTMB <- function(x, ...) { perf <- performance::model_performance(x, ...) perf_table <- insight::format_table(perf) perf_markdown <- insight::export_table(perf_table, format = "markdown") - perf_str <- paste0(perf_markdown, collapse = "\n") + perf_str <- paste(perf_markdown, collapse = "\n") if ("p" %in% names(fixed_params) && "Parameter" %in% names(fixed_params)) { sig_effects <- fixed_params$Parameter[ @@ -189,7 +196,7 @@ report_ai.glmmTMB <- function(x, ...) { } else { sprintf( "- Significant effects (p < 0.05): %s", - paste(sig_effects, collapse = ", ") + toString(sig_effects) ) } } else { @@ -209,20 +216,20 @@ report_ai.glmmTMB <- function(x, ...) { ci_pct <- sprintf("%.0f%%", ci_level * 100) ci_label <- .ci_method_label(ci_method) inference_str <- paste0("- Inference: ", ci_pct, " CI [", ci_label, "]") - } else if (!is.null(ci_level)) { + } else if (is.null(ci_level)) { + inference_str <- NULL + } else { inference_str <- paste0( "- Inference: ", sprintf("%.0f%%", ci_level * 100), " CI" ) - } else { - inference_str <- NULL } - param_section <- if (!is.null(random_str)) { - paste0("## Parameters\n", param_str, "\n\n### Random Effects\n", random_str) - } else { + param_section <- if (is.null(random_str)) { paste0("## Parameters\n", param_str) + } else { + paste0("## Parameters\n", param_str, "\n\n### Random Effects\n", random_str) } model_section <- paste0( @@ -238,7 +245,7 @@ report_ai.glmmTMB <- function(x, ...) { "\n", "- N: ", n_obs, - if (!is.null(inference_str)) paste0("\n", inference_str) else "" + if (is.null(inference_str)) "" else paste0("\n", inference_str) ) res <- paste0( @@ -257,12 +264,12 @@ report_ai.glmmTMB <- function(x, ...) { ) class(res) <- c("report_ai", "character") - return(res) + res } # Helper: human-readable CI / df-method label .ci_method_label <- function(method) { - labels <- c( + method_labels <- c( wald = "Wald", residual = "Residual df (t/F)", satterthwaite = "Satterthwaite df", @@ -275,7 +282,7 @@ report_ai.glmmTMB <- function(x, ...) { eti = "ETI", si = "SI" ) - lab <- labels[tolower(as.character(method))] + lab <- method_labels[tolower(as.character(method))] if (is.na(lab)) { tools::toTitleCase(tolower(as.character(method))) } else { diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 76ad46fa9..f7a856df4 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -1,103 +1,104 @@ url: https://easystats.github.io/report/ template: - bootstrap: 5 - package: easystatstemplate + bootstrap: 5 + package: easystatstemplate navbar: - type: default - left: - - text: Tutorials - icon: fa fa-book-reader - href: articles/index.html - aria-label: Articles - menu: - - text: Report and Cite Packages - href: articles/cite_packages.html - - text: Supporting New Models - href: articles/new_models.html - - text: 'Automated Reporting: Getting Started' - href: articles/report.html - - text: Publication-ready Tables - href: articles/report_table.html - - text: Functions - icon: fa fa-file-code - href: reference/index.html - aria-label: Reference - - text: News - icon: fa fa-newspaper - href: news/index.html - aria-label: News - - text: Help - icon: fa fa-question-circle - href: SUPPORT.html - aria-label: Support + type: default + left: + - text: Tutorials + icon: fa fa-book-reader + href: articles/index.html + aria-label: Articles + menu: + - text: Report and Cite Packages + href: articles/cite_packages.html + - text: Supporting New Models + href: articles/new_models.html + - text: "Automated Reporting: Getting Started" + href: articles/report.html + - text: Publication-ready Tables + href: articles/report_table.html + - text: Functions + icon: fa fa-file-code + href: reference/index.html + aria-label: Reference + - text: News + icon: fa fa-newspaper + href: news/index.html + aria-label: News + - text: Help + icon: fa fa-question-circle + href: SUPPORT.html + aria-label: Support articles: -- title: Tutorials - navbar: ~ - contents: - - cite_packages - - new_models - - report - - report_table + - title: Tutorials + navbar: ~ + contents: + - cite_packages + - new_models + - report + - report_table reference: - - title: Report Statistical Information - desc: | - Main functions for reporting statistical information - contents: - - report_effectsize - - report_info - - report_intercept - - report_model - - report_parameters - - report_participants - - report_performance - - report_priors - - report_random - - report_s - - report_sample - - report_statistics + - title: Report Statistical Information + desc: | + Main functions for reporting statistical information + contents: + - report_effectsize + - report_info + - report_intercept + - report_model + - report_parameters + - report_participants + - report_performance + - report_priors + - report_random + - report_s + - report_sample + - report_statistics - - title: Formatting - desc: | - Functions for formatting content - contents: - - cite_easystats - - format_citation - - format_formula - - format_model - - as.report_text - - report - - report.default - - report_table - - report_text + - title: Formatting + desc: | + Functions for formatting content + contents: + - cite_easystats + - format_citation + - format_formula + - format_model + - as.report_text + - report + - report_ai + - report.default + - report_table + - report_text - - title: Report Statistical Objects - desc: | - Helper functions for reporting of statistical objects - contents: - - report.aov - - report.bayesfactor_models - - report.brmsfit - - report.compare_performance - - report.htest - - report.lavaan - - report.lm - - report.stanreg - - report.test_performance - - report.estimate_contrasts - - report.compare.loo - - report.BFBayesFactor + - title: Report Statistical Objects + desc: | + Helper functions for reporting of statistical objects + contents: + - report.aov + - report.bayesfactor_models + - report.brmsfit + - report.compare_performance + - report.htest + - report.lavaan + - report.lm + - report.stanreg + - report.test_performance + - report.estimate_contrasts + - report.compare.loo + - report.BFBayesFactor - - title: Report Non-Statistical Objects - desc: | - Helper functions for reporting of non-statistical objects - contents: - - report.numeric - - report.character - - report.factor - - report.data.frame - - report_date - - report.sessionInfo + - title: Report Non-Statistical Objects + desc: | + Helper functions for reporting of non-statistical objects + contents: + - report.numeric + - report.character + - report.factor + - report.data.frame + - report_date + - report.sessionInfo diff --git a/tests/testthat/test-report_ai.R b/tests/testthat/test-report_ai.R index f72878e62..2eee19758 100644 --- a/tests/testthat/test-report_ai.R +++ b/tests/testthat/test-report_ai.R @@ -35,11 +35,9 @@ test_that("report_ai.lm - print method", { test_that("report_ai.default - warns and falls back to report()", { # htest has report() support but no dedicated report_ai() method ht <- t.test(mtcars$mpg ~ mtcars$am) - expect_warning(result <- report_ai(ht), "not yet available") + result <- expect_warning(report_ai(ht), "not yet available") expect_s3_class(result, "report") -}) - -test_that("report_ai.default - falls back to human report() when report_audience option is ai", { +}) report() when report_audience option is ai", { # Regression test: fallback from report_ai.default() must not recurse back # into AI routing when the global audience option is set to "ai". ht <- t.test(mtcars$mpg ~ mtcars$am) @@ -47,7 +45,7 @@ test_that("report_ai.default - falls back to human report() when report_audience on.exit(options(report_audience = old)) options(report_audience = "ai") - expect_warning(result <- report_ai(ht), "not yet available") + result <- expect_warning(report_ai(ht), "not yet available") expect_s3_class(result, "report") expect_false(inherits(result, "report_ai")) }) @@ -112,9 +110,10 @@ test_that("report_ai.merMod - lmer", { # Subject (grouping variable) should NOT appear in the Variables section expect_false(grepl( "Subject", - strsplit(strsplit(result, "## Variables\n")[[1]][2], "## Parameters")[[1]][ + strsplit(strsplit(result, "## Variables\n", fixed = TRUE)[[1]][2], "## Parameters", fixed = TRUE)[[1]][ 1 - ] + ], + fixed = TRUE )) }) From d92231d0fbd91f5322e5ed37735f976db93c16b6 Mon Sep 17 00:00:00 2001 From: DominiqueMakowski Date: Wed, 29 Apr 2026 13:12:12 +0100 Subject: [PATCH 07/11] Update test-report_ai.R --- tests/testthat/test-report_ai.R | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-report_ai.R b/tests/testthat/test-report_ai.R index 2eee19758..66f6c850b 100644 --- a/tests/testthat/test-report_ai.R +++ b/tests/testthat/test-report_ai.R @@ -37,7 +37,9 @@ test_that("report_ai.default - warns and falls back to report()", { ht <- t.test(mtcars$mpg ~ mtcars$am) result <- expect_warning(report_ai(ht), "not yet available") expect_s3_class(result, "report") -}) report() when report_audience option is ai", { +}) + +test_that("report_ai.default - falls back to human report() when report_audience option is ai", { # Regression test: fallback from report_ai.default() must not recurse back # into AI routing when the global audience option is set to "ai". ht <- t.test(mtcars$mpg ~ mtcars$am) @@ -110,7 +112,11 @@ test_that("report_ai.merMod - lmer", { # Subject (grouping variable) should NOT appear in the Variables section expect_false(grepl( "Subject", - strsplit(strsplit(result, "## Variables\n", fixed = TRUE)[[1]][2], "## Parameters", fixed = TRUE)[[1]][ + strsplit( + strsplit(result, "## Variables\n", fixed = TRUE)[[1]][2], + "## Parameters", + fixed = TRUE + )[[1]][ 1 ], fixed = TRUE From 04d48fbce9aa151e53e074f252967740175e3897 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 29 Apr 2026 14:15:33 +0200 Subject: [PATCH 08/11] Bump version from 0.6.3.1 to 0.6.3.2 --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 9bbf9215a..9094c1e81 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: report Type: Package Title: Automated Reporting of Results and Statistical Models -Version: 0.6.3.1 +Version: 0.6.3.2 Authors@R: c(person(given = "Dominique", family = "Makowski", From 2cc4bf2d88cd7714cc7e6be8dff3616717b6b1ff Mon Sep 17 00:00:00 2001 From: DominiqueMakowski Date: Wed, 29 Apr 2026 15:33:53 +0100 Subject: [PATCH 09/11] Update _pkgdown.yml --- pkgdown/_pkgdown.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index f7a856df4..b607047c3 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -20,6 +20,8 @@ navbar: href: articles/report.html - text: Publication-ready Tables href: articles/report_table.html + - text: AI-Optimized Reports + href: articles/report_ai.html - text: Functions icon: fa fa-file-code href: reference/index.html @@ -41,6 +43,7 @@ articles: - new_models - report - report_table + - report_ai reference: - title: Report Statistical Information From cb08ffe3b7822a1f8a97b905730f6bcc5e0bbfd4 Mon Sep 17 00:00:00 2001 From: DominiqueMakowski Date: Wed, 29 Apr 2026 16:45:33 +0100 Subject: [PATCH 10/11] don't export --- R/report.R | 13 +++++++++---- R/report_ai.R | 34 +--------------------------------- pkgdown/_pkgdown.yml | 1 - 3 files changed, 10 insertions(+), 38 deletions(-) diff --git a/R/report.R b/R/report.R index 1c63d36cd..d641e99fd 100644 --- a/R/report.R +++ b/R/report.R @@ -21,10 +21,15 @@ #' @param x The R object that you want to report (see list of of supported #' objects above). #' @param audience The intended audience for the report. `"humans"` (default) -#' produces the standard formatted text report. `"ai"` produces a compact, -#' structured output optimised for consumption by a Large Language Model (LLM) -#' or AI agent via [report_ai()]. The default can be changed globally with -#' `options(report_audience = "ai")`. +#' produces the standard narrative text report. `"ai"` produces a compact, +#' structured Markdown output designed for consumption by a Large Language +#' Model (LLM) or AI agent. It strikes a careful balance between +#' comprehensiveness, specificity, and compactness, giving the model the +#' clearest and most relevant analytical information at the lowest possible +#' token cost. The output is a single character vector of class `report_ai` +#' that can be pasted directly into a chat window or fed to an LLM API. +#' The default can be changed globally with `options(report_audience = "ai")`. +#' See `vignette("report_ai", package = "report")` for details and examples. #' @param ... Arguments passed to or from other methods. #' #' @details diff --git a/R/report_ai.R b/R/report_ai.R index 1e5fd3a93..e7a123892 100644 --- a/R/report_ai.R +++ b/R/report_ai.R @@ -1,24 +1,8 @@ -#' Generate AI-optimized reports -#' -#' This function is designed to produce AI-optimized output for statistical models. -#' It strikes a careful balance between comprehensiveness, specificity, and compactness. -#' The primary goal is to provide a Large Language Model (LLM) or AI agent with the -#' clearest and most relevant analytical information at the lowest possible token cost. -#' -#' @param x A statistical model. -#' @param ... Arguments passed to other functions, like \code{parameters::model_parameters()}, -#' \code{performance::model_performance()} or \code{insight::format_table()}. -#' @return A character vector of class `report_ai` containing the formatted text. -#' -#' @examples -#' m <- lm(mpg ~ wt + hp, data = mtcars) -#' report_ai(m) -#' @export +# Internal generic — use report(x, audience = "ai") instead. report_ai <- function(x, ...) { UseMethod("report_ai") } -#' @export report_ai.default <- function(x, ...) { insight::format_warning( paste0( @@ -30,32 +14,16 @@ report_ai.default <- function(x, ...) { report(x, ..., audience = "humans") } -#' @export report_ai.lm <- function(x, ...) { .report_ai_models(x, ...) } -#' @export report_ai.glm <- report_ai.lm -#' @rdname report_ai -#' @examplesIf requireNamespace("lme4", quietly = TRUE) -#' \donttest{ -#' m <- lme4::lmer(Reaction ~ Days + (1 | Subject), data = lme4::sleepstudy) -#' report_ai(m) -#' } -#' @export report_ai.merMod <- function(x, ...) { .report_ai_models(x, ...) } -#' @rdname report_ai -#' @examplesIf requireNamespace("glmmTMB", quietly = TRUE) -#' \donttest{ -#' m <- glmmTMB::glmmTMB(count ~ mined + (1 | site), family = poisson(), data = glmmTMB::Salamanders) -#' report_ai(m) -#' } -#' @export report_ai.glmmTMB <- function(x, ...) { .report_ai_models(x, ...) } diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index b607047c3..66b56df42 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -73,7 +73,6 @@ reference: - format_model - as.report_text - report - - report_ai - report.default - report_table - report_text From d7dc9ae2c3e888685d693cd77f5d1b1591459bf5 Mon Sep 17 00:00:00 2001 From: DominiqueMakowski Date: Mon, 18 May 2026 13:53:15 +0100 Subject: [PATCH 11/11] fixes --- pkgdown/_pkgdown.yml | 6 ++++++ tests/testthat/test-report_ai.R | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 66b56df42..a5094afa9 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -77,6 +77,12 @@ reference: - report_table - report_text + - title: AI-Optimized Reports + desc: | + Functions for generating AI-optimized reports + contents: + - report_ai + - title: Report Statistical Objects desc: | Helper functions for reporting of statistical objects diff --git a/tests/testthat/test-report_ai.R b/tests/testthat/test-report_ai.R index 66f6c850b..20c793161 100644 --- a/tests/testthat/test-report_ai.R +++ b/tests/testthat/test-report_ai.R @@ -35,7 +35,8 @@ test_that("report_ai.lm - print method", { test_that("report_ai.default - warns and falls back to report()", { # htest has report() support but no dedicated report_ai() method ht <- t.test(mtcars$mpg ~ mtcars$am) - result <- expect_warning(report_ai(ht), "not yet available") + expect_warning(report_ai(ht), "not yet available") + result <- suppressWarnings(report_ai(ht)) expect_s3_class(result, "report") }) @@ -47,7 +48,8 @@ test_that("report_ai.default - falls back to human report() when report_audience on.exit(options(report_audience = old)) options(report_audience = "ai") - result <- expect_warning(report_ai(ht), "not yet available") + expect_warning(report_ai(ht), "not yet available") + result <- suppressWarnings(report_ai(ht)) expect_s3_class(result, "report") expect_false(inherits(result, "report_ai")) })