---
title: "bioLeak audit report"
output:
  html_document:
    toc: true
    toc_depth: 2
params:
  audit: NULL
---

```{r report-setup, include=FALSE}
stopifnot(inherits(params$audit, "LeakAudit"))
aud <- params$audit
learner_name <- if (!is.null(aud@trail$learner)) aud@trail$learner else NULL
knitr::opts_chunk$set(echo = FALSE, message = FALSE, warning = FALSE)
```

## Overview

```{r overview}
summary(aud)
```

## Model performance summary

```{r perf-summary}
ms <- aud@fit@metric_summary
if (!is.null(ms) && nrow(ms) > 0) {
  knitr::kable(ms)
} else {
  cat("No metric summary available.")
}
```

```{r perf-by-fold}
mf <- aud@fit@metrics
if (!is.null(mf) && nrow(mf) > 0) {
  knitr::kable(utils::head(mf, 50))
} else {
  cat("No per-fold metrics available.")
}
```

## Label-permutation association test

Note: By default, this test refits models when refit data are available (auto mode);
otherwise it keeps predictions fixed and permutes labels, so the p-value quantifies
prediction-label association rather than a full refit null. Set `perm_refit = FALSE`
to force fixed-prediction shuffles. This test does NOT diagnose
information leakage. Use the Batch Association, Target Leakage Scan, and Duplicate Detection
sections to check for leakage.

```{r perm-significance}
pg <- aud@permutation_gap
if (!is.null(pg) && nrow(pg) > 0) {
  perm_method <- if (!is.null(aud@info$perm_method)) aud@info$perm_method else "fixed"
  perm_mode <- if (!is.null(aud@info$perm_refit_mode)) aud@info$perm_refit_mode else perm_method
  perm_label <- if (identical(perm_method, "refit")) "refit per permutation" else "fixed predictions"
  if (grepl("^auto", perm_mode)) perm_label <- paste0(perm_label, " (auto)")
  cat(sprintf("Permutation method: %s\n", perm_label))
  if (!is.null(aud@info$perm_refit_reason) && nzchar(aud@info$perm_refit_reason)) {
    cat(sprintf("Auto mode: %s\n", aud@info$perm_refit_reason))
  }
  cat("\n")
  knitr::kable(pg)
} else {
  cat("No permutation results available.")
}
```

```{r perm-plot}
if (length(aud@perm_values) > 0 && any(is.finite(aud@perm_values))) {
  plot_perm_distribution(aud)
} else {
  cat("No permutation distribution available.")
}
```

## Batch association

```{r batch-assoc}
ba <- aud@batch_assoc
if (!is.null(ba) && nrow(ba) > 0) {
  knitr::kable(ba)
} else {
  cat("No batch or study association results available.")
}
```

## Confounder sensitivity

```{r confounder-sensitivity}
metric_name <- if (!is.null(aud@trail$metric)) aud@trail$metric else NULL
cs <- try(confounder_sensitivity(aud@fit, metric = metric_name, learner = learner_name), silent = TRUE)
if (inherits(cs, "try-error") || is.null(cs) || nrow(cs) == 0) {
  cat("No confounder sensitivity results available.")
} else {
  knitr::kable(utils::head(cs, 50))
}
```

```{r confounder-plot}
cs_plot <- try(plot_confounder_sensitivity(aud@fit, metric = metric_name, learner = learner_name),
               silent = TRUE)
if (inherits(cs_plot, "try-error")) {
  cat("Confounder sensitivity plot not available.")
}
```

## Calibration checks

```{r calibration-table}
if (identical(aud@fit@task, "binomial")) {
  cal <- try(calibration_summary(aud@fit, bins = 10, learner = learner_name), silent = TRUE)
  if (inherits(cal, "try-error")) {
    cat("Calibration summary not available.")
  } else {
    knitr::kable(cal$metrics)
  }
} else {
  cat("Calibration checks are available for binomial tasks only.")
}
```

```{r calibration-plot}
if (identical(aud@fit@task, "binomial")) {
  cal_plot <- try(plot_calibration(aud@fit, bins = 10, learner = learner_name), silent = TRUE)
  if (inherits(cal_plot, "try-error")) {
    cat("Calibration plot not available.")
  }
}
```

## Target leakage scan

Note: The target leakage scan includes a multivariate/interaction check by default when
`X_ref` is available; disable it with `target_scan_multivariate = FALSE`. Univariate
scans can still miss proxies or features not included in `X_ref`. A multivariate scan
summary (if enabled) is reported below.

```{r target-leakage}
ta <- aud@target_assoc
if (!is.null(ta) && nrow(ta) > 0) {
  threshold <- if (!is.null(aud@info$target_threshold)) aud@info$target_threshold else 0.9
  flagged <- ta[!is.na(ta$score) & ta$score >= threshold, , drop = FALSE]
  show <- if (nrow(flagged) > 0) flagged else ta
  show <- show[order(show$score, decreasing = TRUE, na.last = TRUE), , drop = FALSE]
  knitr::kable(utils::head(show, 50))
} else {
  cat("No target leakage scan results available.")
}
```

## Multivariate target scan

```{r target-leakage-mv}
mv <- aud@info$target_multivariate
if (!is.null(mv) && nrow(mv) > 0) {
  knitr::kable(mv)
} else if (isTRUE(aud@info$target_scan_multivariate)) {
  cat("Multivariate target scan requested but no results were produced.")
} else {
  cat("Multivariate target scan not requested.")
}
```

## Duplicate detection

```{r duplicates}
dd <- aud@duplicates
if (!is.null(dd) && nrow(dd) > 0) {
  scope <- if (!is.null(aud@info$duplicate_scope)) aud@info$duplicate_scope else "all"
  scope_label <- if (identical(scope, "train_test")) "train/test only" else "all pairs"
  cat(sprintf("Scope: %s\n\n", scope_label))
  knitr::kable(utils::head(dd, 50))
} else {
  cat("No duplicates detected or no duplicate check performed.")
}
```

## Provenance

```{r trail}
trail <- aud@trail
if (!is.null(trail)) {
  knitr::kable(data.frame(
    field = names(trail),
    value = vapply(trail, function(x) {
      if (length(x) == 1L) as.character(x) else "<list>"
    }, character(1)),
    stringsAsFactors = FALSE
  ))
}
```
