# 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 ```ts 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](LICENSE).