# kcode-speaker-model 0.2.1
Deterministic, cohort-scoped fitting and identification over the frozen 24-field `kcode-speaker-types` contract.
## API
```rust
pub const ALL_24: FeatureMask;
pub struct ModelConfig {
pub mask: FeatureMask,
pub components: u8,
pub relevance: f64,
pub variance_floor: f64,
pub absolute_threshold: f64,
pub margin_threshold: f64,
}
pub struct FitInput<'a> {
pub cohort_id: &'a Key,
pub samples: &'a [LabeledSample],
pub config: ModelConfig,
}
pub struct TrainingRow {
pub sample_id: Key,
pub speaker_id: Key,
pub cohort_id: Key,
pub features: FeatureVector,
}
pub struct FitRowsInput<'a> {
pub cohort_id: &'a Key,
pub rows: &'a [TrainingRow],
pub config: ModelConfig,
}
pub struct ModelSnapshot { /* private immutable state */ }
pub struct CandidateScore {
pub speaker_id: Key,
pub llr: f64,
}
pub enum Decision {
Known { speaker_id: Key },
Unknown,
}
pub struct Identification {
pub decision: Decision,
pub best: CandidateScore,
pub runner_up: Option<CandidateScore>,
pub absolute_pass: bool,
pub margin_pass: bool,
}
pub fn fit(input: FitInput<'_>) -> Result<ModelSnapshot, ModelError>;
pub fn fit_rows(input: FitRowsInput<'_>) -> Result<ModelSnapshot, ModelError>;
pub fn identify(
model: &ModelSnapshot,
cohort_id: &Key,
features: &FeatureVector,
) -> Result<Identification, ModelError>;
pub fn encode(model: &ModelSnapshot) -> Result<Vec<u8>, ModelError>;
pub fn decode(bytes: &[u8]) -> Result<ModelSnapshot, ModelError>;
```
`ALL_24` selects bits 0 through `FEATURE_COUNT - 1`. Other scoring masks are caller-supplied validated `FeatureMask` values; this package provides no historical extractor-specific subsets.
## Fitting and scoring
`fit` and `fit_rows` require nonempty, uniquely identified rows belonging to the requested cohort, at least one represented speaker, `1..=rows.len()` components, positive finite relevance and variance floor, finite thresholds, and nonzero finite population standard deviation for every selected feature. `fit` retains the provenance-rich `LabeledSample` API. `fit_rows` accepts only the IDs and feature vector the statistical model actually uses, so simple classifiers do not manufacture attempt, media, or recording-quality metadata. Rows have equal weight. Selected features follow ascending frozen mask index. Normalization is learned only from the supplied training rows.
Fitting uses a deterministic global diagonal GMM-UBM with 200 maximum EM updates and `1e-8` relative tolerance; a nonconverged result is rejected. UBM responsibilities produce per-speaker component occupancy and first-order sums. Speaker models adapt component means only with the configured relevance factor while retaining UBM weights and variances.
`identify` requires the snapshot cohort and scores each speaker by speaker-model log likelihood minus UBM log likelihood. Scores are ordered by descending LLR with speaker-key tie order. `Known` requires both `best.llr >= absolute_threshold` and `best.llr - runner_up.llr >= margin_threshold`; equality passes. With one enrolled speaker, `runner_up` is `None`, `margin_pass` is true, and only the absolute gate controls. Identification is read-only.
## Snapshot identity
Snapshots are opaque, immutable, cohort-bound, manifest-hashed, and wire-versioned. `fit` hashes its complete provenance-rich input as before. `fit_rows` uses a distinct tagged manifest containing only sample, speaker, and cohort IDs plus all `FEATURE_COUNT` bytes.
`encode` emits deterministic versioned JSON. `decode` accepts only the current 24-field snapshot version and validates configuration, dimensions, finite values, component statistics, speaker ordering, sample counts, and mean-only MAP state before returning a model. Earlier 35-field artifacts are incompatible. Any training-row change requires a fresh `fit`.
This library performs no persistence, provider work, replay, evaluation-system work, publication, adoption, or deployment.