1
0
mirror of https://github.com/msberends/AMR.git synced 2026-05-14 02:30:45 +02:00

Generalise interpretive rules for multi-guideline support (#268) (#283)

* Generalise interpretive rules for multi-guideline support (#268)

- Rename data-raw/eucast_rules.tsv → interpretive_rules.tsv; add rule.provider
  column (value: "EUCAST") to distinguish future CLSI rows
- Rename EUCAST_RULES_DF → INTERPRETIVE_RULES_DF in _pre_commit_checks.R;
  filter by rule.provider == guideline when applying rules in interpretive_rules()
- Rename custom_eucast_rules() → custom_interpretive_rules() with new S3 class
  "custom_interpretive_rules"; old function becomes a deprecated wrapper in
  zz_deprecated.R; backward-compat S3 dispatch shims added for old class
- Remove stop_if(guideline == "CLSI", ...) so clsi_rules() no longer errors
- Add .onLoad shim in zzz.R to create INTERPRETIVE_RULES_DF from EUCAST_RULES_DF
  for transitional compatibility until sysdata.rda is regenerated

https://claude.ai/code/session_01D46BTsfJSPo3HnLWp3PRkP

* Fix namespace load failure: remove assignInNamespace from .onLoad (#268)

assignInNamespace cannot add NEW bindings to a locked package namespace
(R locks namespace bindings before .onLoad runs). Replace the .onLoad
shim with a runtime fallback inside interpretive_rules(): if
INTERPRETIVE_RULES_DF is absent (pre-regeneration sysdata.rda), derive
it from EUCAST_RULES_DF by adding the rule.provider column. This also
fixes the screening_abx line to reuse the already-resolved
interpretive_rules_df_total instead of a bare INTERPRETIVE_RULES_DF
reference.

https://claude.ai/code/session_01D46BTsfJSPo3HnLWp3PRkP

* fixes

* fixes

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Matthijs Berends
2026-05-01 18:38:51 +01:00
committed by GitHub
parent f7e9294bea
commit 24f24ecaf8
26 changed files with 1770 additions and 183 deletions

View File

@@ -32,4 +32,11 @@ test_that("test-_deprecated.R", {
expect_warning(example_isolates[, ab_class("mycobact")])
expect_warning(example_isolates[, ab_selector(name %like% "trim")])
# deprecated custom_interpretive_rules() still works and emits a warning
expect_warning(
x_old <- custom_eucast_rules(AMC == "R" ~ aminopenicillins == "R"),
regexp = "custom_eucast_rules"
)
expect_inherits(x_old, "custom_interpretive_rules")
})

View File

@@ -53,12 +53,12 @@ test_that("test-data.R", {
expect_false(anyNA(microorganisms.codes$mo))
expect_true(all(dosage$ab %in% AMR::antimicrobials$ab))
expect_true(all(dosage$name %in% AMR::antimicrobials$name))
eucast_abx <- AMR:::EUCAST_RULES_DF$and_these_antibiotics
eucast_abx <- unique(unlist(strsplit(eucast_abx[!is.na(eucast_abx)], ", +")))
expect_true(all(eucast_abx %in% AMR::antimicrobials$ab),
interpretive_abx <- AMR:::INTERPRETIVE_RULES_DF$and_these_antibiotics
interpretive_abx <- unique(unlist(strsplit(interpretive_abx[!is.na(interpretive_abx)], ", +")))
expect_true(all(interpretive_abx %in% AMR::antimicrobials$ab),
info = paste0(
"Missing in `antimicrobials` data set: ",
toString(eucast_abx[which(!eucast_abx %in% AMR::antimicrobials$ab)])
toString(interpretive_abx[which(!interpretive_abx %in% AMR::antimicrobials$ab)])
)
)

View File

@@ -27,13 +27,14 @@
# how to conduct AMR data analysis: https://amr-for-r.org #
# ==================================================================== #
test_that("test-eucast_rules.R", {
test_that("test-interpretive_rules.R", {
skip_on_cran()
# thoroughly check input table
expect_equal(
sort(colnames(AMR:::EUCAST_RULES_DF)),
sort(colnames(AMR:::INTERPRETIVE_RULES_DF)),
sort(c(
"rule.provider",
"if_mo_property", "like.is.one_of", "this_value",
"and_these_antibiotics", "have_these_values",
"then_change_these_antibiotics", "to_value",
@@ -42,7 +43,7 @@ test_that("test-eucast_rules.R", {
"note"
))
)
MOs_mentioned <- unique(AMR:::EUCAST_RULES_DF$this_value)
MOs_mentioned <- unique(AMR:::INTERPRETIVE_RULES_DF$this_value)
MOs_mentioned <- sort(trimws(unlist(strsplit(MOs_mentioned[!AMR:::is_valid_regex(MOs_mentioned)], ",", fixed = TRUE))))
MOs_test <- suppressWarnings(
trimws(paste(
@@ -54,19 +55,19 @@ test_that("test-eucast_rules.R", {
MOs_test[MOs_test == ""] <- mo_fullname(MOs_mentioned[MOs_test == ""], keep_synonyms = TRUE, language = NULL)
expect_equal(MOs_mentioned, MOs_test)
expect_error(suppressWarnings(eucast_rules(example_isolates, col_mo = "Non-existing")))
expect_error(eucast_rules(x = "text"))
expect_error(eucast_rules(data.frame(a = "test")))
expect_error(eucast_rules(data.frame(mo = "test"), rules = "invalid rules set"))
expect_error(suppressWarnings(interpretive_rules(example_isolates, col_mo = "Non-existing")))
expect_error(interpretive_rules(x = "text"))
expect_error(interpretive_rules(data.frame(a = "test")))
expect_error(interpretive_rules(data.frame(mo = "test"), rules = "invalid rules set"))
# expect_warning(eucast_rules(data.frame(mo = "Escherichia coli", vancomycin = "S", stringsAsFactors = TRUE)))
# expect_warning(interpretive_rules(data.frame(mo = "Escherichia coli", vancomycin = "S", stringsAsFactors = TRUE)))
expect_identical(
colnames(example_isolates),
colnames(suppressWarnings(eucast_rules(example_isolates, info = FALSE)))
colnames(suppressWarnings(interpretive_rules(example_isolates, info = FALSE)))
)
expect_output(suppressMessages(eucast_rules(example_isolates, info = TRUE)))
expect_output(suppressMessages(interpretive_rules(example_isolates, info = TRUE)))
a <- data.frame(
mo = c(
@@ -86,8 +87,8 @@ test_that("test-eucast_rules.R", {
amox = "R", # Amoxicillin
stringsAsFactors = FALSE
)
expect_identical(suppressWarnings(eucast_rules(a, "mo", info = FALSE)), b)
expect_output(suppressMessages(suppressWarnings(eucast_rules(a, "mo", info = TRUE))))
expect_identical(suppressWarnings(interpretive_rules(a, "mo", info = FALSE)), b)
expect_output(suppressMessages(suppressWarnings(interpretive_rules(a, "mo", info = TRUE))))
a <- data.frame(
mo = c(
@@ -105,7 +106,7 @@ test_that("test-eucast_rules.R", {
COL = "R", # Colistin
stringsAsFactors = FALSE
)
expect_equal(suppressWarnings(eucast_rules(a, "mo", info = FALSE)), b)
expect_equal(suppressWarnings(interpretive_rules(a, "mo", info = FALSE)), b)
# piperacillin must be R in Enterobacteriaceae when tica is R
if (AMR:::pkg_is_available("dplyr", min_version = "1.0.0", also_load = TRUE)) {
@@ -117,7 +118,7 @@ test_that("test-eucast_rules.R", {
TIC = as.sir("R"),
PIP = as.sir("S")
) %>%
eucast_rules(col_mo = "mo", version_expertrules = 3.1, rules = "expert", info = FALSE, overwrite = TRUE) %>%
interpretive_rules(col_mo = "mo", version_expertrules = 3.1, rules = "expert", info = FALSE, overwrite = TRUE) %>%
pull(PIP) %>%
unique() %>%
as.character()
@@ -127,7 +128,7 @@ test_that("test-eucast_rules.R", {
}
# azithromycin and clarythromycin must be equal to Erythromycin
a <- suppressWarnings(as.sir(eucast_rules(
a <- suppressWarnings(as.sir(interpretive_rules(
data.frame(
mo = example_isolates$mo,
ERY = example_isolates$ERY,
@@ -149,7 +150,7 @@ test_that("test-eucast_rules.R", {
# amox is inferred by benzylpenicillin in Kingella kingae
expect_equal(
suppressWarnings(
as.list(eucast_rules(
as.list(interpretive_rules(
data.frame(
mo = as.mo("Kingella kingae"),
PEN = "S",
@@ -164,16 +165,16 @@ test_that("test-eucast_rules.R", {
# also test norf
if (AMR:::pkg_is_available("dplyr", min_version = "1.0.0", also_load = TRUE)) {
expect_output(suppressWarnings(eucast_rules(example_isolates %>% mutate(NOR = "S", NAL = "S"), info = TRUE)))
expect_output(suppressWarnings(interpretive_rules(example_isolates %>% mutate(NOR = "S", NAL = "S"), info = TRUE)))
}
# check verbose output
expect_output(suppressWarnings(eucast_rules(example_isolates, verbose = TRUE, rules = "all", info = TRUE)))
expect_output(suppressWarnings(interpretive_rules(example_isolates, verbose = TRUE, rules = "all", info = TRUE)))
# AmpC de-repressed cephalo mutants
expect_identical(
eucast_rules(
interpretive_rules(
data.frame(
mo = c("Escherichia coli", "Enterobacter cloacae"),
cefotax = as.sir(c("S", "S"))
@@ -187,7 +188,7 @@ test_that("test-eucast_rules.R", {
)
expect_identical(
eucast_rules(
interpretive_rules(
data.frame(
mo = c("Escherichia coli", "Enterobacter cloacae"),
cefotax = as.sir(c("S", "S"))
@@ -201,7 +202,7 @@ test_that("test-eucast_rules.R", {
)
expect_identical(
eucast_rules(
interpretive_rules(
data.frame(
mo = c("Escherichia coli", "Enterobacter cloacae"),
cefotax = as.sir(c("S", "S"))
@@ -219,7 +220,7 @@ test_that("test-eucast_rules.R", {
expect_inherits(eucast_dosage(c("tobra", "genta", "cipro")), "data.frame")
x <- custom_eucast_rules(
x <- custom_interpretive_rules(
AMC == "R" & genus == "Klebsiella" ~ aminopenicillins == "R",
AMC == "I" & genus == "Klebsiella" ~ aminopenicillins == "I",
AMX == "S" ~ AMC == "S"
@@ -230,7 +231,7 @@ test_that("test-eucast_rules.R", {
# this custom rules makes 8 changes
expect_equal(
nrow(eucast_rules(example_isolates,
nrow(interpretive_rules(example_isolates,
rules = "custom",
custom_rules = x,
info = FALSE,
@@ -240,4 +241,10 @@ test_that("test-eucast_rules.R", {
8,
tolerance = 0.5
)
# clsi_rules() no longer errors (returns data unchanged until CLSI rows are added)
expect_identical(
suppressWarnings(clsi_rules(example_isolates, info = FALSE)),
example_isolates
)
})