## ----setup, include = FALSE--------------------------------------------------- knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(regulog) ## ----init--------------------------------------------------------------------- log <- regulog_init( app = "primary-analysis", version = "1.0.0", user = "jsmith" # Provide path = "logs/audit.rlog" in production for persistent storage ) log ## ----action-basic------------------------------------------------------------- log_action(log, action = "data_read", object = "adsl.sas7bdat", reason = "Reading subject-level dataset for primary efficacy analysis" ) ## ----action-examples---------------------------------------------------------- # Analytical steps log_action(log, action = "model_fit", object = "primary_ANCOVA", reason = "Fitting ANCOVA: CHG ~ TRT01P + BASE + SITEID per SAP section 6.1" ) # Data exports log_action(log, action = "export", object = "Table14_1.rtf", reason = "Primary efficacy table exported for clinical study report" ) # Review and approval events log_action(log, action = "approved", object = "primary_results_v3", reason = "QC review complete — all outputs match SAP-specified formats" ) # User can override the session user for a single entry log_action(log, action = "co_reviewed", object = "primary_results_v3", reason = "Independent statistical review complete", user = "second.reviewer" ) ## ----change-basic------------------------------------------------------------- log_change(log, object = "alpha", field = "value", before = "0.05", after = "0.025", reason = "Significance level updated per protocol amendment 2 (2026-05-01)" ) ## ----change-examples---------------------------------------------------------- # Data correction log_change(log, object = "subject_01042", field = "ae_onset_date", before = "2026-03-01", after = "2026-03-11", reason = "Transcription error — corrected per source CRF page 47, query Q-0192" ) # Configuration update log_change(log, object = "model_config", field = "covariance_structure", before = "compound_symmetry", after = "unstructured", reason = "Unstructured covariance pre-specified in SAP section 6.1.2" ) # Population definition change log_change(log, object = "analysis_population", field = "SAFFL_definition", before = "RANDFL = 'Y'", after = "RANDFL = 'Y' AND EXOCCUR = 'Y'", reason = "Protocol amendment 3: safety population requires confirmed dosing" ) ## ----note-examples------------------------------------------------------------ # Outlier decision log_note( log, "Outlier identified for subject 01-042 at Week 16 (AVAL = 98.4, upper fence = 62.1). Discussed with medical monitor on 2026-06-20. Retained in primary analysis per SAP section 8.3 — no protocol deviation recorded. Sensitivity analysis without outlier pre-specified in SAP section 10.4." ) # Protocol deviation log_note( log, "Subject 01-007: visit window deviation at Week 8 (visited Day 61, window Day 50-58). Classified as minor deviation per deviation assessment log entry DEV-0031. Subject retained in ITT population." ) # Query resolved log_note( log, "Data query Q-0047 resolved 2026-06-15: lab value for subject 01-019 at Screening confirmed as 4.2 mmol/L per site laboratory report. Original value 42.0 was a decimal error." ) # Analysis assumption documented log_note( log, "Missing baseline value for subject 01-033: LOCF imputation applied per SAP section 7.2 — previous non-missing value (Visit 1) used. Imputed value: 24.6." ) ## ----rl-read, eval = FALSE---------------------------------------------------- # adsl <- rl_read(log, haven::read_sas, "data/adsl.sas7bdat") # adae <- rl_read(log, haven::read_sas, "data/adae.sas7bdat") ## ----rl-read-named, eval = FALSE---------------------------------------------- # adae <- rl_read(log, readr::read_csv, col_types = "ccd", file = "data/adae.csv") ## ----with-log, eval = FALSE--------------------------------------------------- # with_log(log, { # adsl <- read(haven::read_sas, "data/adsl.sas7bdat") # adae <- read(haven::read_sas, "data/adae.sas7bdat") # adlb <- read(haven::read_sas, "data/adlb.sas7bdat") # params <- read(readr::read_csv, "config/parameters.csv") # }) ## ----signature-basic---------------------------------------------------------- log_signature( log, "I certify that this primary analysis is accurate and complete, conducted in accordance with SAP version 2.0 dated 2026-05-01" ) ## ----signature-multiple, eval = FALSE----------------------------------------- # log_signature( # log, # "Statistical analysis complete and accurate per SAP v2.0. # All deviations documented." # ) # # # Second reviewer — create a new log or log against the same path with # # a different session user # log2 <- regulog_init( # app = "primary-analysis", version = "1.0.0", # user = "second.reviewer", # path = "logs/trial001_audit.rlog" # ) # # log_signature( # log2, # "Independent QC review complete. Results independently verified." # ) ## ----verify------------------------------------------------------------------- verify_log(log) ## ----verify-result------------------------------------------------------------ result <- verify_log(log, verbose = FALSE) cat("Intact: ", result$intact, "\n") cat("Entries checked:", result$n_entries, "\n") cat("First broken: ", result$first_broken, "\n") ## ----tamper------------------------------------------------------------------- saved <- log$entries[[2L]]$reason log$entries[[2L]]$reason <- "ALTERED REASON" tamper_result <- suppressWarnings(verify_log(log, verbose = FALSE)) cat("Intact after tamper:", tamper_result$intact, "\n") cat("First broken entry: ", tamper_result$first_broken, "\n") log$entries[[2L]]$reason <- saved # restore ## ----verify-file, eval = FALSE------------------------------------------------ # verify_log("logs/trial001_audit.rlog") ## ----filter-all--------------------------------------------------------------- all_entries <- filter_log(log) all_entries[, c("entry_id", "type", "action", "user", "reason")] ## ----filter-type-------------------------------------------------------------- filter_log(log, type = "SIGNATURE")[, c("type", "user", "reason", "after")] ## ----filter-action------------------------------------------------------------ filter_log(log, action = "approved")[, c("action", "object", "reason")] ## ----filter-user-------------------------------------------------------------- filter_log(log, user = "jsmith")[, c("type", "action", "object")] ## ----filter-date-------------------------------------------------------------- # Entries from today onwards filter_log(log, from = format(Sys.Date(), "%Y-%m-%d"))[, c("type", "action")] # Entries before a cutoff (empty for new log) filter_log(log, to = "2025-12-31") ## ----filter-combined---------------------------------------------------------- filter_log(log, type = c("ACTION", "NOTE"), user = "jsmith", from = "2026-01-01" )[, c("type", "action", "reason")] ## ----filter-file, eval = FALSE------------------------------------------------ # filter_log("logs/trial001_audit.rlog", # type = "SIGNATURE", # user = "jsmith" # ) ## ----as-df-------------------------------------------------------------------- df <- as.data.frame(log) names(df) nrow(df) ## ----export-csv--------------------------------------------------------------- df_export <- export_audit_trail(log, format = "csv", signed = TRUE) df_export[, c("entry_id", "type", "action", "user", "chain_intact", "verified_at")] ## ----export-json, eval = FALSE------------------------------------------------ # # JSON envelope with metadata header # export_audit_trail(log, # format = "json", # signed = TRUE, # path = "outputs/audit_trail.json" # ) # # # CSV for regulatory submission or spreadsheet review # export_audit_trail(log, # format = "csv", # signed = TRUE, # path = "outputs/audit_trail_TRIAL001_PRIMARY.csv" # ) ## ----export-filtered, eval = FALSE-------------------------------------------- # # Only entries from a specific analysis phase # export_audit_trail(log, # format = "csv", # from = "2026-06-01", # to = "2026-06-30", # signed = TRUE, # path = "outputs/audit_june2026.csv" # ) ## ----validation, eval = FALSE------------------------------------------------- # # Phase 1: Installation Qualification (10 tests) # # Verifies R version, package installation, dependency integrity, # # file system access, and namespace exports. # source(system.file("validation/IQ_regulog.R", package = "regulog")) # # # Phase 2: Operational Qualification (26 tests) # # Tests every 21 CFR §11.10 requirement: hash chain integrity, # # tamper detection, user attribution, timestamps, export format, # # electronic signatures, and error isolation. # source(system.file("validation/OQ_regulog.R", package = "regulog")) # # # Phase 3: Performance Qualification (7 tests) # # End-to-end clinical workflows: data review, regulatory export, # # multi-user session independence, 500-entry load test, and # # inspector query simulation. # source(system.file("validation/PQ_regulog.R", package = "regulog")) ## ----capture, eval = FALSE---------------------------------------------------- # sink("IQ_execution_record.txt") # source(system.file("validation/IQ_regulog.R", package = "regulog")) # sink() # # sink("OQ_execution_record.txt") # source(system.file("validation/OQ_regulog.R", package = "regulog")) # sink() # # sink("PQ_execution_record.txt") # source(system.file("validation/PQ_regulog.R", package = "regulog")) # sink() ## ----rtm, eval = FALSE-------------------------------------------------------- # read.csv(system.file("validation/RTM_regulog.csv", package = "regulog")) ## ----self-audit, eval = FALSE------------------------------------------------- # log <- regulog_init( # app = "regulog-qualification", # version = "0.2.0", # user = "val.lead", # path = "qualification/audit_trail.rlog" # ) # # log_action(log, # action = "qualification_start", # object = "regulog 0.2.0", # reason = "IQ/OQ/PQ qualification initiated per SOP-VAL-007" # ) # # source(system.file("validation/IQ_regulog.R", package = "regulog")) # log_action(log, # action = "IQ_complete", # object = "IQ_regulog.R", # reason = "10 tests passed. Proceeding to OQ." # ) # # source(system.file("validation/OQ_regulog.R", package = "regulog")) # log_action(log, # action = "OQ_complete", # object = "OQ_regulog.R", # reason = "26 tests passed. Proceeding to PQ." # ) # # source(system.file("validation/PQ_regulog.R", package = "regulog")) # log_action(log, # action = "PQ_complete", # object = "PQ_regulog.R", # reason = "7 tests passed. Qualification complete." # ) # # log_signature(log, # "I certify that regulog 0.2.0 has been qualified in this environment # per SOP-VAL-007 and is approved for use in regulated R workflows." # ) # # verify_log(log) # export_audit_trail(log, # format = "csv", # signed = TRUE, # path = "qualification/audit_trail_export.csv" # )