Skip to contents

Overview

The MRM empirical callables (mrm_otis_*, mrm_tps_*, mrm_siu_*) operate on three external data sources. This vignette documents how rmorie makes each of them accessible:

  • OTIS (Offender Tracking Information System; published by the Ontario Ministry of the Solicitor General) – public CKAN release on data.ontario.ca, reached via the existing morie_load_dataset() infrastructure.
  • TPS (Toronto Police Service) – public ArcGIS Open Data, reached via morie_fetch_tps().
  • SIU (Ontario Special Investigations Unit) – public Director’s Reports. morie_fetch_siu() writes the panel-reviewed corpus bundled in rmoriedata, or fetches the reports live on request.

Four small reference tables, read by morie_sample(), let the examples run offline.

Included reference samples

otis_b09 and otis_c11 ship in rmorie and are the whole published tables. otis_b01 and tps_assault ship in rmoriedata and are 1,000-row extracts.

# Included samples
b01 <- morie_sample("otis_b01")
b09 <- morie_sample("otis_b09")
c11 <- morie_sample("otis_c11")
tps <- morie_sample("tps_assault")

# Schema sanity
str(b01, max.level = 1)
#> 'data.frame':    1000 obs. of  18 variables:
#>  $ EndFiscalYear                                         : int  2025 2023 2023 2023 2024 2024 2025 2025 2024 2023 ...
#>  $ UniqueIndividual_ID                                   : chr  "2025-08960-SG" "2023-11152-SG" "2023-08955-SG" "2023-11360-SG" ...
#>  $ Gender                                                : chr  "Male" "Male" "Female" "Male" ...
#>  $ Region_AtTimeOfPlacement                              : chr  "Eastern" "Northern" "Central" "Central" ...
#>  $ Region_MostRecentPlacement                            : chr  "Eastern" "Northern" "Central" "Central" ...
#>  $ Age_Category                                          : chr  "25 to 49" "25 to 49" "25 to 49" "25 to 49" ...
#>  $ NumberConsecutiveDays_Segregation                     : int  4 1 6 1 2 1 1 1 2 3 ...
#>  $ SegReason_SecurityOfInstitution_SafetyOfOthers        : chr  "No" "No" "No" "No" ...
#>  $ SegReason_InmateNeedsProtection                       : chr  "No" "Yes" "No" "No" ...
#>  $ SegReason_InmateNeedsProtection_Medical               : chr  "Yes" "No" "No" "No" ...
#>  $ SegReason_SecurityOfInstitution_SafetyOfOthers_Medical: chr  "No" "No" "Yes" "Yes" ...
#>  $ SegReason_Disciplinary_Segregation                    : chr  "No" "No" "No" "No" ...
#>  $ SegReason_InmateRefuseSearch_Scan                     : chr  "No" "No" "No" "No" ...
#>  $ MentalHealth_Alert                                    : chr  "Yes" "No" "Yes" "No" ...
#>  $ SuicideRisk_Alert                                     : chr  "Yes" "No" "Yes" "No" ...
#>  $ SuicideWatch_Alert                                    : chr  "Yes" "No" "Yes" "No" ...
#>  $ SegReason_Other                                       : chr  "No" "" "" "" ...
#>  $ Number_Of_Placements                                  : int  1 8 1 1 1 2 2 1 2 1 ...
nrow(b09)
#> [1] 78
nrow(c11)
#> [1] 33
dim(tps)
#> [1] 1000   31

OTIS via CKAN (data.ontario.ca)

The OTIS public release is hosted at data.ontario.ca/dataset/data-on-inmates-in-ontario (31 resources when this vignette was written, 29 of them CSV). The catalogue registers these tables with their CKAN resource IDs:

catalog <- morie_dataset_catalog()
catalog[catalog$source == "otis", c("key", "name", "ckan_resource_id")]
#>        key
#> 37 otisloc
#> 38 otisa01
#> 39 otisb01
#> 40 otisb09
#> 41 otisc11
#>                                                                      name
#> 37 OTIS: Ontario's adult provincial correctional institutions (locations)
#> 38                   OTIS a01: Restrictive Confinement - Detailed Dataset
#> 39                               OTIS b01: Segregation - Detailed Dataset
#> 40            OTIS b09: Individuals in Segregation - Number of Placements
#> 41            OTIS c11: Individuals in Segregation/RC by Aggregate Length
#>                        ckan_resource_id
#> 37                                     
#> 38 5a0c5804-a055-4031-9743-73f556e43bb4
#> 39 406e6d90-d568-4553-8ca7-bc9f90e133b9
#> 40 df24e943-d52b-43a8-a10e-a3cc906e26bb
#> 41 9c7b74a5-53ad-4ef0-a7a6-97772cd01c55

To pull the full b01 from CKAN (network; not run when the vignette is built):

b01_full <- morie_load_dataset("otisb01")
nrow(b01_full)
# 82001 (October 2026)

The loader downloads the CSV on the first call, keeps it in the per-user cache under morie_cache_dir(), and reads that copy on later calls (refresh = TRUE downloads it again).

TPS via ArcGIS Open Data

Toronto Police Service publishes per-category crime events through ArcGIS Online. MORIE knows the layer URLs for nine categories.

morie_tps_layer_urls()
#>                                                                                                                       Assault 
#>                         "https://services.arcgis.com/S9th0jAJ7bqgIRjw/arcgis/rest/services/Assault_Open_Data/FeatureServer/0" 
#>                                                                                                                     AutoTheft 
#>                      "https://services.arcgis.com/S9th0jAJ7bqgIRjw/arcgis/rest/services/Auto_Theft_Open_Data/FeatureServer/0" 
#>                                                                                                                  BicycleTheft 
#>                  "https://services.arcgis.com/S9th0jAJ7bqgIRjw/arcgis/rest/services/Bicycle_Thefts_Open_Data/FeatureServer/0" 
#>                                                                                                                 BreakAndEnter 
#>                 "https://services.arcgis.com/S9th0jAJ7bqgIRjw/arcgis/rest/services/Break_and_Enter_Open_Data/FeatureServer/0" 
#>                                                                                                                     Homicides 
#>        "https://services.arcgis.com/S9th0jAJ7bqgIRjw/arcgis/rest/services/Homicides_Open_Data_ASR_RC_TBL_002/FeatureServer/0" 
#>                                                                                                                       Robbery 
#>                         "https://services.arcgis.com/S9th0jAJ7bqgIRjw/arcgis/rest/services/Robbery_Open_Data/FeatureServer/0" 
#>                                                                                                   ShootingAndFirearmDiscarges 
#> "https://services.arcgis.com/S9th0jAJ7bqgIRjw/arcgis/rest/services/Shooting_and_Firearm_Discharges_Open_Data/FeatureServer/0" 
#>                                                                                                                   TheftFromMV 
#>        "https://services.arcgis.com/S9th0jAJ7bqgIRjw/arcgis/rest/services/Theft_From_Motor_Vehicle_Open_Data/FeatureServer/0" 
#>                                                                                                                     TheftOver 
#>                      "https://services.arcgis.com/S9th0jAJ7bqgIRjw/arcgis/rest/services/Theft_Over_Open_Data/FeatureServer/0"

To fetch a single category (network; not run when the vignette is built):

csv_path <- morie_fetch_tps("Assault")
assault <- utils::read.csv(csv_path)
nrow(assault)
# 267796 (the layer count in October 2026)

The fetcher pages through /query with up to 2,000 records per page (max_per_page) and writes tps_Assault.csv into cache_dir, which defaults to a directory under tempdir(). Later calls in the session return the cached path unless overwrite = TRUE; pass a persistent cache_dir to keep the file between sessions.

Filtering at the server side:

recent <- morie_fetch_tps("Homicides",
                          where = "OCC_YEAR >= 2024",
                          overwrite = TRUE)

SIU Director’s Reports

The Ontario SIU publishes Director’s Reports at siu.on.ca. morie_fetch_siu() returns the path of an SIU.csv file. By default it writes the panel-reviewed corpus bundled in rmoriedata, which needs no network access:

csv_path <- morie_fetch_siu(lang = "en")
#> SIU: wrote the panel-reviewed corpus from rmoriedata (2309 rows) to /tmp/Rtmp5SNd8H/morie/siu/SIU.csv. For a live re-fetch set options(morie.siu.allow_fetch = TRUE).
siu <- utils::read.csv(csv_path)
dim(siu)
#> [1] 2309   66

With options(morie.siu.allow_fetch = TRUE) it fetches the reports live instead: it walks the DRID manifest shipped with rmorie (morie_siu_index()), requests each report and news release at rate_rps requests per second with concurrency parallel requests, and parses them with the package’s C++ parser. That takes a long time and needs the network, so it is not run here:

options(morie.siu.allow_fetch = TRUE)
csv_path <- morie_fetch_siu(lang = "en", cache_html = TRUE,
                            rate_rps = 4, overwrite = TRUE)

The siu-pipeline vignette covers the manifest, auditing and corrections.

Workflow: from included sample to verified result

Putting it together – a complete worked example without any network call:

b01 <- morie_sample("otis_b01")

# Mandela classification, one row per placement
mrm_classify_mandela(b01, denominator = "row")
#>     year denominator n_mandela        rate  pct n_broader_rc rate_broader
#> 1   2023         362         0 0.000000000 0.00            0  0.000000000
#> 2   2024         337         3 0.008902077 0.89            3  0.008902077
#> 3   2025         301         4 0.013289037 1.33            4  0.013289037
#> 4 pooled        1000         7 0.007000000 0.70            7  0.007000000

# Segregation duration KM
mrm_otis_seg_duration_km(b01,
                         group_cols = "MentalHealth_Alert")
#>   stratum   n mean_days median_days q75_days pct_above_mandela
#> 1      No 499      2.76           2        3               0.6
#> 2     Yes 501      3.29           2        4               0.8
#>   median_among_above_mandela days_at_S50 days_at_S25 days_at_S10 days_at_S05
#> 1                       54.0           2           3           4           6
#> 2                       41.5           2           4           5           8
#>   days_at_S01
#> 1       11.02
#> 2       13.00

# Mortification co-occurrence: Cramer's V across alert pairs
mrm_otis_mortification_cooccurrence(b01)
#>              alert_a            alert_b    n   chi2 df   p_value cramers_v
#> 1 MentalHealth_Alert  SuicideRisk_Alert 1000  33.25  1  8.12e-09    0.1823
#> 2 MentalHealth_Alert SuicideWatch_Alert 1000  12.09  1  5.08e-04    0.1099
#> 3  SuicideRisk_Alert SuicideWatch_Alert 1000 470.37  1 2.66e-104    0.6858

For a full-data version, swap morie_sample("otis_b01") for morie_load_dataset("otisb01") and re-run the same callables.

Canadian case law and legislation via A2AJ and CanLII

Access to Algorithmic Justice (A2AJ) publishes Canadian court and tribunal decisions and statutes and regulations as open data, through a REST API and as one Parquet file per court on Hugging Face. The morie_ingest_a2aj_* functions wrap both channels. The chunks in this section need the network and are not run when the vignette is built.

cov <- morie_ingest_a2aj_coverage("cases")
cov[cov$dataset %in% c("SCC", "ONCA", "FC"),
    c("dataset", "earliest_document_date", "number_of_documents")]

Search is full-text or by name, capped at 50 hits per call, and a single decision comes back with its unofficial text:

hits <- morie_ingest_a2aj_search("right to housing",
                                 dataset = c("SCC", "ONCA"),
                                 start_date = "2020-01-01")
doc <- morie_ingest_a2aj_fetch("2023 SCC 17", include_citations = TRUE)
substr(doc$unofficial_text_en, 1, 300)
doc$cases_cited_en[[1]]

For a whole court, load its Parquet file. The native reader decodes the LIST-typed citation columns without arrow; pass columns to skip the full texts, which dominate the file:

sct <- morie_ingest_a2aj_load("SCT",
  columns = c("citation_en", "document_date_en", "cases_cited_en"))
edges <- morie_ingest_a2aj_citation_edges(sct)
head(sort(table(edges$to), decreasing = TRUE))

A2AJ does not yet carry the Human Rights Tribunal of Ontario, the BC Human Rights Tribunal, the Ontario Superior Court, Alberta, Quebec or the remaining provinces (its issues #1, #3 and #4), and individual decisions can be missing (2007 BCSC 1700, issue #2). Those are reachable through CanLII’s API, which needs a free key (CANLII_API_KEY; the chunk stops with a message without one) and returns metadata plus the citation network rather than full text:

morie_ingest_a2aj_gaps()[, c("code", "canlii_database_id")]
hrto <- morie_ingest_canlii_cases("onhrt", result_count = 200,
                                  decision_date_after = "2024-01-01")
ids <- morie_ingest_canlii_case_id("2007 BCSC 1700")
morie_ingest_canlii_case(ids$database_id, ids$case_id)

Every A2AJ record carries an upstream_license field; check it before any commercial use, and treat all texts as unofficial copies.

Caching layout

Data Fetched by Cached in
catalogue datasets (OTIS, PUMFs, …) morie_load_dataset() per-user cache under morie_cache_dir()
TPS layers morie_fetch_tps() cache_dir argument (default under tempdir())
SIU reports morie_fetch_siu() cache_dir argument (default under tempdir())

References

  • OTIS data dictionary – data.ontario.ca/dataset/data-on-inmates-in-ontario
  • Toronto Police Open Data – data.torontopolice.on.ca/
  • SIU Director’s Reports – siu.on.ca/en/case_directors_reports.php
  • Software: citation("rmorie")