citation("risAT")
## To cite package 'risAT' in publications use:
##
## Schmidt R (2026). _risAT: An R package wrapping the API of the
## Austrian Legal Information System (RIS)_. R package version 0.1.0,
## <https://github.com/werkstattcodes/risAT>.
##
## A BibTeX entry for LaTeX users is
##
## @Manual{,
## title = {risAT: An R package wrapping the API of the Austrian Legal Information System (RIS)},
## author = {Roland Schmidt},
## year = {2026},
## url = {https://github.com/werkstattcodes/risAT},
## note = {R package version 0.1.0},
## }Introducing {risAT}: An R package to access with Austria’s Legal Information System (RIS)
{risAT} is an R package that wraps the Austrian RIS (Rechtsinformationssystem) Open Government Data API, making it easier to work with Austrian jurisprudence in R.
Just the results, please
1 Context and purpose
The Austrian Legal Information System, RIS, short for Rechtsinformationssystem, is the central public repository of Austrian law and jurisprudence. Broadly speaking, it provides access to Austria’s current and historical legal norms, including federal and state law, as well as to the jurisprudence of the country’s most important courts and judicial institutions. The latter covers the case law of the Constitutional Court (Verfassungsgerichtshof, VfGH), the Supreme Administrative Court (Verwaltungsgerichtshof, VwGH), the Federal Administrative Court (Bundesverwaltungsgericht, BVwG), and decisions by institutions such as the Equal Treatment Commission, the Data Protection Authority, and the Federal Disciplinary Board.
This wealth of information makes RIS an indispensable source for anyone working on the Austrian legal system. Although individual records are readily available through the official website, obtaining RIS data in bulk and analysing it beyond individual cases has generally required working directly with the service’s API and transforming the returned JSON.
The new R package {risAT} aims to lower this barrier. It wraps the RIS Open Government Data REST API and returns tidy1 data frames with standardised English column names while preserving the links and metadata needed to inspect the original RIS records. The package can help legal scholars, legal professionals, journalists, and other R users assemble corpora for text analysis or calculate descriptive statistics for decisions matching specified criteria. In short, {risAT} is designed for questions that extend beyond a single record.
1 “Tidy” data follows a simple convention—each variable is a column, each observation is a row, and each value is a cell—which makes it easy to work with in R. See Hadley Wickham’s Tidy Data paper for the original formulation.
Please note that the package is still under development (0.1.0) and that its interface may change. The package is neither affiliated with nor endorsed by RIS or any other institution.
You can install {risAT} from GitHub:
# install.packages("pak")
pak::pak("werkstattcodes/risAT")For the full function reference, see the documentation site and the GitHub repository. If you want to receive updates on releases—likely to be particularly relevant in this early phase—I recommend subscribing to the repository on GitHub. For questions or suggestions, please use the discussion board or file an issue on the GitHub page.
If you use the package in a publication, it would be only fair to cite it:
2 Scope
The RIS API organises case law by application. In practice, that means one application for each court system or specialised decision-making body. {risAT} provides convenience wrappers for the main courts, plus a generic search function:
| Function | Court / Body |
|---|---|
ris_search_vfgh() |
Constitutional Court (Verfassungsgerichtshof, VfGH) |
ris_search_vwgh() |
Supreme Administrative Court (Verwaltungsgerichtshof, VwGH) |
ris_search_justiz() |
Ordinary courts (OGH, OLG, LG, BG) |
ris_search_bvwg() |
Federal Administrative Court (Bundesverwaltungsgericht, BVwG) |
ris_search_lvwg() |
State administrative courts (Landesverwaltungsgerichte, LVwG) |
ris_search_dsk() |
Data protection authorities |
ris_search_dok() |
Disciplinary body decisions |
ris_search_pvak() |
Staff representation oversight |
ris_search_gbk() |
Equal Treatment Commission (Gleichbehandlungskommission) |
ris_search_case_law() |
Generic wrapper for any supported case-law application |
The court-specific functions are thin wrappers around ris_search_case_law(). Beyond case law, ris_search_federal() provides access to the federal-law (Bundesrecht) application.
Results share several core columns, including id, application, decision_date, and case_number, although the exact schema varies by RIS application/court. Where available, the content_urls list-column stores links to the record in formats such as HTML, PDF, XML, and RTF.
With echo = TRUE, {risAT} prints the equivalent RIS web search URL and the number of returned rows. This is useful because it lets you move between the reproducible R workflow and the browser interface that many legal researchers already know.
3 Examples
The examples below are meant to show the kinds of questions one can start asking once RIS case law is available as dataset. They are not full analyses, and the query choices are intentionally transparent so that they can be adjusted for more careful research designs.
For details on the API see the official RIS OGD information.
3.1 VfGH — European Convention on Human Rights
To start, we use {risAT} to query records from the Austrian Constitutional Court, the Verfassungsgerichtshof (VfGH). The European Convention on Human Rights (ECHR) is directly applicable in Austria and has constitutional rank, making its presence in VfGH decisions particularly relevant. To examine how frequently VfGH records refer to ECHR norms, the code below
- retrieves data VfGH records from the beginning of 2000 to the end of 2025, and
- checks whether the ECHR is referenced in each record’s
normscolumn.
Here is the relevant function call via {risAT}’s ris_search_vfgh()2:
2 This call is rather comprehensive and can be quite time consuming. To add a layer of robustness, you may want to split it into separate calls for each year, store the interim results, and then combine them. Also a note on netiquette: the RIS API is a public service; please be considerate and avoid sending too many requests in quick succession.
Get VfGH records from 2000 to 2025
vfgh_2000_2025 <- ris_search_vfgh(
decision_date_from = "2000-01-01",
decision_date_to = "2025-12-31",
search_decision_text = TRUE,
search_legal_principles = TRUE,
echo = TRUE
) In total, the query returns 21000 records. Below is a glimpse of the data structure, showing the main columns and their types. The norms column is a list-column, which means that each row can contain multiple norms.
Describe result
glimpse(vfgh_2000_2025)
## Rows: 21,000
## Columns: 31
## $ id <chr> "JFR_09998771_00B0219…
## $ application <chr> "Vfgh", "Vfgh", "Vfgh…
## $ authority <chr> "Verfassungsgerichtsh…
## $ document_url <chr> "https://www.ris.bka.…
## $ document_type <chr> "Rechtssatz", "Rechts…
## $ case_number <list> "B2190/00", "B2289/0…
## $ norms <list> ["VfGG §85 Abs2 / Be…
## $ decision_date <date> 2000-12-29, 2000-12-…
## $ keywords <chr> "VfGH / Wirkung aufsc…
## $ ecli <chr> "ECLI:AT:VFGH:2000:B2…
## $ vfgh_decision_type <chr> "Beschluss", "Beschlu…
## $ vfgh_court <chr> "Verfassungsgerichtsh…
## $ vfgh_indices <list> ["10 Verfassungsr…
## $ vfgh_collection_number <chr> "******", "******", "…
## $ judikatur_vfgh_entscheidungstexte <chr> "", "", NA, NA, NA, N…
## $ content_urls <list> <"https://www.ris.bk…
## $ app_metadata <list> [[NA, 1038, 1, 100],…
## $ vfgh_decision_text_case_number <chr> NA, NA, "A 21/99", NA…
## $ vfgh_decision_text_document_type <chr> NA, NA, "Text", NA, "…
## $ vfgh_decision_text_court <chr> NA, NA, "VfGH", NA, "…
## $ vfgh_decision_text_decision_date <chr> NA, NA, "2000-12-20",…
## $ vfgh_decision_text_document_url <chr> NA, NA, "https://www.…
## $ vfgh_decision_text_document_number <chr> NA, NA, "JFT_09998780…
## $ vfgh_decision_text_decision_type <chr> NA, NA, "Vergleich", …
## $ vfgh_headnote <chr> NA, NA, NA, "Vergleic…
## $ full_decision_url <chr> NA, NA, NA, "https://…
## $ legal_principles_url <chr> NA, NA, NA, "https://…
## $ vfgh_decision_texts <list> NA, NA, NA, NA, NA, …
## $ modified <chr> NA, NA, NA, NA, NA, N…
## $ source_year <int> 2000, 2000, 2000, 200…
## $ judikatur_vfgh_entscheidungstexte_item_anmerkung <chr> NA, NA, NA, NA, NA, N…Next, we check which decisions feature a norm of the ECHR and calculate annual totals with and without such a reference. The code extracts the year from decision_date, checks the norms column for ECHR references, and calculates annual totals and shares.
To avoid counting both a decision text and its associated legal principle, we retain records of type "Text" and exclude "Rechtssatz" records, which summarise the legal essence of decisions.
Identify VfGH decisions with ECHR norm reference
class(vfgh_2000_2025$norms)
## [1] "list"
table(vfgh_2000_2025$document_type)
##
## Rechtssatz Text
## 9681 11319
# Keep only decision texts.
vfgh_2000_2025_echr <- vfgh_2000_2025 %>%
filter(document_type == "Text")
nrow(vfgh_2000_2025_echr)
## [1] 11319
# Extract the year and identify records with at least one EMRK norm.
vfgh_2000_2025_echr <- vfgh_2000_2025_echr %>%
mutate(year = year(decision_date)) %>%
mutate(
norm_echr = map_lgl(norms, \(x) any(str_detect(as.character(unlist(x)), fixed("EMRK")), na.rm = TRUE))
)
# Calculate annual totals and labels for plotting.
vfgh_year <- vfgh_2000_2025_echr %>%
count(year, norm_echr) %>%
mutate(
norm_reference = factor(
if_else(norm_echr, "With ECHR norm reference", "Without ECHR norm reference"),
levels = c("With ECHR norm reference", "Without ECHR norm reference")
)
) %>%
group_by(year) %>%
mutate(
total = sum(n),
share = n / total,
echr_label = if_else(norm_echr, scales::percent(share, accuracy = 1), NA_character_)
) %>%
ungroup()Now we can visualise the results.
Plot annual VfGH decisions and their ECHR references
vfgh_year_totals <- vfgh_year %>%
distinct(year, total) %>%
filter(total > 0)
vfgh_year %>%
ggplot(aes(x = year, y = n, fill = norm_reference)) +
geom_col(width = 0.72, key_glyph = draw_key_point) +
geom_text(
data = vfgh_year_totals,
aes(x = year, y = total, label = total),
inherit.aes = FALSE,
vjust = -0.35,
color = "black",
size = gg_annot_size,
family = gg_plot_family
) +
geom_text(
aes(label = echr_label),
position = position_stack(vjust = 0.5),
color = "black",
size = gg_annot_size * 0.8,
family = gg_plot_family,
na.rm = TRUE
) +
scale_x_continuous(
breaks = seq(2000, 2025, 5),
expand = expansion(mult = c(0, 0))
) +
scale_y_continuous(expand = expansion(mult = c(0, 0.12))) +
scale_fill_manual(
values = c(
"With ECHR norm reference" = "#e69f00",
"Without ECHR norm reference" = "#BFBFBF"
),
name = "Norm reference"
) +
guides(fill = guide_legend(override.aes = list(shape = 21, size = 5, colour = "white"))) +
labs(
title = "How many decisions of the Austrian Constituional Court reference the European Convention on Human Rights (ECHR)?",
subtitle = paste0(
"The Austrian Legal Information System (RIS) returns for each decision of the Constitutional Court the most important legal norms referenced in the decision. This allows identifying decisions with and without references to the ECHR; labels show each year’s total (top) and the ECHR-referencing share (bar segment)."
),
x = NULL,
y = NULL,
caption = ris_graph_caption
) +
ris_plot_theme() +
theme(
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
panel.grid.major.y = element_blank()
)As it turns out, the annual share is relatively stable, fluctuating between about 8 and 17 per cent without a clear long-term trend.
We can refine the analysis by examining the ECHR articles, paragraphs, and Additional Protocol provisions referenced in these records. The code below extracts parseable article and paragraph combinations from norms, counts each combination once per decision, and adds a short descriptive label for each article.
Unnest ECHR norm references and map article numbers to labels
echr_norm_names <- tibble::tribble(
~ZP_type, ~art, ~norm_short,
NA_character_, "1", "Obligation to respect human rights",
NA_character_, "2", "Right to life",
NA_character_, "3", "Prohibition of torture",
NA_character_, "4", "Prohibition of slavery and forced labour",
NA_character_, "5", "Right to liberty and security",
NA_character_, "6", "Right to a fair trial",
NA_character_, "7", "No punishment without law",
NA_character_, "8", "Right to respect for private and family life",
NA_character_, "9", "Freedom of thought, conscience and religion",
NA_character_, "10", "Freedom of expression",
NA_character_, "11", "Freedom of assembly and association",
NA_character_, "12", "Right to marry",
NA_character_, "13", "Right to an effective remedy",
NA_character_, "14", "Prohibition of discrimination",
NA_character_, "15", "Derogation in time of emergency",
NA_character_, "16", "Restrictions on political activity of aliens",
NA_character_, "17", "Prohibition of abuse of rights",
NA_character_, "18", "Limitation on use of restrictions on rights",
"1. ZP", "1", "Protection of property",
"1. ZP", "2", "Right to education",
"1. ZP", "3", "Right to free elections",
"4. ZP", "1", "Prohibition of imprisonment for debt",
"4. ZP", "2", "Freedom of movement",
"4. ZP", "3", "Prohibition of expulsion of nationals",
"4. ZP", "4", "Prohibition of collective expulsion of aliens",
"6. ZP", "1", "Abolition of the death penalty",
"7. ZP", "1", "Procedural safeguards relating to expulsion of aliens",
"7. ZP", "2", "Right of appeal in criminal matters",
"7. ZP", "3", "Compensation for wrongful conviction",
"7. ZP", "4", "Right not to be tried or punished twice",
"7. ZP", "5", "Equality between spouses",
"12. ZP", "1", "General prohibition of discrimination",
"13. ZP", "1", "Abolition of the death penalty in all circumstances"
)
vfgh_2000_2025_echr <- vfgh_2000_2025_echr %>%
filter(norm_echr) %>%
mutate(norms = map(norms, normalise_norm_values)) %>% #account for different norm formats (character and list)
unnest_longer(norms)
class(vfgh_2000_2025_echr$norms)
## [1] "character"
echr_decisions_total <- n_distinct(vfgh_2000_2025_echr$id)
echr_norm_parsed <- vfgh_2000_2025_echr %>%
filter(str_detect(norms, fixed("EMRK"))) %>%
transmute(
id,
parsed = map(norms, parse_echr_norm_reference)
) %>%
unnest(parsed)
unparsed_echr_norms <- echr_norm_parsed %>%
filter(is.na(art))
norm_count <- echr_norm_parsed %>%
filter(!is.na(art)) %>%
distinct(id, ZP_type, art, abs) %>%
count(ZP_type, art, abs, sort = TRUE) %>%
mutate(
label = str_squish(str_c(
coalesce(str_c(ZP_type, ", "), ""),
str_c("Art. ", art),
coalesce(str_c(", Abs. ", abs), "")
))
) %>%
left_join(echr_norm_names, by = join_by(ZP_type, art)) %>%
mutate(norm_short = coalesce(norm_short, "ECHR norm"))1 ECHR norm entry does not contain a parseable article reference and is excluded from the ranking.
Plot the most frequent ECHR norm references
norm_count_top <- norm_count %>%
mutate(label = forcats::fct_lump_n(label, n = 10, w = n, other_level = "all other ECHR norms referenced.")) %>%
summarise(
n = sum(n),
norm_short = if_else(
n_distinct(norm_short) == 1,
first(norm_short),
"Other ECHR norms"
),
.by = label
) %>%
mutate(
rel = n / echr_decisions_total,
bar_label = sprintf("%s (%.1f%%)", n, rel * 100),
label = forcats::fct_reorder(label, n),
label = forcats::fct_relevel(label, "all other ECHR norms referenced.", after = 0)
)
left_axis_labels <- norm_count_top %>%
mutate(
label_md = str_c(
# escape leading "1." etc. so gridtext doesn't parse it as an ordered list
str_replace_all(str_wrap(as.character(label), 15), "\n", "<br>") %>%
str_replace("^(\\d+)\\.", "\\1\\\\."),
if_else(
label == "all other ECHR norms referenced.",
"", # short name would be redundant here
str_c(
"<br><span style='font-size:8pt;color:#52616B'>(",
str_replace_all(str_wrap(norm_short, 22), "\n", "<br>"),
")</span>"
)
)
)
) %>%
{ setNames(.$label_md, as.character(.$label)) }
vfgh_decisions_total <- sum(vfgh_year$n)
echr_share <- echr_decisions_total / vfgh_decisions_total
norm_count_top %>%
ggplot(aes(x = n, y = label)) +
geom_col(fill = "#1185FE", width = 0.72) +
geom_text(
aes(label = bar_label),
hjust = -0.15,
color = "#4D4D4D",
size = gg_annot_size,
family = gg_plot_family
) +
scale_x_continuous(expand = expansion(mult = c(0, 0.18))) +
scale_y_discrete(labels = \(x) left_axis_labels[x]) +
coord_cartesian(clip = "off") +
labs(
title = "Which ECHR provisions does Austria’s Constitutional Court reference most often?",
subtitle = str_glue(
"RIS returns {scales::comma(vfgh_decisions_total)} Austrian Constitutional Court (VfGH) ",
"decisions for 2000–2025; {scales::comma(echr_decisions_total)} ",
"({scales::percent(echr_share, accuracy = 0.1)}) reference norms of the ECHR. ",
"The chart shows the 10 most frequent article–paragraph combinations, each counted once ",
"per decision text; percentages use all ECHR-referencing texts as the denominator. ",
"Additional Protocols are included."
),
x = "Number of decisions",
y = NULL,
caption = ris_graph_caption
) +
ris_plot_theme() +
theme(
# must target the leaf element: with ggplot2 4.0 complete themes,
# axis.text.y gets overridden by the theme's plain axis.text.y.left
axis.text.y.left = ggtext::element_markdown(
size = gg_axis_size,
family = gg_plot_family,
color = "#4D4D4D",
lineheight = 0.95,
hjust = 1, # place the label box against the axis
halign = 1 # right-align lines inside the box
),
panel.grid.major.x = element_line(color = "#D8DEE4", linewidth = 0.35),
panel.grid.major.y = element_blank()
)3.2 VwGH — Subsidiary protection
The Verwaltungsgerichtshof (Supreme Administrative Court, VwGH) is Austria’s highest court in administrative matters. Its jurisdiction includes asylum proceedings; see the VwGH overview of its role.
The example below demonstrates how to query jurisprudence for a specific legal norm. The norm parameter filters records by cited norm; here we search for AsylG 2005 §8, the provision on subsidiary protection. In the example above we used the norms column of the returned dataset to identify ECHR references. Here we use the norm parameter to filter the search results directly with the call specification.
Get VwGH data on subsidiary protection
asyl_subsidiary_all <- ris_search_vwgh(
norm = "'AsylG 2005 §8'",
decision_date_from = "2000-01-01",
decision_date_to = "2025-12-31",
search_legal_principles = FALSE,
echo = TRUE
)
# Check for duplicate identifiers.
nrow(janitor::get_dupes(asyl_subsidiary_all, id))
## [1] 0
glimpse(asyl_subsidiary_all)
## Rows: 1,083
## Columns: 23
## $ id <chr> "JWT_2025190287_20251222L00", "JWT…
## $ application <chr> "Vwgh", "Vwgh", "Vwgh", "Vwgh", "V…
## $ authority <chr> "Verwaltungsgerichtshof (VwGH)", "…
## $ published <chr> "2026-01-22", "2026-01-12", "2026-…
## $ modified <chr> "2026-01-22", "2026-01-12", "2026-…
## $ document_url <chr> "https://www.ris.bka.gv.at/Dokumen…
## $ document_type <chr> "Text", "Text", "Text", "Text", "T…
## $ case_number <chr> "Ra 2025/19/0287", "Ra 2025/19/016…
## $ norms <list> ["AsylG 2005 §8 Abs1", "AsylG 200…
## $ decision_date <date> 2025-12-22, 2025-12-15, 2025-12-1…
## $ ecli <chr> "ECLI:AT:VWGH:2025:RA2025190287.L0…
## $ vwgh_decision_type <chr> "Beschluss", "Erkenntnis", "Erkenn…
## $ vwgh_document_number_type <chr> "L", "L", "L", "L", "L", "L", "L",…
## $ vwgh_primary_legal_principle_number <chr> "JWT_2025190287_20251222L00", "JWT…
## $ vwgh_court <chr> "Verwaltungsgerichtshof (VwGH)", "…
## $ vwgh_indices <list> ["10/01 Bundes-Verfassungsgesetz …
## $ full_decision_url <chr> "https://www.ris.bka.gv.at/Judikat…
## $ legal_principles_url <chr> "https://www.ris.bka.gv.at/Judikat…
## $ content_urls <list> <"https://www.ris.bka.gv.at/Dokum…
## $ vwgh_note <chr> NA, NA, "<br/>Miterledigung (miter…
## $ vwgh_court_decisions <list> NA, NA, NA, "EuGH 62021CJ0663 Bun…
## $ keywords <list> NA, NA, NA, NA, NA, "Anzuwendende…
## $ vwgh_collection_number <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA…The resulting graph below shows the annual counts of distinct VwGH decisions citing AsylG 2005 §8. The series rises during the 2010s, peaks in 2019–2021, and declines thereafter. These numbers are consistent with the so-called ‘refugee-crisis’ period in Austria and Europe more broadly.
Plot data on subsidiary protection by year
asyl_by_year <- asyl_subsidiary_all %>%
mutate(year = year(decision_date)) %>%
distinct(year, id) %>%
count(year, name = "decisions") %>%
complete(year = 2000:2025, fill = list(decisions = 0L)) %>%
arrange(year)
asyl_by_year %>%
ggplot(aes(x = year, y = decisions)) +
geom_col(fill = "#1185FE", width = 0.72) +
geom_text(
data = asyl_by_year %>% filter(decisions > 0),
aes(label = decisions),
vjust = -0.35,
size = gg_annot_size,
family = gg_plot_family
) +
scale_x_continuous(breaks = seq(2000, 2025, 5)) +
scale_y_continuous(expand = expansion(mult = c(0, 0.12))) +
labs(
title = "VwGH decisions citing AsylG 2005 §8 (subsidary protection) 2000–2025",
subtitle = paste0(
"Annual number of decisions of the Austrian Supreme Administrative Court (VwGH) citing AsylG 2005 §8, ",
"2000–2025, counted by distinct record ID. Legal-principle searches are excluded."
),
x = NULL,
y = "Number of decisions",
caption = ris_graph_caption
) +
ris_plot_theme()3.2.1 Searching for a free text match
Figure 3 shows all records returned for the selected norm. We can narrow this set with a free-text query. The search below uses Afghan* OR Syri* OR Irak* to identify records that mention the selected country-name stems. Note that a match indicates only a textual mention, not necessarily that the country is the decision’s principal subject or that the applicant definitly came from one of these countries. It just indicates the presence of the term in the decision text.
Query subsidiary protection records mentioning Afghanistan, Syria, or Iraq
asyl_subsidiary_country_mentions <- ris_search_vwgh(
norm = "'AsylG 2005 §8'",
query = "Afghan* OR Syri* OR Irak*",
decision_date_from = "2000-01-01",
decision_date_to = "2025-12-31",
search_decision_text = TRUE,
search_legal_principles = FALSE,
echo = TRUE
)
country_mention_ids <- as.character(asyl_subsidiary_country_mentions$id)
asyl_subsidiary_all <- asyl_subsidiary_all %>%
mutate(
year = year(decision_date),
mentions_selected_country = as.character(id) %in% country_mention_ids
)
asyl_country_by_year <- asyl_subsidiary_all %>%
distinct(year, id, mentions_selected_country) %>%
count(year, mentions_selected_country, name = "decisions") %>%
complete(
year = 2000:2025,
mentions_selected_country = c(FALSE, TRUE),
fill = list(decisions = 0L)
) %>%
arrange(year, mentions_selected_country)
asyl_country_year_labels <- asyl_country_by_year %>%
group_by(year) %>%
summarise(
total = sum(decisions),
.groups = "drop"
)
asyl_country_segment_labels <- asyl_country_by_year %>%
group_by(year) %>%
mutate(
total = sum(decisions),
country_mention_share = if_else(total > 0, decisions / total, NA_real_),
country_mention_label = if_else(
mentions_selected_country & decisions > 0,
scales::percent(country_mention_share, accuracy = 1),
NA_character_
)
) %>%
ungroup()Plot subsidiary protection records by year and country-name mentions
asyl_country_by_year %>%
ggplot(aes(x = year, y = decisions, fill = mentions_selected_country)) +
geom_col(width = 0.72, key_glyph = draw_key_point) +
geom_text(
data = asyl_country_year_labels %>% filter(total > 0),
aes(x = year, y = total, label = total),
inherit.aes = FALSE,
vjust = -0.35,
size = gg_annot_size,
family = gg_plot_family
) +
geom_text(
data = asyl_country_segment_labels,
aes(label = country_mention_label),
position = position_stack(vjust = 0.5),
color = "white",
size = gg_annot_size,
family = gg_plot_family,
na.rm = TRUE
) +
scale_x_continuous(breaks = seq(2000, 2025, 5)) +
scale_y_continuous(expand = expansion(mult = c(0, 0.12))) +
scale_fill_manual(
values = c("FALSE" = "#7f7f7f", "TRUE" = "#e69f00"),
labels = c(
"FALSE" = "No selected-country-term match",
"TRUE" = "Matches selected country terms"
),
name = NULL
) +
guides(fill = guide_legend(override.aes = list(shape = 21, size = 5, colour = "white"))) +
labs(
title = "Decisions of the Austrian Supreme Administrative Court citing AsylG 2005 §8 (subsidiary protection) and mentioning Afghanistan, Syria, or Iraq",
subtitle = paste0(
"Annual number of decisions of the Supreme Administrative Court (VwGH) citing AsylG 2005 §8, 2000–2025. Highlighted records match “Afghan*”, ",
"“Syri*”, or “Irak*” in the decision text; legal principles ('Rechtssätze') are excluded. Matches only indicate a textual mention, not that the country is the decision’s principal subject or that the applicant definitively came from one of these countries."
),
x = NULL,
y = "Annual total number of decisions",
caption = ris_graph_caption
) +
ris_plot_theme()From 2018 onwards, more than half of the annual results mention at least one of the selected country-name stems, a further finding which is consistent with the already mentioned ‘refugee-crisis’.
3.3 GBK — Discrimination grounds
The Equal Treatment Commission example illustrates the wrapper’s domain-specific filters. Instead of relying only on text search, we can filter the RIS GBK endpoint by discrimination ground. Figure 5 shows annual search-result counts through 12 July 2026. These are endpoint classifications and record counts, not estimates of the underlying incidence of discrimination.
Search GBK records by discrimination ground and year
discrimination_grounds <- c(
gender = "Gender",
ethnicity = "Ethnicity",
religion = "Religion",
worldview = "Worldview",
age = "Age",
sexual_orientation = "Sexual orientation",
multiple = "Multiple discrimination"
)
gbk_topics <- tibble(
discrimination_ground = names(discrimination_grounds),
discrimination_grounds_en = unname(discrimination_grounds)
)
gbk_results_by_ground <- gbk_topics$discrimination_ground %>%
set_names() %>%
map(\(ground) {
ris_search_gbk(
discrimination_ground = ground,
decision_date_to = "2026-07-12",
echo = TRUE
)
})
normalise_gbk_columns <- function(data) {
data %>%
mutate(
across(
any_of(c(
"id", "application", "authority", "document_url",
"gbk_commission", "gbk_discrimination_ground",
"gbk_discrimination_ground_en", "gbk_discrimination_offense",
"gbk_decision_type", "gbk_senate"
)),
as.character
),
across(any_of(c("decision_date", "modified")), as.Date),
across(any_of(c("norms", "content_urls", "case_number")), as.list)
)
}
gbk_records <- gbk_results_by_ground %>%
map(normalise_gbk_columns) %>%
list_rbind(names_to = "discrimination_ground")
gbk_records_by_year <- gbk_records %>%
mutate(year = year(decision_date)) %>%
summarise(decisions = n(), .by = c(year, discrimination_ground)) %>%
left_join(gbk_topics, by = "discrimination_ground") %>%
mutate(
discrimination_grounds_en = factor(
discrimination_grounds_en,
levels = unname(discrimination_grounds)
)
)Plot annual GBK results by discrimination ground
gbk_plot_data <- gbk_records_by_year %>%
mutate(ground_total = sum(decisions), .by = discrimination_grounds_en) %>%
mutate(
ground_share = ground_total / sum(decisions),
facet_label = str_glue(
"{discrimination_grounds_en}: {scales::comma(ground_total)} ({scales::percent(ground_share, accuracy = 0.1)})"
),
facet_label = forcats::fct_reorder(facet_label, as.integer(discrimination_grounds_en))
)
gbk_plot_data %>%
ggplot(aes(x = year, y = decisions)) +
geom_col(fill = "#1185FE", width = 0.72) +
facet_wrap(vars(facet_label)) +
scale_x_continuous(breaks = scales::breaks_width(2)) +
scale_y_continuous(expand = expansion(mult = c(0, 0.08)), labels = scales::comma) +
labs(
title = "Which discrimination grounds appear most often in Equal Treatment Commission records?",
subtitle = paste0(
"Annual RIS query results by decision year, 2014–12 July 2026; 2026 is incomplete. ",
"Facet headings show each ground’s total and share across all returned results. Counts are ",
"endpoint classifications, not estimates of the incidence of discrimination."
),
x = NULL,
y = "Number of decisions",
fill = NULL,
caption = ris_graph_caption
) +
ris_plot_theme() +
theme(
legend.position = "none",
strip.text = element_textbox(face = "bold")
)4 What’s next
At the moment, {risAT} focuses on the RIS case-law and federal-law endpoints. That already covers a lot: constitutional review, administrative-law disputes, data protection, equality decisions, ordinary court case law, and more.
The analyses presented above are only a snapshot, but I hope they have given you a sense of what {risAT} can contribute—and perhaps sparked your interest in the project. As time permits, I will gradually expand and harden the package, including by broadening its API coverage and refining the court-specific parameter mappings. If you have any questions or suggestions, please feel free to contact me via direct message on Bluesky or leave a note on the {risAT} GitHub discussion board.
Reuse
Citation
@online{schmidt2026,
author = {Schmidt, Roland},
title = {Introducing {\{risAT\}:} {An} {R} Package to Access with
{Austria’s} {Legal} {Information} {System} {(RIS)}},
date = {2026-07-23},
url = {https://werk.statt.codes/posts/2026-06-30-risAT-intro/},
langid = {en}
}
