Based on this week’s model, and the Markov simulation website, create a model in Simio to represent 4 possible states of a process. Impersonate the objects used in the model to resemble the process illustrated with the help of the symbols. Bring a small narrative of the process.
In this assignment I will model lifecycle of a medical claim at health insurance plan
Logic
A claim enters Intake and may go straight to Financial Review - if claim is clean or Clinical Review when documentation and policy verification is needed. Either review can bounce the claim back to Intake ( in case if medical information is missing or incorrect) or hand it off to the other review. Once criteria and pricing clear, the claim moves to Resolved stage.
KPI from the model: probability a claim resolves without
ever needing clinical review, expected # of touchpoints before the
resolution, share of “rework” loops back to Intake stage, and
distribution of time- to -resolution.
set.seed(42)
states <- c("Intake", "ClinicalReview", "FinancialReview", "Resolved")
P <- matrix(
c(
# From Intake:
# to Intake, Clin, Fin, Resolved
0.05, 0.70, 0.10, 0.15,
# From ClinicalReview:
0.15, 0.10, 0.60, 0.15,
# From FinancialReview:
0.10, 0.10, 0.10, 0.70,
# From Resolved (absorbing):
0.00, 0.00, 0.00, 1.00
),
nrow = 4, byrow = TRUE,
dimnames = list(from = states, to = states)
)
P<-as.matrix(P)
mc <- new("markovchain", states = states, transitionMatrix = P, name = "ClaimsMC")
mc
## ClaimsMC
## A 4 - dimensional discrete Markov Chain defined by the following states:
## Intake, ClinicalReview, FinancialReview, Resolved
## The transition matrix (by rows) is defined as follows:
## to
## from Intake ClinicalReview FinancialReview Resolved
## Intake 0.05 0.7 0.1 0.15
## ClinicalReview 0.15 0.1 0.6 0.15
## FinancialReview 0.10 0.1 0.1 0.70
## Resolved 0.00 0.0 0.0 1.00
absorbingIdx <- which(states == "Resolved")
transientIdx <- setdiff(seq_along(states), absorbingIdx)
Q <- P[transientIdx, transientIdx, drop = FALSE]
R <- P[transientIdx, absorbingIdx, drop = FALSE ]
I3 <- diag(nrow(Q))
Nmat <- solve(I3 - Q) # Fundamental matrix: expected # of visits to transient states
t_steps <- as.vector(Nmat %*% rep(1, nrow(Q))) # Expected number of steps to absorption starting from each transient state
names(t_steps) <- states[transientIdx]
# Absorption probabilities (from each transient state to Resolved)
B <- Nmat %*% R
rownames(B) <- states[transientIdx]
colnames(B) <- "Pr(Resolved)"
cat("\nExpected steps to resolution (by starting state):\n")
##
## Expected steps to resolution (by starting state):
print(round(t_steps, 2))
## Intake ClinicalReview FinancialReview
## 3.36 2.87 1.80
cat("\nProbability the claim eventually resolves (it should be 1):\n")
##
## Probability the claim eventually resolves (it should be 1):
print(round(B, 4))
## to
## Pr(Resolved)
## Intake 1
## ClinicalReview 1
## FinancialReview 1
simulate_claim <- function(P, start = "Intake", states) {
cur <- start
path <- cur
while (cur != "Resolved") {
cur <- sample(states, size = 1, prob = P[cur, ])
path <- c(path, cur)
# Safety net in case of a bad matrix
if (length(path) > 1000) break
}
path
}
nClaims <- 10000
paths <- vector("list", nClaims)
for (i in seq_len(nClaims)) {
paths[[i]] <- simulate_claim(P = P, start = "Intake", states = states)
}
steps_to_resolve <- sapply(paths, function(p) length(p) - 1) # transitions taken
ever_clinical <- sapply(paths, function(p) any(p == "ClinicalReview"))
ever_financial <- sapply(paths, function(p) any(p == "FinancialReview"))
loops_back_to_intake <- sapply(paths, function(p) {
# Count revisits to Intake after the first element
sum(p[-1] == "Intake") > 0
})
kpi <- list(
avg_steps_to_resolution = mean(steps_to_resolve),
median_steps_to_resolution = median(steps_to_resolve),
pct_without_clinical = mean(!ever_clinical),
pct_needing_clinical = mean(ever_clinical),
pct_needing_financial = mean(ever_financial),
pct_rework_back_to_intake = mean(loops_back_to_intake)
)
cat("\n--- Simulated KPIs (n =", nClaims, ") ---\n")
##
## --- Simulated KPIs (n = 10000 ) ---
print(lapply(kpi, function(x) if(is.numeric(x)) round(x, 3) else x))
## $avg_steps_to_resolution
## [1] 3.355
##
## $median_steps_to_resolution
## [1] 3
##
## $pct_without_clinical
## [1] 0.244
##
## $pct_needing_clinical
## [1] 0.756
##
## $pct_needing_financial
## [1] 0.681
##
## $pct_rework_back_to_intake
## [1] 0.246
# Distribution of steps
dist_steps <- table(steps_to_resolve) / nClaims
cat("\nDistribution of steps to resolution:\n")
##
## Distribution of steps to resolution:
print(round(dist_steps, 3))
## steps_to_resolve
## 1 2 3 4 5 6 7 8 9 10 11 12 13
## 0.156 0.174 0.339 0.120 0.087 0.050 0.030 0.019 0.009 0.006 0.003 0.002 0.002
## 14 15 16 17 18
## 0.000 0.001 0.000 0.000 0.000
P_fast_fin <- P
P_fast_fin["FinancialReview", ] <- c(0.05, 0.05, 0.10, 0.80) # +10pp to Resolved, lower bounce-backs
row_sums <- rowSums(P_fast_fin)
stopifnot(all(abs(row_sums - 1) < 1e-8))
mc_fast <- new("markovchain", states = states, transitionMatrix = P_fast_fin)
Q_base <- P[transientIdx, transientIdx, drop = FALSE]
Q_fast <- P_fast_fin[transientIdx, transientIdx, drop = FALSE]
t_base <- as.vector(solve(diag(nrow(Q_base)) - Q_base) %*% rep(1, nrow(Q_base)))[1]
t_fast <- as.vector(solve(diag(nrow(Q_fast)) - Q_fast) %*% rep(1, nrow(Q_fast)))[1]
cat("\nExpected steps from Intake (base vs faster FinancialReview): ",
round(t_base, 2), "vs", round(t_fast, 2), "\n")
##
## Expected steps from Intake (base vs faster FinancialReview): 3.36 vs 3.1
percent <- function(x) sprintf("%.1f%%", 100 * x)
cat("\nTransition Matrix (%):\n")
##
## Transition Matrix (%):
print(apply(P, 2, percent))
## to
## Intake ClinicalReview FinancialReview Resolved
## [1,] "5.0%" "70.0%" "10.0%" "15.0%"
## [2,] "15.0%" "10.0%" "60.0%" "15.0%"
## [3,] "10.0%" "10.0%" "10.0%" "70.0%"
## [4,] "0.0%" "0.0%" "0.0%" "100.0%"
In this assignment I modeled the medical claims lifecycle as a 4-state absorbing Markov chain (Intake → ClinicalReview → FinancialReview → Resolved). The model estimates operational efficiency (steps to resolution) and rework (loops back to Intake). Results indicate the average claim takes ~3.36 steps to reach Resolved, with 76% passing through Clinical Review and 68% through Financial Review. The absorbing analysis confirms Pr(Resolved) = 1 for all transient starting states, i.e., process stability.
“Every rework loop in the claims process translates to time, cost, and provider dissatisfaction — Markov modeling helps identify exactly where inefficiencies occur and how they can impact company’s financial performance.”
| KPI | Result | Interpretation |
|---|---|---|
| Avg steps to resolution | 3.36 | Touchpoints per claim (lower is better) |
| Median steps to resolution | 3 | Typical path length |
| % without clinical | 24% | Straight-through clean claims |
| % needing clinical | 76% | Share needing medical policy review |
| % needing financial | 68% | Share needing pricing/COB/edits |
| % rework back to Intake | 25% | Rework/returns to Intake (documentation/edits) |