---
title: "Pipeline gates"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Pipeline gates}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(ksCompare)
```

A `ks_comparison` makes a natural CI gate: fail loudly on unexpected
diffs, succeed silently otherwise.

## ks_assert_clean

```{r, error = TRUE}
a <- data.frame(id = 1:3, x = c(1, 2, 3))
b <- data.frame(id = 1:3, x = c(1, 2, 4))

ks_compare(a, b, by = "id") |>
  ks_assert_clean()
```

The error is classed (`ksCompare_assertion_failed`) so test suites can
catch it precisely:

```{r, eval = FALSE}
testthat::expect_error(
  ks_compare(a, b, by = "id") |> ks_assert_clean(),
  class = "ksCompare_assertion_failed"
)
```

## Tolerating known diffs

```{r}
ks_compare(a, b, by = "id") |>
  ks_assert_clean(max_value_diffs = 1L) |>
  invisible()
```

Other knobs: `max_schema_diffs`, `max_unmatched_rows`. The function
returns the comparison invisibly on success, so it composes cleanly
inside `|>` chains.

- `max_value_diffs` -- budget for differing cells in matched rows.
- `max_schema_diffs` -- budget counted as base-only columns +
  comp-only columns + matched columns whose `kind` differs.
- `max_unmatched_rows` -- budget for `n_base_only_rows + n_comp_only_rows`.

## CI integration

In a CI script you usually want a summary written before the gate
fails so the failure is debuggable:

```{r, eval = FALSE}
cmp <- ks_compare(target, qc_baseline, by = "USUBJID")
ks_report_html(cmp, "qc.html")    # always written

tryCatch(
  ks_assert_clean(cmp),
  ksCompare_assertion_failed = function(e) {
    message(conditionMessage(e))
    quit(status = 1)
  }
)
```

## With pointblank

```{r, eval = FALSE}
library(pointblank)

cmp <- ks_compare(target, qc_baseline, by = "USUBJID")

agent <- create_agent(tbl = mtcars) |>
  ks_pointblank_step(cmp, max_value_diffs = 0L) |>
  interrogate()
```

`ks_pointblank_step()` wraps `pointblank::specially()`, so the gate
participates in the agent's standard pass / warn / fail action levels.

## With arsenal

If your codebase already uses `arsenal::comparedf()`, the
`as_ks_comparison()` method re-runs the comparison via `ks_compare()`
on the same source frames and BY keys:

```{r, eval = FALSE}
arsenal::comparedf(adsl_a, adsl_b, by = "USUBJID") |>
  as_ks_comparison() |>
  ks_assert_clean()
```
