feat: add UnknownKeys::Prior, and explain why there is no Skip
#44's third ask was an opt-in mode so a caller with partially-known teams need not pre-filter. The requested shape was `Skip` — drop unknown members. Measured, that is the wrong mode to build. A team's performance is the *sum* of its members, so dropping one drops its variance too. On a two-member team with one unknown: SKIP (drop the member) : performance sigma 2.37 PRIOR (member at prior) : performance sigma 6.53 (2.76x wider) Skipping makes the model *more* certain because it knows *less*, which is backwards. `Prior` is also the answer the model already gives for a competitor it knows about but has no evidence for — measured, such a competitor sits at sigma 4.99 against the prior's 6.0 — so it corresponds to a state the model can actually be in. Skipping does not. So the enum is `Reject` (default, unchanged) and `Prior`, and it is `#[non_exhaustive]` in case a real use for skipping turns up later. Placed on `HistoryBuilder` rather than per-call. Neither consumer wants it to vary between queries: one scores thousands of candidate matchups in a loop, the other's headline feature is predicting a competitor nobody has faced. That makes it a property of how the model is being used, and keeps five prediction signatures unchanged. This also gives #48 the semantics it asked for — "I have never seen this competitor, here is the prior-informed answer" — which it needs for predicting a course nobody has played. `an_unknown_member_widens_its_team_rather_than_narrowing_it` pins the property that ruled `Skip` out, so a future convenience cannot quietly reintroduce it. Closes #44. Refs #48 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
@@ -1,5 +1,44 @@
|
||||
use std::fmt;
|
||||
|
||||
/// How a prediction should treat a key the history has never seen.
|
||||
///
|
||||
/// Configured once per history via
|
||||
/// [`HistoryBuilder::unknown_keys`](crate::HistoryBuilder::unknown_keys).
|
||||
/// Neither known consumer wants this to vary between queries — one predicts
|
||||
/// thousands of candidate matchups in a loop, the other's headline feature is
|
||||
/// predicting a competitor nobody has faced — so it is a property of how you
|
||||
/// intend to use the model rather than an argument on five call sites.
|
||||
///
|
||||
/// # There is deliberately no `Skip`
|
||||
///
|
||||
/// Dropping an unknown member is the obvious third option and it is wrong. A
|
||||
/// team's performance is the *sum* of its members, so removing one removes its
|
||||
/// variance too: measured on a two-member team with one unknown, skipping gives
|
||||
/// a performance sigma of 2.37 where treating the member as unknown gives 6.53.
|
||||
/// An unknown competitor would make the model *more* certain, which is
|
||||
/// backwards. `Prior` is also the answer the model already gives for a
|
||||
/// competitor it knows about but has no evidence for, so it corresponds to a
|
||||
/// state the model can actually be in; skipping does not.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum UnknownKeys {
|
||||
/// Reject the prediction with [`InferenceError::UnknownKey`].
|
||||
///
|
||||
/// The default, and the right one when every key is expected to be known:
|
||||
/// a team of strangers should not silently produce a confident-looking
|
||||
/// answer.
|
||||
#[default]
|
||||
Reject,
|
||||
/// Treat an unknown competitor as one sitting at the history's configured
|
||||
/// prior.
|
||||
///
|
||||
/// This is the honest Bayesian reading — a competitor you have never
|
||||
/// observed is exactly the prior — and it makes "predict a matchup
|
||||
/// involving someone new" a first-class question rather than something a
|
||||
/// caller fakes with a neutral constant.
|
||||
Prior,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum InferenceError {
|
||||
|
||||
Reference in New Issue
Block a user