98 lines
3.9 KiB
Markdown
98 lines
3.9 KiB
Markdown
# 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).
|