Buffer Zone Overview — Net Flow Position by SKU
R Code
library(ggplot2)
library(dplyr)
library(tidyr)
skus <- data.frame(
sku = c("PCB-100","DSP-200","BAT-300","HSG-400","CAM-500","CHG-600","SPK-700","ANT-800"),
adu = c(45, 40, 60, 50, 35, 55, 42, 38),
dlt = c(12, 18, 8, 10, 20, 6, 9, 15),
ltf = c(0.35, 0.40, 0.30, 0.30, 0.45, 0.25, 0.30, 0.40),
vf = c(0.50, 0.60, 0.40, 0.35, 0.55, 0.30, 0.40, 0.55),
moq = c(200, 150, 500, 300, 100, 1000, 200, 100),
oh = c(380, 120, 850, 520, 95, 1200, 280, 60),
oo = c(500, 400, 0, 300, 350, 0, 200, 0),
qd = c(210, 195, 180, 240, 175, 165, 130, 190)
)
skus <- skus %>% mutate(
red_base = adu * dlt * ltf * vf,
red_safety = red_base * ltf,
total_red = red_base + red_safety,
yellow = adu * dlt,
green = pmax(adu * dlt * ltf, moq),
tog = total_red + yellow + green,
toy = total_red + yellow,
tor = total_red,
nfp = oh + oo - qd,
status = case_when(nfp <= tor ~ "Red", nfp <= toy ~ "Yellow", TRUE ~ "Green")
)
zones <- skus %>%
select(sku, total_red, yellow, green, nfp, oh) %>%
pivot_longer(cols = c(total_red, yellow, green),
names_to = "zone", values_to = "value") %>%
mutate(zone = factor(zone, levels = c("green","yellow","total_red")))
ggplot(zones, aes(x = sku, y = value, fill = zone)) +
geom_col(position = "stack", width = 0.6) +
geom_point(data = skus, aes(x = sku, y = nfp, fill = NULL),
shape = 23, size = 3, fill = "black") +
scale_fill_manual(values = c(
total_red = "#E74C3C", yellow = "#F0AD4E", green = "#2ECC71"
)) +
labs(title = "Buffer Zone Overview", y = "Units") +
theme_minimal() +
theme(panel.grid.major.x = element_blank(),
legend.position = "top")
Buffer Detail — Zone Calculations & Replenishment Signals
R Code
library(kableExtra)
skus %>%
mutate(
order_qty = ifelse(nfp < toy, ceiling(tog - nfp), 0),
pct_of_tog = round(nfp / tog * 100, 1)
) %>%
select(sku, adu, dlt, red_base, red_safety, total_red,
yellow, green, tog, toy, tor, oh, oo, qd, nfp,
status, order_qty) %>%
kbl(col.names = c("SKU","ADU","DLT","Red Base","Red Safety",
"Total Red","Yellow","Green","TOG","TOY",
"TOR","On-Hand","On-Order","Qual.Dem",
"NFP","Status","Order Qty"),
digits = 0) %>%
kable_styling(bootstrap_options = c("striped","condensed")) %>%
row_spec(which(skus$status == "Red"), background = "#FDF2F0") %>%
row_spec(which(skus$status == "Yellow"), background = "#FFF9F0")
Dynamic Buffer Adjustment — Normal vs. Seasonal Peak (DAF = 1.4)
R Code
library(ggplot2)
library(patchwork)
daf <- 1.4
selected <- skus %>% filter(sku %in% c("PCB-100","DSP-200","CAM-500"))
selected_peak <- selected %>% mutate(
adu_peak = adu * daf,
red_base_p = adu_peak * dlt * ltf * vf,
red_safety_p = red_base_p * ltf,
total_red_p = red_base_p + red_safety_p,
yellow_p = adu_peak * dlt,
green_p = pmax(adu_peak * dlt * ltf, moq),
tog_p = total_red_p + yellow_p + green_p
)
bind_normal <- selected %>%
select(sku, total_red, yellow, green) %>%
mutate(scenario = "Normal")
bind_peak <- selected_peak %>%
transmute(sku, total_red = total_red_p,
yellow = yellow_p, green = green_p,
scenario = "Peak (DAF=1.4)")
combined <- bind_rows(bind_normal, bind_peak)
ggplot(combined %>% pivot_longer(total_red:green),
aes(x = scenario, y = value, fill = name)) +
geom_col(width = 0.5) +
facet_wrap(~sku, scales = "free_y") +
scale_fill_manual(values = c(
total_red="#E74C3C", yellow="#F0AD4E", green="#2ECC71")) +
theme_minimal()
Net Flow Position Timeline — PCB-100 Main Circuit Board (30 Days)
R Code
library(ggplot2)
set.seed(42)
days <- 1:30
nfp_series <- numeric(30)
oh <- 670
tor <- 127.6; toy <- 667.6; tog <- 867.6
orders_placed <- c()
orders_arrive <- c()
for (d in days) {
consumption <- rpois(1, 45)
arrival <- ifelse(d %in% orders_arrive,
tog - oh, 0)
oh <- oh - consumption + arrival
if (oh < toy) {
orders_placed <- c(orders_placed, d)
orders_arrive <- c(orders_arrive, d + 12)
}
nfp_series[d] <- oh
}
df <- data.frame(day = days, nfp = nfp_series)
ggplot(df, aes(x = day, y = nfp)) +
annotate("rect", xmin=0, xmax=31, ymin=0,
ymax=tor, fill="#E74C3C", alpha=0.2) +
annotate("rect", xmin=0, xmax=31, ymin=tor,
ymax=toy, fill="#F0AD4E", alpha=0.2) +
annotate("rect", xmin=0, xmax=31, ymin=toy,
ymax=tog, fill="#2ECC71", alpha=0.15) +
geom_line(color = "#2C3E50", size = 0.8) +
geom_hline(yintercept = c(tor, toy, tog),
linetype = "dashed", color = "#666") +
geom_point(data = df[df$day %in% orders_placed,],
shape = 24, size = 3, fill = "#C0392B") +
theme_minimal() +
labs(title = "PCB-100 NFP Over 30 Days",
x = "Day", y = "Net Flow Position")
Buffer Penetration Heat Map — Last 4 Weeks
R Code
library(ggplot2)
library(reshape2)
pen_data <- data.frame(
sku = rep(skus$sku, each = 4),
week = rep(paste("Wk", 1:4), 8),
penetration = c(
18, 22, 20, 23,
55, 62, 68, 74,
28, 30, 35, 36,
25, 28, 30, 33,
60, 65, 72, 79,
15, 18, 20, 24,
38, 42, 44, 45,
72, 78, 88, 113
)
)
pen_data$zone <- cut(pen_data$penetration,
breaks = c(-Inf, 33, 66, Inf),
labels = c("Green", "Yellow", "Red"))
ggplot(pen_data, aes(x = week, y = sku, fill = zone)) +
geom_tile(color = "white", size = 1) +
geom_text(aes(label = paste0(penetration, "%")),
size = 3.5) +
scale_fill_manual(values = c(
Green="#D5EDDB", Yellow="#FFF0CC", Red="#F8D7DA")) +
theme_minimal() +
labs(title = "Buffer Penetration Heatmap")
Replenishment Signal Board — Prioritized Actions
R Code
signals <- skus %>%
mutate(
order_qty = ifelse(nfp < toy, ceiling(tog - nfp), 0),
priority = case_when(
nfp <= tor ~ "CRITICAL",
nfp <= toy ~ "WARNING",
TRUE ~ "OK"
),
delivery_date = Sys.Date() + dlt
) %>%
filter(order_qty > 0) %>%
arrange(desc(priority == "CRITICAL"), order_qty)
signals %>%
select(sku, priority, order_qty, nfp, tog,
delivery_date) %>%
kbl() %>%
kable_styling()
How DDMRP Buffers Work
Demand Driven MRP decouples the supply chain by placing strategic
inventory buffers at key positions. Unlike traditional MRP which pushes forecasted demand
through every level of the BOM, DDMRP uses actual demand signals to drive replenishment.
The buffer is divided into three zones. The Green Zone controls order
frequency -- a wider green zone means less frequent but larger orders. The Yellow Zone
represents the normal demand coverage during lead time. The Red Zone is
safety stock that absorbs variability in both demand and supply.
When the Net Flow Position (on-hand + on-order - qualified demand) drops
below the Top of Yellow, a replenishment order is generated to bring the
NFP back to the Top of Green. This creates a natural pull-based
replenishment system that responds to actual consumption.
R Code
library(ggplot2)
zones <- data.frame(
zone = factor(c("Red Base","Red Safety","Yellow","Green"),
levels = c("Red Base","Red Safety","Yellow","Green")),
value = c(95, 33, 540, 200)
)
ggplot(zones, aes(x = "Buffer", y = value, fill = zone)) +
geom_col(width = 0.4) +
scale_fill_manual(values = c(
"Red Base" = "#C0392B",
"Red Safety" = "#E74C3C",
"Yellow" = "#F0AD4E",
"Green" = "#2ECC71"
)) +
geom_hline(yintercept = cumsum(zones$value)[c(2,3,4)],
linetype = "dashed") +
annotate("text", x = 1.4,
y = cumsum(zones$value)[c(2,3,4)],
label = c("TOR","TOY (Order Point)","TOG (Order-Up-To)"),
hjust = 0, size = 3.5) +
coord_flip() +
theme_minimal() +
labs(title = "DDMRP Buffer Structure",
y = "Units")