Farpost · Reputation Scoring
$ Scoring a Blank Slate Without Lying About It
Reputation-based dispatch ranks candidates by their track record — but a brand-new professional with zero history has no track record to rank. The naive fix is to score every unknown dimension as a flat, neutral 0.5 and call it fair. Farpost doesn't do that.
Each signal gets its own honest floor instead: recent activity floors at 0.3, not 0.5 — someone with no activity history isn't assumed to be exactly average, they're assumed slightly below it until proven otherwise. Engagement and experience floor at 0.0, since there's nothing to credit yet. The one place a flat 0.5 does appear is deliberately narrow: only when the reputation computation itself throws an exception, which is a different situation — a broken calculation, not an absence of history — and the code says so in a comment.
async def rank_candidates(candidates: list[Professional]) -> list[Professional]:
"""Rank candidates by reputation signals. Role-agnostic.
Uses the reputation timeline read-model. Professionals with no history are not
flatly neutral — each signal has its own floor: recently_active=0.3,
engagement=0.0, experience=0.0, availability computed as normal from real
capacity fields. Only the `except` fallback below (a timeline-computation
error, not "no history") uses a flat 0.5.
"""
scored = []
for pro in candidates:
try:
timeline = await get_reputation_timeline(pro.slug)
s = timeline.signals
if s.quarantined:
continue # provisional role — ineligible for dispatch
availability = (
(pro.dispatch_capacity_max - pro.dispatch_capacity_current)
/ pro.dispatch_capacity_max
if pro.dispatch_capacity_max > 0 else 0.5
)
experience = min(s.total_events, 20) / 20.0
recently = 1.0 if s.recently_active else 0.3
engagement = min(s.distinct_relationship_types, 5) / 5.0
score = (
recently * 0.30 + engagement * 0.25 + experience * 0.20
+ availability * 0.15 + 0.10 # base — everyone gets a floor
)
scored.append((round(score, 4), pro))
except Exception as exc:
scored.append((0.5, pro)) # timeline computation failed — NOT "no history"
scored.sort(key=lambda t: t[0], reverse=True)
return [pro for _, pro in scored]The fix
recently_active is boolean, so it resolves to 1.0 or the 0.3 floor, never an average. experience and engagement scale linearly against a capped denominator (20 events, 5 relationship types) so they floor naturally at 0.0 with no history. Only the except branch — an actual computation failure — falls back to a flat 0.5, and that fallback is deliberately distinguished, in a comment, from what "no history" should mean.
Why this matters
Conflating "we have no data" with "something broke" is a common, easy-to-miss scoring-system bug — both look like a missing number, but they should never resolve to the same score. Treating them differently, and documenting why in the code, is the kind of small, deliberate honesty about what a metric actually means that separates a scoring system that's technically working from one that's actually fair.
