Rubric Playground
Re-weight a real investment screen and watch the shortlist re-rank, with every score traceable to its rubric anchor.
- Role
- Analysis, scoring engine and interface
- Year
- 2026
- Stack
- Next.js, TypeScript, Motion, Python parity tests
- Links
- Source
Oxyle
Due DiligencePuraffinity
Due DiligencePrevisico
WatchlistHydraloop
WatchlistDetektia
WatchlistSolarDew
WatchlistFluves
Screened & Declined
- Why
- I built a Python screener on Aurum Impact's published investment criteria. Does its ranking survive other priorities?
- Cut
- I left out generated explanations (every reason is a written rubric anchor), adding companies, and shareable score edits.
- Limits
- Scores are my judgement, not measurement; independent work, not affiliated with or endorsed by Aurum Impact, and not investment advice.
How it scores
Hard filters reject on mandate, never on quality: geography (Europe), stage (pre-seed to Series A) and scope (deferred segments, defence, non-equity legal forms). Survivors get twelve scores from 0 to 5, each against a written anchor, weighted Impact 35, Bottleneck 35, Fit 30. A source that is only self-reported costs 5%. Tiers start at 72, 60 and 48. Then check_consistency compares scores with structural priors, such as a municipal buyer with a fast sales cycle, and raises the question a committee would ask.
The port
Python in the browser means megabytes of WebAssembly, so I ported the engine line by line and test it against the screener's committed shortlist.csv. Same summation order, multiply before divide, haircut after the sum, and round() reproduced half-to-even on the exact float. Weights are stored as integers per mille, so Reset restores bit-identical weights.
composite = sum(cr.weight * _get(c, cr.key) / 5.0 for cr in ALL_CRITERIA) * 100.0
has_unverified = any(e.confidence == "unverified" for e in c.evidence)
if has_unverified or not c.evidence:
composite *= UNVERIFIED_PENALTY
composite = round(composite, 1)let composite = pySum(ALL_CRITERIA.map((cr) => (weights[cr.key] * getScore(c, cr.key)) / 5.0)) * 100.0;
const haircutApplied = config.haircut && hasUnverifiedTraction(c);
if (haircutApplied) composite *= UNVERIFIED_PENALTY;
const rounded = pyRound(composite, 1);What the numbers say
Oxyle leads Puraffinity by 0.1 points. Its composite is 75.0499999999999972 as a float. Python prints 75.0; Math.round(x * 10) / 10 prints 75.1, a number the screener never produced.
All 7 scored companies carry an unverified claim, so the haircut never reorders them. It does decide a tier: without it Previsico moves from 58.9 (Watchlist) to 62.0 (Closer Screening).
Two earlier claims did not survive the faithful port. The top two do change as Bottleneck sweeps from 0 to 80%: at 66%, 74%, 78% they tie at one decimal, and Python's last tie-break, file order, puts Puraffinity first. And ranks 3 to 7 swap at 11%, 22%, 28%, 44%, 45%, 69%, not 23% for the second.
The stability band redraws 400 weightings around yours, each criterion weight scaled by 0.5 to 1.5 from a seeded PRNG. At Aurum's weights Oxyle ranks first in 54% of them, Puraffinity in 46%.
Funnel: 12 screened, 5 out on mandate, 2 to due diligence, 4 to the watchlist, 1 declined on score. 8 of 9 segments have nothing above the watchlist.
A vitest suite ports the screener's tests (weight audit, filter integrity, determinism, a missing score throws) and pins every number here.
Limits
The universe is hand-built from public sources, so it favours companies with funding news; stealth companies are absent by construction. The band perturbs weights, never scores. Data as of 25 Aug 2026; claims marked unverified are self-reported. The committed CSV names two columns outcome_kpi, so the KPI text is overwritten by the score.