Skip to content

Editorial

The editorial module is where the platform’s thesis becomes code. Five editorial constraints — topical diversity, recency / freshness, sentiment balance, editorial promotion, and sensitive-topic guard — applied to a candidate set through the ranker.

The ranker is the platform’s headline deep module: a pure function from (candidate_set, constraint_configuration) to a ranked list of articles. That’s the entire interface. Everything else in the module is in service of making that function trustworthy, testable, and editorially defensible.

Rep #5 is a self-contained lesson: tutorial/editorial/lesson-01-editorial-constraints.py. It takes one candidate set and re-ranks it under three editor policies, so you see the platform-leverage thesis instead of reading about it.

Terminal window
make -C tutorial/editorial run # rank one candidate set under three policies
make -C tutorial/editorial check # assert the rankings + the checkpoint
make -C tutorial/editorial clean # nothing to clean (pure in-memory)

The lesson is a compact, readable version of the production ranker: the three soft terms (diversity, recency, sentiment) and the two hard rules (promotion, sensitivity guard), as a pure (candidates, policy) -> ranked function. It then runs three policies over the SAME six candidates:

  • click-only baseline (all weights 0) → pure relevance: a1 a2 a3 a4 a5 a6
  • diversity-forward (diversity 0.5) → a1 a3 a5 a2 a6 a4
  • front-page promotion (force a6 to the top) → a6 a1 a2 a3 a4 a5

Same model output, three different slates — chosen by the editor, not the model. That is the whole thesis in one runnable file.

Between the click-only baseline and the diversity-forward policy, which article makes the biggest jump up the list, and why?

a5 (culture) jumps from position 5 to position 3 — the biggest upward move. Under pure relevance it sits behind two politics articles and a sports article. The moment diversity is weighted, repeating a category is penalised: once a1 (politics) and a3 (sports) lead, a fresh category beats the second-best politics article (a2). The editor changed one weight — not a single relevance score — and the reader sees a broader front. That is editorial judgment exercised at the platform layer, exactly where ADR-0004 argues it belongs.

Following the foundations template (ADR-0033): the lesson is a pure function with no I/O, so make run is instant and deterministic, make check asserts every policy’s exact order plus the checkpoint, and there is nothing to clean. The compact ranker mirrors the production one in serving/ranker.py — drilling the toy is what makes the grown-up version legible.

The ranker lives at tutorial/serving/src/serving/ranker.py. It is dependency-free — no database connection, no HTTP framework, no filesystem. That isolation is deliberate (ADR-0010 explains why) and is what makes the test suite fast and exhaustive: every config, every constraint, every edge case covered with seeded inputs.

The sensitive-topic detector is its own deep module:

The editorial configuration is a table, not code: constraint_configurations.sql. That table is what the editor interface reads and writes, and it is part of the analytical contract analysts can query directly.

The mixed-enforcement model (ADR-0010) refuses the temptation to make every editorial concern a numeric preference.

Soft constraints (weighted into a score):

  • Topical diversity — anti-overlap with already-ranked articles in the list
  • Recency / freshness — exponential decay against article age, half-life tunable
  • Sentiment balance — Wasserstein-style penalty against a target sentiment distribution; degrades gracefully when sentiment scores are absent

Hard rules (applied after sorting):

  • Editorial promotion — articles in the always-include list are inserted at their target positions, period
  • Sensitive-topic guard — a hard cap on the share of sensitive articles per list, dropping from the bottom until the cap holds

A promoted investigation cannot vanish into a low weight. A sensitive-topic cap cannot be overridden by a high relevance score. That asymmetry is the editorial promise this module makes.

ADR-0015 spells out the exact formulas: the score combination, each soft-term shape, the configuration schema, and the defaults. Anyone reading the ranker code can compare line-for-line against the ADR.

The ranker is consumed by serving through /recommendations/ and /preview endpoints. The editor module gives editors sliders + toggles + an always-include list management UI that all read and write the constraint_configurations table the ranker references. The evaluation module sweeps configurations and produces the Pareto chart that makes the editorial-cost-of-clicks argument visible.