software_engineering/on_call_metrics.rs
1//! # On-Call, Capacity, and Operational Load Metrics
2//!
3//! On-call load often concentrates on a small number of experienced people
4//! who can resolve incidents fastest — the same pattern this book warns
5//! against for code review load and knowledge concentration elsewhere,
6//! applied here to operational burden. A team-wide average page frequency
7//! can hide this concentration entirely; measuring the busiest individual's
8//! share, and how often any one person is on call relative to a sustainable
9//! limit, surfaces the burnout and bus-factor risk a simple average cannot.
10//!
11//! ## Formula
12//!
13//! ```text
14//! Paging concentration (%) = busiest engineer's pages / total pages × 100
15//! On-call frequency ratio = weeks on call / total weeks
16//! Exceeds sustainable frequency when on-call frequency ratio > max ratio
17//! (commonly 0.25, "no more than one week in four")
18//! ```
19//!
20//! ## Why it matters
21//!
22//! Rebalancing rotations deliberately, once concentration appears, depends
23//! on actually measuring individual-level page distribution rather than
24//! only a team-wide average — the average can look entirely reasonable
25//! while two or three people effectively carry the rotation due to skill
26//! gaps or availability constraints. Aggregate this data at the team level
27//! to inform staffing and hiring decisions; never use individual
28//! page-response metrics to evaluate a specific engineer's performance.
29//!
30//! ## Example
31//!
32//! ```rust
33//! use software_engineering::on_call_metrics::{
34//! paging_concentration_percent, on_call_frequency_ratio,
35//! exceeds_sustainable_on_call_frequency,
36//! };
37//!
38//! // Of 40 pages across the team last quarter, the busiest engineer took 22.
39//! let concentration = paging_concentration_percent(22.0, 40.0).unwrap();
40//! assert!((concentration - 55.0).abs() < 1e-9);
41//!
42//! // That same engineer was on call 6 of the last 12 weeks: one week in two,
43//! // well past the "no more than one week in four or five" guideline.
44//! let ratio = on_call_frequency_ratio(6.0, 12.0).unwrap();
45//! assert_eq!(ratio, 0.5);
46//! assert_eq!(exceeds_sustainable_on_call_frequency(6.0, 12.0, 0.25), Some(true));
47//! ```
48//!
49//! ## Pitfalls
50//!
51//! - **Reporting only a team-wide average page frequency** — hides severe
52//! individual concentration that drives both burnout and bus-factor risk.
53//! - **Treating a nominally adequate rotation roster as sufficient** without
54//! checking whether it effectively relies on only two or three people due
55//! to skill gaps or availability constraints.
56//! - **Measuring only active incident time**, ignoring the psychological
57//! cost of being on call even during a shift with zero pages.
58//! - **Using individual page-response metrics to evaluate a specific
59//! engineer** — the goal is sustainable staffing and system design, never
60//! individual scorekeeping.
61//!
62//! ## Sources
63//!
64//! - Chapter 6.3, On-call, capacity, and operational load metrics.
65//!
66//! Topic doc: software-engineering-metrics/locales/en-001/chapters/06-03-on-call-capacity-and-operational-load-metrics.md
67
68/// Paging concentration: how much of the team's total paging load fell on
69/// the single busiest on-call engineer.
70///
71/// `busiest_engineer_pages / total_pages × 100`.
72///
73/// # Arguments
74///
75/// * `busiest_engineer_pages` — count of pages received by the busiest
76/// individual engineer.
77/// * `total_pages` — total count of pages received by the whole team.
78///
79/// # Returns
80///
81/// The concentration as a percentage, or `None` if `total_pages` is zero.
82///
83/// # Examples
84///
85/// ```rust
86/// use software_engineering::on_call_metrics::paging_concentration_percent;
87///
88/// assert!((paging_concentration_percent(22.0, 40.0).unwrap() - 55.0).abs() < 1e-9);
89/// assert_eq!(paging_concentration_percent(1.0, 0.0), None);
90/// ```
91#[must_use]
92pub fn paging_concentration_percent(busiest_engineer_pages: f64, total_pages: f64) -> Option<f64> {
93 if total_pages == 0.0 {
94 return None;
95 }
96 Some((busiest_engineer_pages / total_pages) * 100.0)
97}
98
99/// On-call frequency ratio: the fraction of weeks an engineer spent on
100/// call.
101///
102/// `weeks_on_call / total_weeks`.
103///
104/// # Arguments
105///
106/// * `weeks_on_call` — count of weeks the engineer was on call.
107/// * `total_weeks` — total number of weeks in the observation period.
108///
109/// # Returns
110///
111/// The ratio, or `None` if `total_weeks` is zero.
112///
113/// # Examples
114///
115/// ```rust
116/// use software_engineering::on_call_metrics::on_call_frequency_ratio;
117///
118/// assert_eq!(on_call_frequency_ratio(6.0, 12.0), Some(0.5));
119/// assert_eq!(on_call_frequency_ratio(1.0, 0.0), None);
120/// ```
121#[must_use]
122pub fn on_call_frequency_ratio(weeks_on_call: f64, total_weeks: f64) -> Option<f64> {
123 if total_weeks == 0.0 {
124 return None;
125 }
126 Some(weeks_on_call / total_weeks)
127}
128
129/// Whether an engineer's on-call frequency exceeds a given sustainable
130/// maximum, such as the chapter's example of "no more than one week in four
131/// or five" (a `max_ratio` of `0.25` or `0.20`).
132///
133/// # Arguments
134///
135/// * `weeks_on_call` — count of weeks the engineer was on call.
136/// * `total_weeks` — total number of weeks in the observation period.
137/// * `max_ratio` — the maximum sustainable on-call frequency ratio.
138///
139/// # Returns
140///
141/// `Some(true)` if the engineer's ratio exceeds `max_ratio`, `Some(false)`
142/// otherwise, or `None` if `total_weeks` is zero.
143///
144/// # Examples
145///
146/// ```rust
147/// use software_engineering::on_call_metrics::exceeds_sustainable_on_call_frequency;
148///
149/// // 6 of 12 weeks is one week in two, well past a one-in-four limit.
150/// assert_eq!(exceeds_sustainable_on_call_frequency(6.0, 12.0, 0.25), Some(true));
151/// // 3 of 12 weeks is exactly one week in four: not exceeding it.
152/// assert_eq!(exceeds_sustainable_on_call_frequency(3.0, 12.0, 0.25), Some(false));
153/// ```
154#[must_use]
155pub fn exceeds_sustainable_on_call_frequency(
156 weeks_on_call: f64,
157 total_weeks: f64,
158 max_ratio: f64,
159) -> Option<bool> {
160 let ratio = on_call_frequency_ratio(weeks_on_call, total_weeks)?;
161 Some(ratio > max_ratio)
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 // "Measure how many pages each individual on-call engineer receives,
169 // not just a team-wide average that can hide severe concentration."
170 #[test]
171 fn paging_concentration_computes_share_of_total_pages() {
172 let concentration = paging_concentration_percent(22.0, 40.0).unwrap();
173 assert!((concentration - 55.0).abs() < 1e-6);
174 }
175
176 #[test]
177 fn paging_concentration_is_none_for_zero_total_pages() {
178 assert_eq!(paging_concentration_percent(1.0, 0.0), None);
179 }
180
181 #[test]
182 fn on_call_frequency_ratio_computes_fraction_of_weeks() {
183 let ratio = on_call_frequency_ratio(6.0, 12.0).unwrap();
184 assert!((ratio - 0.5).abs() < 1e-9);
185 }
186
187 #[test]
188 fn on_call_frequency_ratio_is_none_for_zero_total_weeks() {
189 assert_eq!(on_call_frequency_ratio(1.0, 0.0), None);
190 }
191
192 // "Establish a maximum reasonable frequency for how often any
193 // individual should be on call, commonly no more than one week in four
194 // or five."
195 #[test]
196 fn frequency_exceeding_one_week_in_four_is_flagged() {
197 assert_eq!(exceeds_sustainable_on_call_frequency(6.0, 12.0, 0.25), Some(true));
198 }
199
200 #[test]
201 fn frequency_at_exactly_one_week_in_four_does_not_exceed_it() {
202 assert_eq!(exceeds_sustainable_on_call_frequency(3.0, 12.0, 0.25), Some(false));
203 }
204
205 #[test]
206 fn exceeds_sustainable_frequency_is_none_for_zero_total_weeks() {
207 assert_eq!(exceeds_sustainable_on_call_frequency(1.0, 0.0, 0.25), None);
208 }
209}