A ranking engine that turns raw professional performance into a comparable rating — an "overall" score, like a sports rating, for people.
Give it performance records for an industry that has real measurables — sales (revenue, quota %, deals), athletics (40-yard dash, vertical, bench), academia (GPA, publications, citations) — and RawScore computes, for each person within their cohort:
- a direction-aware percentile and z-score for every metric,
- a composite 0–99 RawScore (a weighted blend of those percentiles), and
- an A / B / C / D tier.
The result is a leaderboard where an A-player is provably an A-player, and every score is explainable — the per-metric breakdown ships with the result, so a recruiter can see why someone ranks where they do instead of trusting a number.
It's a zero-dependency TypeScript REST API, Dockerized, with a
Prometheus /metrics endpoint so it drops straight into a Grafana dashboard.
This is a focused, working slice of the core idea behind a merit-based professional platform: stop guessing whether a candidate is a top or bottom performer — measure it and rank it. It's built to be read in ten minutes and to show three things at once: clean backend engineering, statistical modelling, and production-minded ops (containers + metrics).
- Orient each metric so bigger = better. "Lower is better" metrics (deal cycle time, 40-yard dash) are negated, so the rest of the math is uniform.
- Percentile per metric within the cohort — fair across metrics on wildly different scales (dollars vs seconds). Ties resolve to the middle, so being average yields ~50, never 0 or 100.
- Composite = weighted average of those percentiles (weights are per-metric, set in the industry definition), mapped onto a 0–99 rating.
- Tier: A ≥ 85, B ≥ 65, C ≥ 40, else D.
z-scores are computed too and returned in the breakdown, for a standard-deviations-from-the-mean view alongside the percentile.
Requires Node.js 22+ (runs the TypeScript sources directly — no build step).
node src/server/main.ts # starts on http://localhost:8080
node --test # 18 tests: stats, engine, tiers, HTTP APIdocker compose up # RawScore on :8080, Prometheus on :9090Then point Grafana at the Prometheus datasource (http://localhost:9090) and
build panels on rawscore_requests_total, rawscore_cohort_size, and
rawscore_request_duration_seconds.
| Method | Path | Purpose |
|---|---|---|
| GET | /health |
liveness + record count |
| GET | /industries |
industries and their metric definitions |
| GET | /leaderboard/:industry?limit=N |
ranked players with RawScore, tier, and breakdown |
| GET | /rank/:industry/:id |
one person's score and their place in the cohort |
| POST | /records |
ingest one record or an array |
| GET | /metrics |
Prometheus exposition format (Grafana-ready) |
{
"industry": "sales",
"label": "Sales",
"cohortSize": 8,
"leaderboard": [
{
"id": "s-01",
"name": "Maria Ortega",
"rawScore": 89,
"tier": "A",
"overallPercentile": 93.8,
"breakdown": [
{ "key": "revenue", "raw": 2450000, "percentile": 93.8, "zScore": 1.45 },
{ "key": "quota_pct", "raw": 168, "percentile": 93.8, "zScore": 1.39 },
{ "key": "deals", "raw": 54, "percentile": 81.3, "zScore": 0.92 },
{ "key": "avg_cycle_days", "raw": 22, "percentile": 81.3, "zScore": 0.93 }
]
}
]
}Note avg_cycle_days: a lower cycle time (22 days) scores in the 81st
percentile — the engine handles "lower is better" metrics correctly.
curl -X POST localhost:8080/records -H 'Content-Type: application/json' -d '{
"id": "s-99", "name": "New Star", "industry": "sales",
"metrics": { "revenue": 3200000, "quota_pct": 205, "deals": 72, "avg_cycle_days": 14 }
}'
# -> {"inserted":1,"updated":0,"totalRecords":20}
# GET /rank/sales/s-99 now returns rank 1, tier "A"# HELP rawscore_requests_total Total HTTP requests
# TYPE rawscore_requests_total counter
rawscore_requests_total{route="/leaderboard/:industry",method="GET",status="200"} 1
# HELP rawscore_cohort_size People scored in a cohort
# TYPE rawscore_cohort_size gauge
rawscore_cohort_size{industry="sales"} 8
# HELP rawscore_request_duration_seconds Most recent request duration
# TYPE rawscore_request_duration_seconds gauge
rawscore_request_duration_seconds{route="/leaderboard/:industry"} 0.0028
src/lib/types.ts shared domain types
src/lib/stats.ts mean, stddev, percentileRank, zScore, clamp
src/lib/score.ts the RawScore engine: percentiles -> composite -> tier
src/lib/store.ts in-memory data layer (swappable for a real DB)
src/server/metrics.ts zero-dep Prometheus registry
src/server/main.ts node:http REST API
data/industries.json industry + metric definitions (weights, direction)
data/records.json synthetic sample performers across 3 industries
test/engine.test.ts stats + scoring + tier tests
test/api.test.ts black-box HTTP tests against the running server
Dockerfile containerized, no build step
docker-compose.yml API + Prometheus, one command
- No third-party dependencies. The API, the metrics registry, and the scoring all use the Node standard library, so the whole thing is auditable and starts instantly.
- The data layer is a small interface (
src/lib/store.ts) on purpose: moving from in-memory to a real database (e.g. PostgreSQL or SurrealDB) is a localized change — the engine and server don't touch storage details. - The sample data in
data/is synthetic, written for this demo. - Weights and tier bands are configuration, not code — tune them per industry without touching the engine.
MIT