Skip to content

About

Exact-arithmetic Ruby implementation of a six-factor niche viability score (GO/CONDITIONAL/KILL)

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

niche_viability

Exact arithmetic for a six-factor viability score, in Ruby with no dependencies.

Six subscores from 0 to 100, six weights that sum to 1.00, one weighted total, and one of three verdicts: GO, CONDITIONAL or KILL. That is the whole contract. The reason it is a library and not a one-line multiply is that the total has to be exact, because a threshold is the only thing anybody acts on and floating point disagrees with itself near a threshold.

require "niche_viability"

score = NicheViability.score(a: 40, v: 49, s: 78, c: 37, m: 71, b: 95)

score.total            # => (2797/50), exactly 55.94
score.reported         # => 55.9
score.verdict          # => :go
score.page_cap         # => nil, a GO is not capped
score.contribution(:s) # => (312/25), exactly 12.48
$ niche-viability --a 45 --v 45 --s 45 --c 45 --m 45 --b 45
     factor                          sub     wt      pts
A    Difficulty accessibility       45.0   0.26    11.70
V    Addressable volume             45.0   0.24    10.80
S    SERP capture headroom          45.0   0.16     7.20
C    Competitor displaceability     45.0   0.14     6.30
M    Monetization fit               45.0   0.12     5.40
B    Format buildability            45.0   0.08     3.60
     total, exact                                  45.00

score   45.0
verdict CONDITIONAL
cap     25 pages
$ echo $?
1

Install

gem install niche_viability

Or from a checkout: rake test runs the suite, ruby -Ilib exe/niche-viability --help runs the CLI. Ruby 2.6 or newer, standard library only.

The six factors

key factor weight
a difficulty accessibility 0.26
v addressable volume 0.24
s SERP capture headroom 0.16
c competitor displaceability 0.14
m monetization fit 0.12
b format buildability 0.08

VIABILITY = 0.26a + 0.24v + 0.16s + 0.14c + 0.12m + 0.08b, and the verdict is KILL below 40, CONDITIONAL below 55, GO otherwise. A CONDITIONAL verdict carries a 25-page cap, which Score#page_cap returns so a caller can enforce it.

Where the constants come from

None of the numbers above were invented here. The weight vector, both reference profiles and the two thresholds are taken from the published specification of the score, the niche score calculator and its printed method, which prints the formula, the six weights with a written justification for each, and the derivation of the thresholds from the two reference profiles. If you want to argue with a weight, that page is where the argument is. For where this one score sits among the stages that surround it — it is the gate at the end of stage 02, and the stages after it never start if it returns KILL — see the six-stage method.

This library implements only the arithmetic. It does not sample a SERP, estimate a subscore, or know anything about your niche. Six numbers you supply yourself cannot tell you what a search engine will do, and a GO here is a decision about your own calendar rather than a forecast of traffic, rankings or indexation.

The two reference profiles ship as fixtures and as a public API, because a threshold changed without re-deriving the profile it came from is a configuration error rather than a preference:

NicheViability.reference(:k).reported  # => 39.1, immediately below the kill line
NicheViability.reference(:g).reported  # => 56.0, immediately above the conditional line

Why exact arithmetic

Weights are stored as integer hundredths and subscores as exact Rationals parsed without ever passing through a binary float. Three things follow, and each one is a test in the suite:

  1. The total is the sum of the unrounded contributions. Profile G's six contributions round to 10.48 + 11.76 + 12.45 + 5.17 + 8.56 + 7.58 = 56.00, but the exact total is 55.992, which reports as 56.0. Round first and you get a different number. Near 55 that is the difference between a GO and a CONDITIONAL.
  2. No drift. 0.26 * 40.3 in Ruby floats is 10.478000000000002. Here it is Rational(10478, 1000), so two implementations of the same contract agree to the last digit rather than to within an epsilon.
  3. Thresholds are evaluated on the exact total, not the reported one. A score of 39.96 reports as 40.0 and is still a KILL.

What the library refuses

A check that cannot be evaluated is a failure, not a pass, so the library raises rather than guessing:

  • a subscore outside 0..100, missing, non-numeric, or carrying more than one decimal place — the contract carries one, and silently rounding a second one away would move a score across a threshold without saying so;
  • an unknown subscore key, which is nearly always a typo for a real one;
  • a weight vector that does not sum to exactly 1.00, or one holding a non-integer number of hundredths. This is WeightSumError rather than a warning because a vector summing to 0.98 still returns a plausible-looking number, on a different scale, that no longer means what the thresholds mean.

Weights are overridable as long as they still sum to one:

weights = NicheViability::Weights.new(a: 30, v: 20, s: 16, c: 14, m: 12, b: 8)
NicheViability::Score.new({ a: 40, v: 49, s: 78, c: 37, m: 71, b: 95 }, weights: weights)

CLI

niche-viability takes the six subscores as flags, or a JSON object on stdin, and prints either the contribution table or JSON (--json). --reference k and --reference g score the two published profiles. Exit status is the verdict, so the tool composes into a shell script or a CI job:

status meaning
0 GO
1 CONDITIONAL
2 KILL
64 bad input; the reason goes to stderr

Tests

rake test — 37 tests, 1097 assertions, no dependencies beyond minitest from the standard library. They cover both reference profiles against their published per-factor arithmetic, both thresholds at the exact boundary, 1000 pseudo-random profiles checked against the definition of the formula, every refusal listed above, and the CLI's output and exit statuses.

Licence

MIT. See LICENSE.

About

Exact-arithmetic Ruby implementation of a six-factor niche viability score (GO/CONDITIONAL/KILL)

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages