Skip to main content

software_engineering/
flow_framework.rs

1//! # Flow Framework
2//!
3//! Mik Kersten's Flow Framework (from *Project to Product*) treats software
4//! delivery as a value stream and defines five core measurements: flow
5//! velocity, flow distribution, flow time, flow load, and flow efficiency.
6//! This module implements the arithmetic behind flow time, flow load, and
7//! the Little's law relationship that binds flow load to flow time.
8//!
9//! ## Formula
10//!
11//! ```text
12//! Flow time  = t(delivery) − t(entry)
13//! Flow load  = count of items currently active or waiting in the value stream
14//! Little's law: flow load (WIP) = arrival rate × flow time (cycle time)
15//! Flow efficiency = active time / total elapsed time × 100%
16//!
17//! t(entry)     = when a flow item enters the value stream
18//! t(delivery)  = when a flow item is delivered
19//! arrival rate = new items entering the value stream per unit time
20//! ```
21//!
22//! ## Why it matters
23//!
24//! Flow load does not just correlate with flow time — via Little's law it
25//! mathematically dictates it. If flow load keeps rising while arrival rate
26//! stays flat, flow time is *guaranteed* to rise too. This turns "we're too
27//! overloaded, things are taking too long" from a qualitative complaint into
28//! a provable, quantitative argument a business leader cannot easily
29//! dismiss. Flow efficiency captures a related, and usually surprising,
30//! fact: most software delivery pipelines run between 10% and 25% flow
31//! efficiency, meaning the dominant cost is wait time, not active effort.
32//!
33//! ## Example
34//!
35//! ```rust
36//! use software_engineering::flow_framework::{
37//!     flow_time, littles_law_wip, littles_law_flow_time, flow_efficiency_percent,
38//! };
39//!
40//! // A flow item enters day 0 and is delivered day 12.
41//! let t = flow_time(0.0, 12.0);
42//! assert_eq!(t, 12.0);
43//!
44//! // Little's law: WIP = arrival rate (items/day) × flow time (days).
45//! let wip = littles_law_wip(2.0, t);
46//! assert_eq!(wip, 24.0);
47//!
48//! // Solve the other direction: given WIP and arrival rate, find flow time.
49//! let recovered = littles_law_flow_time(wip, 2.0).unwrap();
50//! assert!((recovered - t).abs() < 1e-9);
51//!
52//! // Ten active hours out of ninety total elapsed hours: 10% flow efficiency.
53//! let efficiency = flow_efficiency_percent(10.0, 100.0).unwrap();
54//! assert!((efficiency - 10.0).abs() < 1e-9);
55//! ```
56//!
57//! ## Pitfalls
58//!
59//! - **Quietly narrowing the flow-time starting point** (e.g. from genuine
60//!   business-need identification to engineering pickup) shrinks flow time
61//!   without improving genuine responsiveness — document the entry point
62//!   explicitly and audit it periodically.
63//! - **Measuring flow load only periodically** forfeits its value as a
64//!   leading indicator; track it continuously.
65//! - **Applying a WIP limit as an individual quota** rather than a system
66//!   constraint misapplies the technique and risks individual gaming.
67//! - **Treating a low flow-efficiency number as a sign of a bad team**: 10%
68//!   to 25% is typical of most delivery pipelines, and is a starting point
69//!   for investigation, not a verdict.
70//!
71//! ## Sources
72//!
73//! - Kersten, Mik. *Project to Product: How to Survive and Thrive in the Age
74//!   of Digital Disruption with the Flow Framework*. IT Revolution Press,
75//!   2018.
76//! - Little, John D. C. "A Proof for the Queuing Formula: L = λW." *Operations
77//!   Research*, 1961.
78//!
79//! Topic doc: 02-01-the-flow-framework.md, 02-03-flow-velocity-and-flow-distribution.md,
80//! 02-04-flow-time-and-flow-load.md, 02-05-flow-efficiency-and-work-in-process.md
81
82/// Flow velocity: count of flow items completed per unit time.
83///
84/// Flow velocity is the Flow Framework's throughput measure (chapter 2.3).
85/// It should always be reported alongside flow distribution — a rising item
86/// count can hide a shift toward rework or item-splitting gaming, so
87/// velocity alone is an incomplete picture.
88///
89/// # Arguments
90///
91/// * `items_completed` — count of flow items completed in the period.
92/// * `period` — length of the observation period (any consistent time unit).
93///
94/// # Returns
95///
96/// `Some(items per unit period)`, or `None` when `period` is zero.
97///
98/// # Examples
99///
100/// ```rust
101/// use software_engineering::flow_framework::flow_velocity;
102///
103/// // 40 items completed in a 4-week period = 10 items/week.
104/// assert_eq!(flow_velocity(40.0, 4.0), Some(10.0));
105/// assert_eq!(flow_velocity(40.0, 0.0), None);
106/// ```
107#[must_use]
108pub fn flow_velocity(items_completed: f64, period: f64) -> Option<f64> {
109    if period == 0.0 {
110        None
111    } else {
112        Some(items_completed / period)
113    }
114}
115
116/// Flow distribution: the percentage share of one flow item type among all
117/// completed items.
118///
119/// Flow distribution (chapter 2.3) answers "what kind of work was it" —
120/// features, defects, risk, or debt. Report it alongside flow velocity,
121/// never alone, so a rising item count that is quietly dominated by defect
122/// rework is visible rather than mistaken for accelerating feature delivery.
123///
124/// # Arguments
125///
126/// * `items_of_type` — count of completed items of one flow item type.
127/// * `total_completed_items` — count of all completed items in the period.
128///
129/// # Returns
130///
131/// `Some(percentage)` (e.g. `45.0` for 45%), or `None` when
132/// `total_completed_items` is zero.
133///
134/// # Examples
135///
136/// ```rust
137/// use software_engineering::flow_framework::flow_distribution_percent;
138///
139/// // The vendor's "features" share fell from 70% to 45% while velocity rose.
140/// assert_eq!(flow_distribution_percent(45.0, 100.0), Some(45.0));
141/// assert_eq!(flow_distribution_percent(1.0, 0.0), None);
142/// ```
143#[must_use]
144pub fn flow_distribution_percent(items_of_type: f64, total_completed_items: f64) -> Option<f64> {
145    if total_completed_items == 0.0 {
146        None
147    } else {
148        Some(items_of_type / total_completed_items * 100.0)
149    }
150}
151
152/// Flow time: total elapsed time from a flow item entering the value stream
153/// to its delivery.
154///
155/// Flow time (chapter 2.4) spans the *whole* value stream, from a business
156/// need being identified to a customer receiving value — broader than
157/// [`crate::cycle_time`], which covers only the engineering stages. It is
158/// conceptually just elapsed time, `delivery − entry`, but the definition of
159/// `entry` must be fixed and documented: quietly narrowing it is this
160/// metric's central gaming risk.
161///
162/// # Arguments
163///
164/// * `entry` — the timestamp (any consistent unit) the item entered the
165///   value stream.
166/// * `delivery` — the timestamp the item was delivered.
167///
168/// # Returns
169///
170/// The elapsed flow time, `delivery − entry`.
171///
172/// # Examples
173///
174/// ```rust
175/// use software_engineering::flow_framework::flow_time;
176///
177/// // Entered on day 3, delivered on day 15: 12 days of flow time.
178/// assert_eq!(flow_time(3.0, 15.0), 12.0);
179/// ```
180#[must_use]
181pub fn flow_time(entry: f64, delivery: f64) -> f64 {
182    delivery - entry
183}
184
185/// Flow load: total count of flow items currently active or waiting in the
186/// value stream.
187///
188/// Flow load (chapter 2.4) is a count, not a computed ratio — it is what
189/// [`crate::code_churn`]-style metrics are to churn, a raw tally that other
190/// formulas (Little's law) then relate to other quantities. This helper
191/// simply sums active and waiting items so the intent is explicit at the
192/// call site.
193///
194/// # Arguments
195///
196/// * `active` — count of items currently being actively worked on.
197/// * `waiting` — count of items currently waiting in a queue.
198///
199/// # Returns
200///
201/// The total flow load, `active + waiting`.
202///
203/// # Examples
204///
205/// ```rust
206/// use software_engineering::flow_framework::flow_load_from_items;
207///
208/// // 5 items actively being worked, 17 waiting: flow load of 22.
209/// assert_eq!(flow_load_from_items(5, 17), 22);
210/// ```
211#[must_use]
212pub fn flow_load_from_items(active: u32, waiting: u32) -> u32 {
213    active + waiting
214}
215
216/// Little's law, solved for flow load (work in process): `WIP = arrival rate
217/// × flow time`.
218///
219/// This is a proof from queueing theory (chapter 2.4, chapter 2.7), not a
220/// heuristic: for any stable value stream, flow load equals arrival rate
221/// multiplied by flow time. It is the single most persuasive tool in this
222/// book for arguing that overloading a value stream provably slows every
223/// item already in it.
224///
225/// # Arguments
226///
227/// * `arrival_rate` — new items entering the value stream per unit time.
228/// * `flow_time` — average time an item spends in the value stream, in the
229///   same time unit as `arrival_rate`'s denominator.
230///
231/// # Returns
232///
233/// The implied flow load (work in process).
234///
235/// # Examples
236///
237/// ```rust
238/// use software_engineering::flow_framework::littles_law_wip;
239///
240/// // Arrival rate of 3 items/day, average flow time of 8 days: WIP = 24.
241/// assert_eq!(littles_law_wip(3.0, 8.0), 24.0);
242/// ```
243#[must_use]
244pub fn littles_law_wip(arrival_rate: f64, flow_time: f64) -> f64 {
245    arrival_rate * flow_time
246}
247
248/// Little's law, solved for flow time: `flow time = WIP / arrival rate`.
249///
250/// The inverse of [`littles_law_wip`], useful for checking whether measured
251/// flow load, arrival rate, and flow time are internally consistent, or for
252/// predicting flow time from a target WIP and known arrival rate.
253///
254/// # Arguments
255///
256/// * `wip` — flow load (work in process).
257/// * `arrival_rate` — new items entering the value stream per unit time.
258///
259/// # Returns
260///
261/// `Some(flow time)`, or `None` when `arrival_rate` is zero.
262///
263/// # Examples
264///
265/// ```rust
266/// use software_engineering::flow_framework::littles_law_flow_time;
267///
268/// // WIP of 24 items at an arrival rate of 3 items/day implies 8 days flow time.
269/// assert_eq!(littles_law_flow_time(24.0, 3.0), Some(8.0));
270/// assert_eq!(littles_law_flow_time(24.0, 0.0), None);
271/// ```
272#[must_use]
273pub fn littles_law_flow_time(wip: f64, arrival_rate: f64) -> Option<f64> {
274    if arrival_rate == 0.0 {
275        None
276    } else {
277        Some(wip / arrival_rate)
278    }
279}
280
281/// Flow efficiency: the percentage of total elapsed time that was active
282/// work.
283///
284/// Most software delivery pipelines, measured honestly, land between 10%
285/// and 25% flow efficiency (chapter 2.5) — wait time, not active effort,
286/// dominates. A low number is typical, not a sign of a broken team; it
287/// redirects attention from "work harder" toward "reduce queueing."
288///
289/// # Arguments
290///
291/// * `active_time` — time actively spent coding, reviewing, or testing.
292/// * `total_elapsed_time` — total time from start to finish, including wait
293///   time, in the same unit as `active_time`.
294///
295/// # Returns
296///
297/// `Some(percentage)` (e.g. `10.0` for 10%), or `None` when
298/// `total_elapsed_time` is zero.
299///
300/// # Examples
301///
302/// ```rust
303/// use software_engineering::flow_framework::flow_efficiency_percent;
304///
305/// // 10 active hours out of 100 total elapsed hours: 10% flow efficiency.
306/// assert_eq!(flow_efficiency_percent(10.0, 100.0), Some(10.0));
307/// assert_eq!(flow_efficiency_percent(10.0, 0.0), None);
308/// ```
309#[must_use]
310pub fn flow_efficiency_percent(active_time: f64, total_elapsed_time: f64) -> Option<f64> {
311    if total_elapsed_time == 0.0 {
312        None
313    } else {
314        Some(active_time / total_elapsed_time * 100.0)
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321
322    // Worked example: "the 'features' share of that rising velocity had
323    // actually fallen from 70% to 45% over the same period".
324    #[test]
325    fn flow_distribution_matches_45_percent_features_share() {
326        assert!((flow_distribution_percent(45.0, 100.0).unwrap() - 45.0).abs() < 1e-9);
327        assert!(flow_distribution_percent(1.0, 0.0).is_none());
328    }
329
330    // Worked example: "flow load equals arrival rate multiplied by flow
331    // time" (Little's law, chapter 2.4).
332    #[test]
333    fn littles_law_wip_equals_arrival_rate_times_flow_time() {
334        assert!((littles_law_wip(3.0, 8.0) - 24.0).abs() < 1e-9);
335        let recovered_flow_time = littles_law_flow_time(24.0, 3.0).unwrap();
336        assert!((recovered_flow_time - 8.0).abs() < 1e-9);
337        assert!(littles_law_flow_time(24.0, 0.0).is_none());
338    }
339
340    // Worked example: "flow efficiency below 25% is typical ... a change
341    // spends ten hours actively being coded ... but sits idle ... across its
342    // whole journey, flow efficiency is 10%" (chapter 2.5).
343    #[test]
344    fn flow_efficiency_of_ten_active_hours_in_a_hundred_is_ten_percent() {
345        let efficiency = flow_efficiency_percent(10.0, 100.0).unwrap();
346        assert!((efficiency - 10.0).abs() < 1e-9);
347        assert!(flow_efficiency_percent(10.0, 0.0).is_none());
348    }
349
350    // Simple elapsed-time and count checks.
351    #[test]
352    fn flow_time_is_delivery_minus_entry() {
353        assert!((flow_time(3.0, 15.0) - 12.0).abs() < 1e-9);
354    }
355
356    #[test]
357    fn flow_load_sums_active_and_waiting() {
358        assert_eq!(flow_load_from_items(5, 17), 22);
359    }
360
361    #[test]
362    fn flow_velocity_divides_items_by_period() {
363        assert_eq!(flow_velocity(40.0, 4.0), Some(10.0));
364        assert_eq!(flow_velocity(40.0, 0.0), None);
365    }
366}