| 1 |
# painhours |
| 2 |
|
| 3 |
The painhours urgency model for Rust: a 0-100 heat score computed from how much |
| 4 |
something hurts, how broadly it hits, and how long it has been sitting there. |
| 5 |
|
| 6 |
Nothing stores a priority. Rank by the score; use the band only for color. |
| 7 |
|
| 8 |
## The model |
| 9 |
|
| 10 |
```text |
| 11 |
raw = pain^1.5 * scale^2.0 * age_weeks^1.5 |
| 12 |
painhours = round(100 * (1 - e^(-raw/266))) |
| 13 |
``` |
| 14 |
|
| 15 |
`pain` and `scale` are 1-5 guesstimates. Each exponent tunes one factor |
| 16 |
independently: `scale` drives the ranking, `pain` gives severity a secondary |
| 17 |
pull, and the exponent on age is the anti-starvation lever that decides how fast |
| 18 |
a narrow problem climbs rather than languishing forever. |
| 19 |
|
| 20 |
Band cutoffs on the score: Critical at 75, High at 50, Medium at 20, Low below. |
| 21 |
|
| 22 |
## Use |
| 23 |
|
| 24 |
```rust |
| 25 |
use chrono::Utc; |
| 26 |
use painhours::{Priority, age_weeks, painhours}; |
| 27 |
|
| 28 |
// Age is measured to an anchor you choose. Pass `Utc::now()` while the item is |
| 29 |
// live; pass the resolution instant once it is terminal, so closed items stop |
| 30 |
// climbing the list. |
| 31 |
let weeks = age_weeks(created_at, Utc::now()); |
| 32 |
let score = painhours(pain, scale, weeks); |
| 33 |
let band = Priority::from_painhours(score); |
| 34 |
``` |
| 35 |
|
| 36 |
The crate is the scoring model and nothing else. It has no opinion about what |
| 37 |
your record type is, what statuses it has, or where you store it. |
| 38 |
|
| 39 |
## Consumers |
| 40 |
|
| 41 |
`MNW/wam` (tickets) and GoingsOn (problems), so one ranked list can span both. |
| 42 |
Changing a constant here reranks both, which is the point: duplicating the tuning |
| 43 |
would let the rankings drift. |
| 44 |
|