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

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = FALSE, comment = "")
# Console colour carries no meaning on a rendered page. pkgdown turns it on for
# its own build, and the escape sequences then reach the reader as literal text,
# so colour is switched off here for a plain vignette render and a site build
# alike. The fixed width keeps tibbles inside the documentation column.
options(cli.num_colors = 1, cli.hyperlink = FALSE, crayon.enabled = FALSE,
        width = 80)
# Print data frames and tibbles as formatted tables.
local({
  kp <- function(x, ...) {
    if (any(vapply(x, is.list, logical(1)))) return(knitr::normal_print(x))
    knitr::knit_print(knitr::kable(x))
  }
  for (cls in c("data.frame", "tbl_df", "tbl")) {
    registerS3method("knit_print", cls, kp, envir = asNamespace("knitr"))
  }
})
```

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

This vignette is fully reproducible without a Scopus API key. Elsevier's API
terms do not permit redistributing retrieved records, so no package can ship a
genuine Scopus harvest, and scopusflow bundles an openly licensed stand-in
instead. `example_records` holds 138 real journal articles on graphene
supercapacitors published between 2015 and 2024, with their real titles, DOIs,
journals, first authors and citation counts. They come from OpenAlex, whose
metadata is released under CC0, reshaped into the schema a retrieval returns.
The harvest is complete, so its rows per year are the real number of
publications per year for that query, and its gaps are real gaps too.
Eleven records carry no DOI and two no source title, exactly as they arrive.
Running the equivalent query against Scopus yields the same kind of object, with
the same columns and the same handling, though not an identical set of records.
The steps that need the API are shown but not run, each paired with the offline
equivalent.

## Describing a search as a plan

A plan separates describing a search from executing it. Plans are inspectable,
saveable and version-controllable, and they can be partitioned, for example by
year, so that a large retrieval stays under the API's `start < 5000` ceiling and
can be cached and resumed.

The plan below describes the search the bundled records came from, so the rest
of the article follows one worked example from description to export.

```{r}
plan <- scopus_plan(
  "graphene supercapacitor",
  years     = 2015:2024,
  field     = "TITLE-ABS-KEY",
  partition = "year"
)
plan
```

Each row is one query cell. Field tags wrap the query and years become a date
filter.

```{r}
scopus_plan("language learning", field = "TITLE")$query
scopus_plan("x", years = 2015:2020)$date

# A plan is a classed object, and is_scopus_plan() confirms it.
is_scopus_plan(plan)
```

## Sizing and fetching

`scopus_has_key()` reports whether a key is configured, without revealing it. It
is the guard the package's own examples use to skip the steps that need the API,
so it is the natural switch for a reproducible script.

```{r}
scopus_has_key()
```

With a key configured, you size a search cheaply and then execute the plan,
optionally caching each cell so that an interrupted run resumes without
re-spending quota. These contact the API, so they are not evaluated here.

```{r eval = FALSE}
scopus_count(
  "graphene supercapacitor",
  years = 2015:2024,
  field = "TITLE-ABS-KEY"
)

records <- scopus_fetch_plan(
  plan,
  cache_dir = scopus_cache_dir(),
  resume = TRUE
)
```

Without a key, the bundled corpus stands in for the result of that harvest, and
the sections below run on it.

## The record schema

Whether records come from the API or from the bundled corpus, they share one
stable schema, so everything below would read the same on a harvest of your
own. `summary()` takes stock of a set, and the first rows show the columns.

```{r}
records <- example_records
summary(records)

head(records)

# A record set is a classed tibble, and is_scopus_records() confirms the
# contract.
is_scopus_records(records)
```

`scopus_records()` produces this same shape from a raw API response, flattening
the nested result into one row per record. The entry below carries the fields of
a real article, one of those in the bundled corpus, in the form the API returns
them.

```{r}
raw <- list(entry = list(
  list(`prism:doi` = "10.1021/am509065d",
       `dc:title` =
         "Flexible and Stackable Laser-Induced Graphene Supercapacitors",
       `dc:creator` = "Zhiwei Peng",
       `prism:publicationName` = "ACS Applied Materials & Interfaces",
       `prism:coverDate` = "2015-01-13", `citedby-count` = "469")
))
scopus_records(raw, query = "TITLE-ABS-KEY(graphene supercapacitor)")
```

## Most frequent sources and authors

A record set already answers the first descriptive questions. `scopus_top()`
tallies the most frequent sources or authors, counting each contributor once
per record. Across these 138 articles the tally is long-tailed, as a real
literature is. They are spread over 90 distinct journals, and only one, *ACS
Applied Materials & Interfaces*, appears more than five times.

```{r}
scopus_top(records, by = "source")
```

`vignette("analysing-a-literature")` covers growth trends, top-source and
top-author plots and abstract retrieval in depth.

## DOIs and change tracking

Extract a clean, deduplicated DOI list for import into a reference manager, and
compare two retrievals to see exactly what changed. Eleven of the 138 records
arrived without a DOI, so 127 come back.

```{r}
dois <- scopus_extract_dois(records)
length(dois)
head(dois, 4)
```

A search re-run later gains records and occasionally loses one to re-indexing.
Here the baseline stops at 2023 and the second pull adds the 2024 articles while
dropping the first record.

```{r}
baseline <- records[records$year <= 2023, ]
later <- records[-1, ]
print(scopus_diff_dois(old = baseline, new = later))
```

You can write the DOIs to a path you specify, and read the file back to see
exactly what lands on disk.

```{r}
out <- file.path(tempdir(), "dois.csv")
scopus_extract_dois(records, file = out)
writeLines(head(readLines(out), 5))
```

## Comparing topic trends

`scopus_compare_topics()` measures how the internal emphasis of a literature
shifts, expressed as each comparison topic's yearly share of the reference
literature. It issues one count request per term per year, so it needs the API.

```{r eval = FALSE}
cmp <- scopus_compare_topics(
  reference_query  = "language learning",
  comparison_terms = c("effect size", "Bayesian"),
  years            = 2015:2020,
  field            = "TITLE-ABS-KEY"
)
plot_scopus_comparison(cmp)
```

The result is a tidy table with one row per topic and year, which
`plot_scopus_comparison()` draws with direct line labels, a colour-blind-safe
palette and shaded stability bands. `vignette("comparing-topics")` builds the
object offline, shows the plot in its variations and explains how to read the
bands.

## Author keywords and references

A search only returns the fields the Search API carries. Author keywords and a
document's own reference list need `view = "COMPLETE"` and Abstract Retrieval
respectively, both at a materially different quota cost from an ordinary
search. `vignette("keywords-and-references")` walks through both, and
`scopus_corpus()`, which combines them into a minimal
`id`/`title`/`year`/`keywords`/`references` shape for downstream tools.

## Export and interoperability

Hand results to `bibliometrix`-style workflows, or save and reload them.

```{r}
m <- as_bibliometrix(records)
head(m[, c("AU", "TI", "PY", "SO", "TC")])

path <- file.path(tempdir(), "records.rds")
write_scopus_records(records, path)
identical(read_scopus_records(path), records)
```

## Writing the search up

A search that is going into a paper has to be reported. `scopus_search_report()`
assembles that record from the plan and the harvest, to the PRISMA-S standard,
and formats it as a methods paragraph. It states only what the objects hold, so
the record below is candid about the bundled corpus carrying no retrieval time
of its own. The *Search plans and quota-aware retrieval* article works through
it in full.

```{r}
report <- scopus_search_report(records, plan = plan)
cat(format(report, style = "paragraph"))
```

## Handling failures

Network and API problems surface as typed conditions, all inheriting from
`scopus_error`, so a workflow can respond to them in code.

```{r eval = FALSE}
tryCatch(
  scopus_fetch("..."),
  scopus_error_no_key     = function(e) message("No API key configured."),
  scopus_error_rate_limit = function(e)
    message("Rate limited, so backing off."),
  scopus_error            = function(e)
    message("Scopus error: ", conditionMessage(e))
)
```
