Skip to main content

recsys-eval

Offline evaluation and promotion gating for recommender systems.

Status: alpha. Feature-complete for v0.1 — metrics, fixture, ranker protocol, scoring, preflight checks, and promotion gate. The API may still move before 1.0.

Built around one claim: the useful question is usually not "how good is this model" but "is this candidate worse than the one already in production". Those need different machinery, and conflating them is how teams end up with a dashboard full of numbers nobody can act on.

The whole loop

from recsys_eval import Fixture, Interaction, RelevancePolicy, preflight
from recsys_eval.gate import GatePolicy, run_gate

fixture = Fixture.from_interactions(
    events,  # your logs, as Interaction(...)
    candidates=serving_index,  # your pool, passed explicitly
    holdout_start=cutoff,
    holdout_end=cutoff + timedelta(days=7),
    policy=RelevancePolicy(positive_kinds=frozenset({"like", "share", "save"})),
)

preflight(build_fixture, candidate, incumbent)  # prove the harness works

verdict = run_gate(
    candidate,
    incumbent,
    fixture,
    policy=GatePolicy(gated_metrics=["ndcg@10", "recall@50"]),
    recorder=save_audit_row,
)
if verdict.promoted:
    deploy(candidate)

Implementing a ranker is two methods:

class MyRanker:
    def prepare(self, candidates): ...  # build an index, once
    def rank(self, user, k, exclude): ...  # return bare ids, best first

A runnable end-to-end version lives in examples/quickstart.py.

Install

pip install recsys-eval

No dependencies. The metrics are pure stdlib; the fixture and gate layers talk to your data through protocols rather than through pandas, numpy, or an ORM.

Metrics

from recsys_eval import ndcg_at_k, recall_at_k, catalog_coverage

# Graded relevance: say that a share is worth more than a like, and NDCG
# will reward ranking shares higher.
ndcg_at_k(["post_a", "post_b"], {"post_a": 3.0, "post_b": 1.0}, k=10)

# Relevant items your candidate index never even offered stay in the
# denominator, so a shrinking index shows up as a falling score.
recall_at_k(["post_a"], {"post_a", "post_c"}, k=10)  # 0.5, not 1.0

# Read this beside NDCG. A ranker can raise NDCG by collapsing onto
# universally-popular items, and every per-user metric is blind to that.
catalog_coverage(rankings, catalog_size=50_000, k=10)
Function Relevance Sensitive to rank
ndcg_at_k graded yes
recall_at_k binary no
precision_at_k binary no
average_precision_at_k binary yes
reciprocal_rank_at_k binary yes
hit_rate_at_k binary no
catalog_coverage — no

All are per-user quantities. MAP@k, MRR@k and NDCG@k are their macro-averages — naming them honestly keeps the aggregation choice visible at the call site, where it belongs.

Every function returns 0.0 — never NaN, never raising — on degenerate input, so a user with no usable labels cannot poison a macro-average.

What these numbers are not

They are not a measure of absolute recommendation quality. In any real system the candidate pool is truncated, which structurally caps Recall below 1.0 no matter how good the model is.

Do not quote them as system quality. Do compare them run over run, candidate against incumbent.

License

Apache-2.0

Metadata

Release files for recsys-eval 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for recsys-eval 0.1.1
File Size Uploaded
recsys_eval-0.1.1.tar.gz 39.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for recsys-eval 0.1.1
File Interpreter ABI Platform
recsys_eval-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 72.1 kB

Release files / recsys_eval-0.1.1.tar.gz

Download URL recsys_eval-0.1.1.tar.gz
Size 39.6 kB
Tags Source
SHA-256 checksum
How to use checksums
75d276d3787a1ac02a00f93750fc48a1b2dd6e34f2ba887baf767459cd91b472
BLAKE2b-256 checksum
How to use checksums
90a21b8164b2ddc9c89a78e5b83ffcc37c657c890e209b4ca3e28d373d56a3f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

Release files / recsys_eval-0.1.1-py3-none-any.whl

Download URL recsys_eval-0.1.1-py3-none-any.whl
Size 32.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
478b9111960b0a9760b759002b754f59d43286857675cba5910b1e800fb372dd
BLAKE2b-256 checksum
How to use checksums
52c3026e1b5be10f667a8b5d0e3b16dcdb33e83b902c809ce8d19679db3377f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page