1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//! # Return on Investment for Engineering Initiatives
//!
//! **Return on investment (ROI)** turns a delivery or reliability
//! improvement into a financial case decision-makers outside engineering can
//! weigh directly against competing investments. Build the cost side from
//! full total cost of ownership, not just upfront development cost, and
//! build the benefit side from documented, honest outcome evidence rather
//! than an optimistic first-principles guess. Because both sides carry real
//! uncertainty, present ROI as a range — a conservative case and an
//! optimistic case — rather than a single, falsely precise number.
//!
//! ## Formula
//!
//! ```text
//! ROI = (benefit − cost) / cost
//! ROI range = (roi(conservative benefit, cost), roi(optimistic benefit, cost))
//! ```
//!
//! ## Why it matters
//!
//! A single point estimate that turns out to be wrong damages a case's
//! credibility far more than a well-explained range the actual outcome
//! falls within. An analysis process that is genuinely capable of
//! concluding "this is not worth it," and treats that as a legitimate
//! result rather than a failure of the analysis, is what keeps ROI
//! reporting trustworthy over time — an organization known for only ever
//! producing positive ROI cases quickly loses credibility, because
//! stakeholders correctly infer the analysis is not independent of the
//! decision it is meant to inform.
//!
//! ## Example
//!
//! A platform team's change-failure-rate improvement (see
//! [`crate::dora_metrics`]) avoids 17 failed deployments a year, each saving
//! $12,000 in incident cost, for a $204,000 annual benefit against a
//! $150,000 investment.
//!
//! ```rust
//! use software_engineering::return_on_investment::{roi, roi_range};
//!
//! let r = roi(300_000.0, 100_000.0).unwrap();
//! assert_eq!(r, 2.0);
//!
//! let benefit = 204_000.0;
//! let return_on_investment = roi(benefit, 150_000.0).unwrap();
//! assert!((return_on_investment - 0.36).abs() < 1e-9);
//!
//! // Present the same benefit as a conservative-to-optimistic range instead
//! // of one falsely precise number.
//! let (conservative, optimistic) = roi_range(150_000.0, 250_000.0, 150_000.0).unwrap();
//! assert_eq!(conservative, 0.0);
//! assert!((optimistic - (2.0 / 3.0)).abs() < 1e-9);
//! ```
//!
//! ## Money
//!
//! [`roi`] and [`roi_range`] take plain `f64` amounts. For currency-checked
//! accounting, use [`rusty_money::Money`] directly rather than through a
//! wrapper this crate provides — its own `sub` already returns `Result`,
//! rejecting a benefit and cost quoted in different currencies (USD
//! against EUR, say) instead of silently treating them as the same unit,
//! and [`rusty_money::Money::to_f64_lossy`] converts the net benefit and
//! cost into the same plain proportion [`roi`] returns:
//!
//! ```rust
//! use rusty_money::{Money, iso};
//! use software_engineering::return_on_investment::roi;
//!
//! let benefit = Money::from_major(300_000, iso::USD);
//! let cost = Money::from_major(100_000, iso::USD);
//!
//! // rusty_money's own sub() catches a currency mismatch before it ever
//! // reaches roi(), which only ever sees plain, same-unit f64 amounts.
//! let net_benefit = benefit.sub(cost).unwrap();
//! assert_eq!(net_benefit, Money::from_major(200_000, iso::USD));
//!
//! let r = roi(benefit.to_f64_lossy(), cost.to_f64_lossy()).unwrap();
//! assert!((r - 2.0).abs() < 1e-9);
//!
//! // Mismatched currencies are rejected rather than silently subtracted.
//! let eur_cost = Money::from_major(100_000, iso::EUR);
//! assert!(benefit.sub(eur_cost).is_err());
//! ```
//!
//! ## Pitfalls
//!
//! - **Costing only the upfront investment**, omitting ongoing maintenance,
//! infrastructure, and opportunity cost — makes a case look cheaper than
//! its full lifetime cost.
//! - **Inventing a benefit estimate from first principles** rather than
//! grounding it in documented, measured, or comparable historical outcome
//! data.
//! - **Presenting a single point estimate** instead of a range — a falsely
//! precise number that damages credibility when it turns out wrong.
//! - **Never reporting a negative or marginal ROI finding** — a sign the
//! analysis is not actually independent of the decision it informs.
//! - **Never closing the loop** — failing to compare actual outcomes against
//! the projected range after the fact erodes the organization's future
//! forecasting credibility.
//!
//! ## Sources
//!
//! - Chapter 5.5, Return on investment for engineering initiatives.
//!
//! Topic doc: software-engineering-metrics/locales/en-001/chapters/05-05-return-on-investment-for-engineering-initiatives.md
/// Return on investment: net benefit as a proportion of cost.
///
/// `(benefit − cost) / cost`. A result of `2.0` means every $1 invested
/// returns $2 in net profit — a 3x total return.
///
/// # Arguments
///
/// * `benefit` — the total realized or projected benefit, in any currency
/// unit.
/// * `cost` — the total cost, including ongoing total cost of ownership, in
/// the same unit.
///
/// # Returns
///
/// The ROI as a proportion (not a percentage), or `None` if `cost` is zero.
///
/// # Examples
///
/// ```rust
/// use software_engineering::return_on_investment::roi;
///
/// assert_eq!(roi(300_000.0, 100_000.0), Some(2.0));
/// assert_eq!(roi(1.0, 0.0), None);
/// ```
/// ROI expressed as a conservative-to-optimistic range against the same
/// cost, rather than a single, falsely precise number.
///
/// `(roi(conservative_benefit, cost), roi(optimistic_benefit, cost))`.
///
/// # Arguments
///
/// * `conservative_benefit` — the low, conservative-case benefit estimate.
/// * `optimistic_benefit` — the high, optimistic-case benefit estimate.
/// * `cost` — the total cost shared by both cases, in the same unit.
///
/// # Returns
///
/// A `(conservative_roi, optimistic_roi)` pair, or `None` if `cost` is zero.
///
/// # Examples
///
/// ```rust
/// use software_engineering::return_on_investment::roi_range;
///
/// let (conservative, optimistic) = roi_range(150_000.0, 250_000.0, 150_000.0).unwrap();
/// assert_eq!(conservative, 0.0);
/// assert!((optimistic - (2.0 / 3.0)).abs() < 1e-9);
/// assert_eq!(roi_range(1.0, 2.0, 0.0), None);
/// ```