# kcode-speaker-extract
Deterministic segment-range planning and a two-stage speaker-extraction contract. The library performs no media slicing, provider calls, persistence, replay, adoption, or deployment.
## API
```rust
pub struct SegmentPlan {
pub policy: Key,
pub source_duration_ms: u64,
pub segments: Vec<PlannedSegment>,
}
pub struct PlannedSegment {
pub ordinal: u16,
pub start_ms: u64,
pub end_ms: u64,
}
pub struct ExtractionContract {
pub schema_key: Key,
pub prompt: &'static str,
pub response_mime: &'static str,
pub normalized_schema_key: Key,
pub normalized_mime: &'static str,
}
pub enum ExtractionOutcome {
Scored(ScoredClip),
Unscorable {
reason: String,
additional_speakers: Vec<AdditionalSpeaker>,
},
}
pub struct ScoredClip {
pub speakers: Vec<CompleteSpeaker>,
pub additional_speakers: Vec<AdditionalSpeaker>,
}
pub struct CompleteSpeaker {
pub speaker_ordinal: u16,
pub primary_language: Key,
pub closest_dialect: String,
pub usable_speech_ms: u32,
pub features: FeatureVector,
}
pub struct AdditionalSpeaker {
pub speaker_ordinal: u16,
pub description: String,
}
pub struct BatchChunkAnalysis {
pub chunk_index: usize,
pub duration_ms: u64,
pub raw_analysis: String,
}
pub struct BatchExtraction {
pub chunk_index: usize,
pub outcome: ExtractionOutcome,
}
pub enum ExtractError {
ZeroDuration,
SegmentCountExceedsU16 { required: u128 },
BlankRawAnalysis,
NormalizationEncoding(String),
InvalidJson(String),
InvalidResponse(&'static str),
}
pub fn plan_segments(duration_ms: u64) -> Result<SegmentPlan, ExtractError>;
pub fn contract() -> &'static ExtractionContract;
pub fn normalization_prompt(raw_analysis: &str) -> Result<String, ExtractError>;
pub fn batch_normalization_prompt(
chunks: &[BatchChunkAnalysis],
) -> Result<String, ExtractError>;
pub fn parse(
response: &str,
clip_duration_ms: u64,
) -> Result<ExtractionOutcome, ExtractError>;
pub fn parse_batch(
response: &str,
chunks: &[BatchChunkAnalysis],
) -> Result<Vec<BatchExtraction>, ExtractError>;
```
`Key` and `FeatureVector` are re-exported from the compatibility-tracked `kcode-speaker-types` 0.2 dependency.
## Segment planning
`plan_segments` returns policy `speaker-segments/1` and rejects zero duration. Durations below 240,000 ms produce one end-exclusive range. Longer sources receive the minimum deterministic number of ranges, each at most 239,999 ms, with exactly 5,000 ms overlap between adjacent ranges. Ranges cover from zero through the source end, differ in length by at most 1 ms, and use contiguous `u16` ordinals. A required count above `u16::MAX` returns `SegmentCountExceedsU16`.
The function computes ranges only; it does not slice or transport media.
## Two-stage extraction contract
`contract` exposes:
- provider schema key `gemini-speaker-24-freeform/1`;
- the frozen prompt bytes embedded from `src/assets/prompt-speaker-24-freeform-v1.txt`;
- provider response MIME `text/plain`;
- normalized schema key `gemini-speaker-24-normalized/1`;
- normalized response MIME `application/json`.
The provider prompt intentionally permits free-form analysis. `normalization_prompt` rejects blank input, imposes no arbitrary input-size cap, embeds the raw analysis as one JSON string, and requests one strict normalized JSON object without inventing missing speakers, metadata, or feature ratings.
`batch_normalization_prompt` is the recording-wide form. It accepts every chronological Gemini result, requires unique chunk indexes and positive durations, embeds the results as untrusted JSON data, and requests exactly one normalized outcome per input in a single model response. `parse_batch` requires exact chunk coverage, rejects duplicate or unknown indexes, validates each outcome against its own chunk duration, and returns results in input order.
## Normalized parsing
`parse` accepts exactly one normalized scored or unscorable object. Unknown, duplicate, missing, mixed-status, and wrongly typed fields are rejected.
A scored response requires at least one complete profile. Each complete profile contains a unique ordinal, a shared validated primary-language key, a nonblank dialect of at most 128 UTF-8 bytes, a positive usable-speech duration no greater than the clip duration, and exactly 24 integer feature values in the frozen `FeatureVector` order. Every feature is validated from 0 through 100, including `perceived_vocal_age_percentile`.
Additional substantive speakers without enough evidence for complete profiles contain only an ordinal and a nonblank description of at most 240 UTF-8 bytes. Unscorable responses contain no complete profiles and require a nonblank reason of at most 240 UTF-8 bytes. Across complete and additional speakers, ordinals must be unique and contiguous from zero.
Recording quality is caller or host metadata and is not present in the provider prompt, normalized schema, parser output, or validation. Validation failures return typed `ExtractError` values.