Files
portfolio-advisor/README.md
T
2026-09-28 14:49:02 -04:00

98 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PortfolioAdvisor
Rank a portfolio of capabilities by transition readiness, and say what would
move each one.
PortfolioAdvisor takes a list of scored capabilities and returns them ordered
by the best next move. It is designed to consume
`@extant2000/sustain-score` output, but it takes a plain shape, so any source
of scores can feed it. It is pure, with no clock and no I/O.
## What it is built against
A bad ranking is not the main risk. The risk is a ranking that reads as
authoritative when the numbers behind it are not comparable. There are three
ways that happens.
1. Unassessed capabilities sort as bad. A capability nobody scored lands at
the bottom next to one that was scored and found weak. Those are opposite
situations: one needs work, the other needs an assessment. A single
ordered list cannot hold both, so unassessed capabilities are listed
separately and never interleaved. A `null` score is not zero.
2. A composite hides its binding constraint. "Reports: 6" tells you where
something sits, not what to do. Every entry carries its binding
constraint and next action. An entry with neither is reported as "a
position, not advice".
3. Effort is invisible in a score. Two capabilities at 45, one needing a key
rotation and one needing a rewrite, are not equally worth doing next.
So the ranking is by readiness gained per unit of effort, not by score.
Each entry gets a `leverage` value:
```
(100 - score) × weight ÷ effort
```
That is the headroom left, scaled by how much the capability matters, divided
by what the work costs. Effort weights are `trivial` 1, `small` 2, `medium` 4
and `large` 8. `unknown` counts as `medium`, not `trivial`. An unestimated
task is not a cheap one, and defaulting optimistically is how an unscoped item
reaches the top of a plan. Only a capability scoring 90 or more passes; the
rest are reported as work outstanding.
## Two orders
`formatRanking()` prints the ranked table first and then the findings
worst-first. The two answer different questions ("what should I do next" and
"what is most wrong"), and they are not the same order. A ranking whose
display order contradicts its own numbering is worse than no numbering, so the
table always prints #1 first.
## Usage
```ts
import { rank, rankingTable, formatRanking, type CapabilityStanding } from '@extant2000/portfolio-advisor'
const portfolio: CapabilityStanding[] = [
{ name: 'docs', score: 90, effort: 'small', bindingConstraint: 'none material' },
{ name: 'gateway', score: 85, effort: 'small', bindingConstraint: 'retry fix unmerged', nextAction: 'merge the retry fix' },
{ name: 'reports', score: 60, effort: 'large', bindingConstraint: 'scheduled exports promised, nothing runs them', nextAction: 'build the scheduler' },
{ name: 'billing', score: 80, effort: 'trivial', bindingConstraint: 'debug logging left on', nextAction: 'turn off debug logging' },
]
const { ranked, unassessed } = rank(portfolio)
console.log(rankingTable(portfolio))
// Table plus the evidence-record findings
console.log(formatRanking(portfolio))
```
`toReport(portfolio)` returns a `CapabilityReport` from
`@extant2000/evidence-record`. Each entry can also carry a `weight` (default
1) and a `where` that is cited as the source of the score.
## Sample output
Produced by running `rankingTable()` on the example above:
```
#1 billing 80/100 leverage 20 turn off debug logging
#2 gateway 85/100 leverage 7.5 merge the retry fix
#3 reports 60/100 leverage 5 build the scheduler
#4 docs 90/100 leverage 5 none material
```
The weakest capability is third. Ordering by score alone would have put the
large rebuild ahead of a change that is close to free. A capability with
`score: null` would appear below the table, unnumbered, as unassessed.
## Install
```
npm install @extant2000/portfolio-advisor
```
## License
MIT. Copyright (c) 2026 Extant 2000 LLC. See [LICENSE](LICENSE).