Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RawScore

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.


Why this project exists

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).

The scoring model, briefly

  1. 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.
  2. 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.
  3. Composite = weighted average of those percentiles (weights are per-metric, set in the industry definition), mapped onto a 0–99 rating.
  4. 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.

Run it

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 API

Or with Docker (API + Prometheus)

docker compose up              # RawScore on :8080, Prometheus on :9090

Then point Grafana at the Prometheus datasource (http://localhost:9090) and build panels on rawscore_requests_total, rawscore_cohort_size, and rawscore_request_duration_seconds.

API

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)

Example: GET /leaderboard/sales?limit=3

{
  "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.

Example: ingest a new performer

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"

Example: GET /metrics

# 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

Layout

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

Design notes

  • 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.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages