3.5 KiB
BuildOrBuy
Compares building a capability against adopting one over a multi-year horizon, and checks the constraints that disqualify an option before it does any arithmetic.
What it does and why
Build-or-buy decisions are usually made as build days against a licence fee. That framing hides the three things that most often decide the answer, so this library models each of them.
A prohibited option is not an expensive one
Suppose a hosted vendor is cheaper, but its terms of service forbid the use
you need. No cost comparison can give the right answer, because that option
was never available. You record the constraint as a blocker. Blockers are
checked before any cost is computed, and a blocked option is reported as
not-applicable. Pricing it would imply it could win.
Self-hosting a third-party tool is a build with a buy label
Running someone else's software yourself is not purchasing it. You take on
backups, upgrades, capacity and on-call, and none of that shows up in a
licence table. An adopt-self-hosted option that has not listed its
operationalSurface is reported as not assessed and left out of the
ranking.
The operating cost dominates, and it is the number nobody fills in
Build cost is paid once and estimated with care. Operating cost is paid
every year, for example the upkeep on an adopted tool or a usage bill that
keeps running after the project that needed it has ended. Costs are totalled
over horizonYears, and operateDaysPerYear: null makes an option
uncostable instead of free. Treating that term as zero is how "adopt" wins
on paper.
A recommendation made while any option is still unestimated is labelled provisional. When most of an option's cost is operating, the finding says so.
A cost win is not the whole decision
The result is a cost comparison, not a verdict. Some reasons to build are not about cost: data custody, keeping a third party out of the critical path, a licence you cannot accept. If such a reason rules an option out, model it as a blocker so the option is removed. If it is a preference, weigh it in the decision made around this output.
Usage
import { compare, formatComparison, type Candidate } from '@extant2000/build-or-buy'
const candidates: Candidate[] = [
{ name: 'in-house build', approach: 'build',
costs: { buildDays: 20, operateDaysPerYear: 2 }, where: 'decision.md' },
{ name: 'hosted service', approach: 'buy-hosted',
costs: { buildDays: 1, operateDaysPerYear: 1, cashPerYear: 600 }, where: 'decision.md' },
{ name: 'self-hosted tool', approach: 'adopt-self-hosted',
costs: { buildDays: 3, operateDaysPerYear: 6 },
operationalSurface: ['backups', 'upgrades', 'capacity', 'on-call'], where: 'decision.md' },
{ name: 'vendor X', approach: 'buy-hosted',
costs: { buildDays: 1, operateDaysPerYear: 0, cashPerYear: 300 },
blockers: [{ reason: 'terms of service prohibit the intended use', where: 'vendor-terms.md' }],
where: 'decision.md' },
]
const cmp = { horizonYears: 3, dayRate: 800 }
const { winner, viable, disqualified, uncostable, reason } = compare(candidates, cmp)
// A report in the @extant2000/evidence-record format, rendered as text.
console.log(formatComparison(candidates, cmp))
totalCost(candidate, cmp) returns the horizon total, or null when an
input is missing. toReport() returns the structured report that
formatComparison() renders. Every finding cites the where of its
candidate or blocker.
Install
npm install @extant2000/build-or-buy
License
MIT. See LICENSE.