---
title: "Getting started with biobouncer"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting started with biobouncer}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

biobouncer validates biological identifiers and inputs. It answers one question,
"is this a valid identifier?", with the same verdict in R and Python. This
vignette walks through the sources, the checking modes, and the validation
adapters using only offline data, so every chunk here runs without a network.

```{r setup}
library(biobouncer)
```

## What can be checked

`sources()` lists the source databases biobouncer knows about, and `source_info()`
returns a table with a short description of each.

```{r sources}
sources()
```

## Pattern mode: is the string well-formed?

`pattern` mode is offline and deterministic. It checks the shape of an
identifier against the source's pattern. It does not check that the identifier
exists.

`check_id()` returns a tibble with one row per input. Note that it preserves the
order and length of the input, and never collapses an invalid value into a bare
`FALSE`: the row tells you why.

```{r pattern}
check_id(
  c("MONDO:0005148", "mondo:5148", "GO:0006915"),
  source_db = "mondo"
)
```

The `normalized` column holds the canonical form of a valid input. The
`suggestion` column holds a best-effort correction for an invalid but mappable
input, such as the lowercase prefix above.

For just the verdict, use `is_valid_id()`, which returns a logical vector.

```{r is-valid}
is_valid_id(c("P04637", "p04637"), source_db = "uniprot")
```

## Cache mode: does the identifier exist in a snapshot?

`cache` mode checks existence against a pinned, offline snapshot. A small
`sample` snapshot ships with the package, so the examples below need no
downloads. A real analysis pins a dated snapshot instead.

```{r cache}
check_id(
  c("MONDO:0005148", "MONDO:9999999"),
  source_db = "mondo",
  how = "cache",
  version = "sample"
)
```

`MONDO:9999999` is well-formed, so it passes `pattern` mode, but it is not in the
snapshot, so it fails `cache` mode. Choosing the mode is choosing how strict you
want the check to be.

Snapshots are managed with a few helpers:

```{r snapshots}
biobouncer_snapshots()
```

## Remote mode

`remote` mode checks existence live against a source API. It needs a network, so
it is not run in this vignette, but the call looks like this:

```{r remote, eval = FALSE}
# Live check against the Ensembl REST API.
check_id("ENSG00000139618", source_db = "ensembl", how = "remote")

# existence mode uses a snapshot when one is available and otherwise
# falls back to remote.
check_id("MONDO:0005148", source_db = "mondo", how = "existence")
```

A network failure in `remote` mode raises an error. It never returns a silent
`FALSE`, so a failed lookup cannot be mistaken for an absent identifier.

## Species and version awareness

Some sources are species-aware. For Ensembl, the species is encoded in the id,
so `pattern` mode can reject a well-formed id that belongs to the wrong species.

```{r species}
# ENSMUSG is a mouse gene id.
is_valid_id("ENSMUSG00000059552", source_db = "ensembl", species = "mus_musculus")
is_valid_id("ENSMUSG00000059552", source_db = "ensembl", species = "homo_sapiens")
```

`species` accepts a name such as `"homo_sapiens"` or an NCBI taxon id such as
`9606`. A source that is not species-aware ignores the argument.

## HGVS variant syntax

The `hgvs` source checks the syntax of an HGVS sequence variant name. This is a
grammar check in `pattern` mode. It confirms the shape of the variant. It does
not check coordinates or that the variant exists.

```{r hgvs}
is_valid_id(
  c(
    "NM_004006.2:c.4375C>T",
    "NP_003997.1:p.(Gly56Ala)",
    "NM_004006.2:c.76insG"
  ),
  source_db = "hgvs"
)
```

The last one is invalid: an insertion must sit between two flanking positions,
so it needs a range such as `c.76_77insG`.

## Clean a column

The most common job is not "is this one id valid" but "here is a column, which
values are wrong, and can you fix the ones you can". `report_id()` runs a check
over a whole column and returns a table that prints with a one-line summary of
how many values are valid, repairable, invalid, or missing.

```{r report}
genes <- c("TP53", "MLL", "notagene", NA)
report_id(genes, "hgnc", how = "cache")
```

`MLL` is a withdrawn gene symbol, so it is invalid but repairable: its successor
is `KMT2A`. `repair_id()` substitutes the fixable values and leaves valid,
unmappable, and missing values untouched, so it keeps the column's length and
order and drops into `dplyr::mutate()`:

```{r repair}
repair_id(genes, "hgnc", how = "cache")
```

Calling `summary()` on a report returns the counts as a one-row table.

## Validation framework adapters

The adapters wrap the core classifier so it plugs into common validation
frameworks. They never reimplement any checks. The checkmate-style adapters work
out of the box:

```{r checkmate}
# TRUE when all are valid, otherwise a message.
check_valid_id(c("MONDO:0005148", "mondo:5148"), "mondo")

# A single logical.
test_valid_id("MONDO:0005148", "mondo")
```

`id_predicate()` returns an elementwise predicate for data-frame validation with
assertr or validate:

```{r predicate}
is_mondo <- id_predicate("mondo")
ids <- c("MONDO:0005148", "mondo:5148", "MONDO:0018076")
ids[is_mondo(ids)]
```

For Shiny apps, `sv_biobouncer()` returns a shinyvalidate rule. The rule returns
`NULL` for a valid input and a message otherwise:

```{r shiny}
rule <- sv_biobouncer("mondo")
rule("MONDO:0005148")
rule("mondo:5148")
```

## Generating test data

`synthesize_ids()` builds a labeled "messy column" for any source, so you can
exercise a validation pipeline without hand-writing test ids. Each row carries
the input, its category (valid, repairable, invalid, or missing), and the
verdict fields the checker returned for it.

```{r synthesize}
rows <- synthesize_ids("mondo")
rows[, c("input", "category", "suggestion")]

# Feed the column straight into a report.
report_id(rows$input, "mondo")
```

The column is deterministic and offline, and the Python `synthesize()` produces
the same one. `ec`, `hgvs`, and `hgnc` have no repairable form, so they omit that
category. Pass `how = "cache"` for a snapshot-mode column, where a repairable
value can be a retired id that maps to its successor.

## Summary

- `pattern` mode checks shape, `cache` and `remote` check existence, and
  `existence` uses a snapshot first and remote as a fallback.
- `report_id()` and `repair_id()` validate and clean a whole column in one call.
- Results are vectorized and preserve input order and length. Errors are
  explicit.
- The same inputs give the same verdicts in the Python package, which is
  enforced by a shared conformance corpus.
