Created
August 17, 2025 11:56
-
-
Save FrankRuns/13c76f300f15da7d74b3d60d7b7c0d5d to your computer and use it in GitHub Desktop.
risk_complacency_model.R
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ############################################################### | |
| # TUTORIAL: The “complacency model” for safety incidents | |
| # | |
| # Audience: Curious operators and analysts. No math background required. | |
| # | |
| # Big idea in plain English: | |
| # - Instead of assuming risk is constant, let’s assume it RISES | |
| # the longer we go without an incident or intervention. | |
| # - Think of it like tension in a spring. The longer it goes untouched, | |
| # the more tightly wound it gets. Eventually something snaps. | |
| # - This better reflects human systems, where people become complacent | |
| # after long clean streaks. | |
| # | |
| # This code shows: | |
| # - How to model risk as "baseline + drift per week without incident" | |
| # - How that risk resets after a safety drill, training, or actual incident | |
| # - How it looks on a chart (the sawtooth pattern) | |
| ############################################################### | |
| # --------------------------- | |
| # 1) Define the complacency model | |
| # --------------------------- | |
| # Baseline risk (probability of incident the first week after training) | |
| baseline <- 0.002 # ~0.2% chance in week 1 | |
| # Drift per week (how much risk creeps up each week with no incident) | |
| drift <- 0.0005 # adds 0.05% risk per week | |
| # Number of weeks to simulate | |
| T <- 30 | |
| # Vector for risk each week | |
| weeks <- 1:T | |
| # Age since last reset (like "days since last training"). | |
| # For simplicity, assume no incident actually occurs, so age just increases. | |
| age <- weeks - 1 # starts at 0 in week 1 | |
| # Risk function: probability this week = 1 - exp(-(baseline + drift*age)) | |
| # (This is just a smooth way to map "baseline + drift" into a probability). | |
| lambda <- baseline + drift * age | |
| risk_this_week <- 1 - exp(-lambda) | |
| # --------------------------- | |
| # 2) Simulate a reset | |
| # --------------------------- | |
| # Imagine we do a safety training on week 15 that resets risk back to baseline. | |
| risk_reset <- risk_this_week | |
| risk_reset[weeks >= 15] <- 1 - exp(-(baseline + drift * (weeks[weeks >= 15] - 15))) | |
| # --------------------------- | |
| # 3) Bundle into a table | |
| # --------------------------- | |
| out <- data.frame( | |
| week = weeks, | |
| complacency_risk = round(risk_reset, 4) | |
| ) | |
| print(out, row.names = FALSE) | |
| # --------------------------- | |
| # 4) Visualize the sawtooth | |
| # --------------------------- | |
| # install.packages("ggplot2") # if not already installed | |
| library(ggplot2) | |
| ggplot(out, aes(week, complacency_risk)) + | |
| geom_line(color = "red", size = 1.2) + | |
| geom_vline(xintercept = 15, linetype = "dashed") + | |
| labs( | |
| x = "Weeks since start", | |
| y = "Probability of an incident this week", | |
| title = "Complacency Model: Risk creeps upward, then resets after incident / training" | |
| ) + | |
| annotate("text", x = 15, y = max(out$complacency_risk) - 0.001, vjust = -1, | |
| label = "Training / reset at week 15") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment