use derive_more::IsVariant;
use jiff::Timestamp as JiffTimestamp;
use smol_str::SmolStr;
use crate::domain::Uuid7;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct IndexProgress {
total: u32,
indexed: u32,
failed: u32,
}
impl IndexProgress {
#[inline(always)]
pub const fn new() -> Self {
Self {
total: 0,
indexed: 0,
failed: 0,
}
}
#[inline(always)]
pub const fn from_parts(total: u32, indexed: u32, failed: u32) -> Self {
Self {
total,
indexed,
failed,
}
}
pub const fn try_new(total: u32, indexed: u32, failed: u32) -> Result<Self, IndexProgressError> {
let sum = match indexed.checked_add(failed) {
Some(s) => s,
None => return Err(IndexProgressError::SumOverflows),
};
if sum > total {
return Err(IndexProgressError::SumExceedsTotal);
}
Ok(Self {
total,
indexed,
failed,
})
}
#[inline(always)]
pub const fn total(&self) -> u32 {
self.total
}
#[inline(always)]
pub const fn indexed(&self) -> u32 {
self.indexed
}
#[inline(always)]
pub const fn failed(&self) -> u32 {
self.failed
}
#[inline(always)]
pub const fn has_failures(&self) -> bool {
self.failed > 0
}
#[must_use]
#[inline(always)]
pub const fn with_total(mut self, total: u32) -> Self {
self.total = total;
self
}
#[must_use]
#[inline(always)]
pub const fn with_indexed(mut self, indexed: u32) -> Self {
self.indexed = indexed;
self
}
#[must_use]
#[inline(always)]
pub const fn with_failed(mut self, failed: u32) -> Self {
self.failed = failed;
self
}
#[inline(always)]
pub const fn set_total(&mut self, total: u32) -> &mut Self {
self.total = total;
self
}
#[inline(always)]
pub const fn set_indexed(&mut self, indexed: u32) -> &mut Self {
self.indexed = indexed;
self
}
#[inline(always)]
pub const fn set_failed(&mut self, failed: u32) -> &mut Self {
self.failed = failed;
self
}
}
impl Default for IndexProgress {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, IsVariant, thiserror::Error)]
#[non_exhaustive]
pub enum IndexProgressError {
#[error("IndexProgress: indexed + failed must not exceed total")]
SumExceedsTotal,
#[error("IndexProgress: indexed + failed overflows u32")]
SumOverflows,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Provenance {
model_name: SmolStr,
model_version: SmolStr,
prompt_version: SmolStr,
indexer_version: SmolStr,
}
impl Provenance {
#[inline]
pub fn new() -> Self {
Self {
model_name: SmolStr::default(),
model_version: SmolStr::default(),
prompt_version: SmolStr::default(),
indexer_version: SmolStr::default(),
}
}
#[inline]
pub fn from_parts(
model_name: impl Into<SmolStr>,
model_version: impl Into<SmolStr>,
prompt_version: impl Into<SmolStr>,
indexer_version: impl Into<SmolStr>,
) -> Self {
Self {
model_name: model_name.into(),
model_version: model_version.into(),
prompt_version: prompt_version.into(),
indexer_version: indexer_version.into(),
}
}
#[inline]
pub fn model_name(&self) -> &str {
self.model_name.as_str()
}
#[inline]
pub fn model_version(&self) -> &str {
self.model_version.as_str()
}
#[inline]
pub fn prompt_version(&self) -> &str {
self.prompt_version.as_str()
}
#[inline]
pub fn indexer_version(&self) -> &str {
self.indexer_version.as_str()
}
#[inline]
pub fn with_model_name(mut self, v: impl Into<SmolStr>) -> Self {
self.model_name = v.into();
self
}
#[inline]
pub fn with_model_version(mut self, v: impl Into<SmolStr>) -> Self {
self.model_version = v.into();
self
}
#[inline]
pub fn with_prompt_version(mut self, v: impl Into<SmolStr>) -> Self {
self.prompt_version = v.into();
self
}
#[inline]
pub fn with_indexer_version(mut self, v: impl Into<SmolStr>) -> Self {
self.indexer_version = v.into();
self
}
#[inline]
pub fn set_model_name(&mut self, v: impl Into<SmolStr>) {
self.model_name = v.into();
}
#[inline]
pub fn set_model_version(&mut self, v: impl Into<SmolStr>) {
self.model_version = v.into();
}
#[inline]
pub fn set_prompt_version(&mut self, v: impl Into<SmolStr>) {
self.prompt_version = v.into();
}
#[inline]
pub fn set_indexer_version(&mut self, v: impl Into<SmolStr>) {
self.indexer_version = v.into();
}
#[inline]
pub fn is_empty(&self) -> bool {
self.model_name.is_empty()
&& self.model_version.is_empty()
&& self.prompt_version.is_empty()
&& self.indexer_version.is_empty()
}
}
impl Default for Provenance {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LocalizedText {
src: SmolStr,
translated: SmolStr,
}
impl LocalizedText {
#[inline]
pub fn new() -> Self {
Self {
src: SmolStr::default(),
translated: SmolStr::default(),
}
}
#[inline]
pub fn from_src_translated(src: impl Into<SmolStr>, translated: impl Into<SmolStr>) -> Self {
Self {
src: src.into(),
translated: translated.into(),
}
}
#[inline]
pub fn from_src(src: impl Into<SmolStr>) -> Self {
Self {
src: src.into(),
translated: SmolStr::default(),
}
}
#[inline]
pub fn src(&self) -> &str {
self.src.as_str()
}
#[inline]
pub fn translated(&self) -> &str {
self.translated.as_str()
}
#[inline]
pub fn with_src(mut self, v: impl Into<SmolStr>) -> Self {
self.src = v.into();
self
}
#[inline]
pub fn with_translated(mut self, v: impl Into<SmolStr>) -> Self {
self.translated = v.into();
self
}
#[inline]
pub fn set_src(&mut self, v: impl Into<SmolStr>) {
self.src = v.into();
}
#[inline]
pub fn set_translated(&mut self, v: impl Into<SmolStr>) {
self.translated = v.into();
}
#[inline]
pub fn is_empty(&self) -> bool {
self.src.is_empty() && self.translated.is_empty()
}
#[inline]
pub fn display(&self) -> &str {
if !self.translated.is_empty() {
self.translated.as_str()
} else {
self.src.as_str()
}
}
}
impl Default for LocalizedText {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VoiceFingerprint<Id = Uuid7> {
vector_id: Id,
dimensions: u32,
extracted_at: JiffTimestamp,
confidence: Option<f32>,
provenance: Provenance,
}
impl VoiceFingerprint<Uuid7> {
pub fn try_new(
vector_id: Uuid7,
dimensions: u32,
extracted_at: JiffTimestamp,
confidence: Option<f32>,
provenance: Provenance,
) -> Result<Self, VoiceFingerprintError> {
if vector_id.is_nil() {
return Err(VoiceFingerprintError::NilVectorId);
}
if dimensions == 0 {
return Err(VoiceFingerprintError::ZeroDimensions);
}
if let Some(c) = confidence {
if !c.is_finite() || !(0.0..=1.0).contains(&c) {
return Err(VoiceFingerprintError::ConfidenceOutOfRange);
}
}
Ok(Self {
vector_id,
dimensions,
extracted_at,
confidence,
provenance,
})
}
}
impl<Id> VoiceFingerprint<Id> {
#[inline(always)]
#[must_use]
pub const fn from_parts(
vector_id: Id,
dimensions: u32,
extracted_at: JiffTimestamp,
confidence: Option<f32>,
provenance: Provenance,
) -> Self {
Self {
vector_id,
dimensions,
extracted_at,
confidence,
provenance,
}
}
#[inline(always)]
pub const fn vector_id_ref(&self) -> &Id {
&self.vector_id
}
#[inline(always)]
pub const fn dimensions(&self) -> u32 {
self.dimensions
}
#[inline(always)]
pub const fn extracted_at(&self) -> JiffTimestamp {
self.extracted_at
}
#[inline(always)]
pub const fn confidence(&self) -> Option<f32> {
self.confidence
}
#[inline(always)]
pub const fn provenance_ref(&self) -> &Provenance {
&self.provenance
}
#[inline(always)]
#[must_use]
pub fn with_vector_id(mut self, vector_id: Id) -> Self {
self.vector_id = vector_id;
self
}
#[inline]
pub fn try_with_dimensions(mut self, dimensions: u32) -> Result<Self, VoiceFingerprintError> {
self.try_set_dimensions(dimensions)?;
Ok(self)
}
#[inline(always)]
#[must_use]
pub const fn with_extracted_at(mut self, t: JiffTimestamp) -> Self {
self.extracted_at = t;
self
}
#[inline]
pub fn try_with_confidence(mut self, c: f32) -> Result<Self, VoiceFingerprintError> {
self.try_set_confidence(c)?;
Ok(self)
}
#[inline(always)]
#[must_use]
pub fn with_provenance(mut self, provenance: Provenance) -> Self {
self.provenance = provenance;
self
}
#[inline(always)]
pub fn set_vector_id(&mut self, vector_id: Id) -> &mut Self {
self.vector_id = vector_id;
self
}
#[inline]
pub fn try_set_dimensions(
&mut self,
dimensions: u32,
) -> Result<&mut Self, VoiceFingerprintError> {
if dimensions == 0 {
return Err(VoiceFingerprintError::ZeroDimensions);
}
self.dimensions = dimensions;
Ok(self)
}
#[inline(always)]
pub const fn set_extracted_at(&mut self, t: JiffTimestamp) -> &mut Self {
self.extracted_at = t;
self
}
#[inline]
pub fn try_set_confidence(&mut self, c: f32) -> Result<&mut Self, VoiceFingerprintError> {
if !c.is_finite() || !(0.0..=1.0).contains(&c) {
return Err(VoiceFingerprintError::ConfidenceOutOfRange);
}
self.confidence = Some(c);
Ok(self)
}
#[inline(always)]
pub const fn clear_confidence(&mut self) -> &mut Self {
self.confidence = None;
self
}
#[inline(always)]
pub fn set_provenance(&mut self, provenance: Provenance) -> &mut Self {
self.provenance = provenance;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, IsVariant, thiserror::Error)]
#[non_exhaustive]
pub enum VoiceFingerprintError {
#[error("VoiceFingerprint vector_id must not be the nil UUID")]
NilVectorId,
#[error("VoiceFingerprint dimensions must be > 0")]
ZeroDimensions,
#[error("VoiceFingerprint confidence must be finite and in [0.0, 1.0]")]
ConfidenceOutOfRange,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provenance_new_is_empty_and_default_delegates() {
let p = Provenance::new();
assert!(p.is_empty());
assert_eq!(p.model_name(), "");
assert_eq!(p.model_version(), "");
assert_eq!(p.prompt_version(), "");
assert_eq!(p.indexer_version(), "");
assert_eq!(Provenance::default(), p);
}
#[test]
fn provenance_construction_and_emptiness() {
let p = Provenance::from_parts(
"qwen2-vl-7b",
"v0.3.0",
"vlm-prompt@2",
"findit-indexer-0.1.0",
);
assert!(!p.is_empty());
assert_eq!(p.model_name(), "qwen2-vl-7b");
assert_eq!(p.indexer_version(), "findit-indexer-0.1.0");
let p2 = Provenance::from_parts("", "", "", "x");
assert!(!p2.is_empty());
}
#[test]
fn provenance_builders_chain() {
let p = Provenance::default()
.with_model_name("qwen2-vl-7b")
.with_model_version("v0.3.0")
.with_prompt_version("vlm-prompt@2")
.with_indexer_version("findit-indexer-0.1.0");
assert_eq!(p.model_name(), "qwen2-vl-7b");
assert_eq!(p.model_version(), "v0.3.0");
assert_eq!(p.prompt_version(), "vlm-prompt@2");
assert_eq!(p.indexer_version(), "findit-indexer-0.1.0");
}
#[test]
fn provenance_setters_mutate_in_place() {
let mut p = Provenance::default();
p.set_model_name("qwen2-vl-7b");
p.set_indexer_version("findit-indexer-0.1.0");
assert_eq!(p.model_name(), "qwen2-vl-7b");
assert_eq!(p.indexer_version(), "findit-indexer-0.1.0");
}
#[test]
fn localized_text_new_is_empty_and_default_delegates() {
let t = LocalizedText::new();
assert!(t.is_empty());
assert_eq!(t.display(), "");
assert_eq!(LocalizedText::default(), t);
}
#[test]
fn localized_text_from_src_no_translation() {
let t = LocalizedText::from_src("Jane is eating");
assert!(!t.is_empty());
assert_eq!(t.src(), "Jane is eating");
assert_eq!(t.translated(), "");
assert_eq!(t.display(), "Jane is eating");
}
#[test]
fn localized_text_display_prefers_translation() {
let t = LocalizedText::from_src_translated("\u{4f60}\u{597d}", "Hello");
assert_eq!(t.src(), "\u{4f60}\u{597d}");
assert_eq!(t.translated(), "Hello");
assert_eq!(t.display(), "Hello");
}
#[test]
fn localized_text_translation_only_displays_translation() {
let t = LocalizedText::from_src_translated("", "Hello");
assert!(!t.is_empty());
assert_eq!(t.display(), "Hello");
}
#[test]
fn localized_text_builders_and_setters() {
let t = LocalizedText::default()
.with_src("Jane")
.with_translated("Jane");
assert_eq!(t.display(), "Jane");
let mut t = t;
t.set_translated("");
assert_eq!(t.translated(), "");
assert_eq!(t.display(), "Jane");
}
#[test]
fn index_progress_new_is_empty_and_default_delegates() {
let p = IndexProgress::new();
assert_eq!(p.total(), 0);
assert_eq!(p.indexed(), 0);
assert_eq!(p.failed(), 0);
assert!(!p.has_failures());
assert_eq!(IndexProgress::default(), p);
}
#[test]
fn index_progress_from_parts_is_unchecked() {
let p = IndexProgress::from_parts(2, 1, 0);
assert_eq!(p.total(), 2);
assert_eq!(p.indexed(), 1);
assert_eq!(p.failed(), 0);
}
#[test]
fn index_progress_try_new_validates_invariant() {
assert_eq!(
IndexProgress::try_new(2, 2, 1).err(),
Some(IndexProgressError::SumExceedsTotal)
);
assert!(IndexProgressError::SumExceedsTotal.is_sum_exceeds_total());
assert_eq!(
IndexProgress::try_new(u32::MAX, u32::MAX, 1).err(),
Some(IndexProgressError::SumOverflows)
);
assert!(IndexProgressError::SumOverflows.is_sum_overflows());
let ok = IndexProgress::try_new(5, 3, 2).unwrap();
assert_eq!(ok.total(), 5);
}
#[test]
fn index_progress_has_failures() {
let none = IndexProgress::try_new(5, 5, 0).unwrap();
let some = IndexProgress::try_new(5, 3, 2).unwrap();
assert!(!none.has_failures());
assert!(some.has_failures());
}
#[test]
fn index_progress_builders_and_setters() {
let p = IndexProgress::new()
.with_total(5)
.with_indexed(3)
.with_failed(1);
assert_eq!(p.total(), 5);
assert_eq!(p.indexed(), 3);
assert_eq!(p.failed(), 1);
let mut p = p;
p.set_total(10);
p.set_indexed(7);
p.set_failed(2);
assert_eq!(p.total(), 10);
assert_eq!(p.indexed(), 7);
assert_eq!(p.failed(), 2);
}
fn vfp_ts() -> JiffTimestamp {
JiffTimestamp::from_millisecond(1_700_000_000_000).expect("valid timestamp")
}
fn vfp_provenance() -> Provenance {
Provenance::from_parts("ecapa-tdnn", "v1.0.0", "", "findit-indexer-0.1.0")
}
#[test]
fn voice_fingerprint_try_new_rejects_nil_vector_id() {
let err = VoiceFingerprint::try_new(Uuid7::nil(), 192, vfp_ts(), Some(0.95), vfp_provenance())
.unwrap_err();
assert_eq!(err, VoiceFingerprintError::NilVectorId);
assert!(err.is_nil_vector_id());
}
#[test]
fn voice_fingerprint_try_new_rejects_zero_dimensions() {
let err = VoiceFingerprint::try_new(Uuid7::new(), 0, vfp_ts(), Some(0.95), vfp_provenance())
.unwrap_err();
assert_eq!(err, VoiceFingerprintError::ZeroDimensions);
assert!(err.is_zero_dimensions());
}
#[test]
fn voice_fingerprint_try_new_rejects_nan_inf_out_of_range_confidence() {
for bad in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, -0.01, 1.01] {
let err = VoiceFingerprint::try_new(Uuid7::new(), 192, vfp_ts(), Some(bad), vfp_provenance())
.unwrap_err();
assert_eq!(
err,
VoiceFingerprintError::ConfidenceOutOfRange,
"value {bad} should be rejected"
);
assert!(err.is_confidence_out_of_range());
}
for ok in [0.0_f32, 1.0_f32] {
VoiceFingerprint::try_new(Uuid7::new(), 192, vfp_ts(), Some(ok), vfp_provenance())
.expect("boundary confidence must be accepted");
}
VoiceFingerprint::try_new(Uuid7::new(), 192, vfp_ts(), None, vfp_provenance())
.expect("None confidence must be accepted");
}
#[test]
fn voice_fingerprint_try_new_happy_path_and_from_parts_round_trip() {
let vid = Uuid7::new();
let prov = vfp_provenance();
let f = VoiceFingerprint::try_new(vid, 192, vfp_ts(), Some(0.83), prov.clone())
.expect("valid construction must succeed");
assert_eq!(f.vector_id_ref(), &vid);
assert_eq!(f.dimensions(), 192);
assert_eq!(f.extracted_at(), vfp_ts());
assert_eq!(f.confidence(), Some(0.83));
assert_eq!(f.provenance_ref(), &prov);
let rebuilt = VoiceFingerprint::from_parts(
*f.vector_id_ref(),
f.dimensions(),
f.extracted_at(),
f.confidence(),
f.provenance_ref().clone(),
);
assert_eq!(rebuilt, f);
}
#[test]
fn voice_fingerprint_with_vector_id_replaces_field() {
let f = VoiceFingerprint::try_new(Uuid7::new(), 192, vfp_ts(), None, vfp_provenance()).unwrap();
let new_id = Uuid7::new();
let f = f.with_vector_id(new_id);
assert_eq!(f.vector_id_ref(), &new_id);
}
#[test]
fn voice_fingerprint_try_with_dimensions_validates() {
let f = VoiceFingerprint::try_new(Uuid7::new(), 192, vfp_ts(), None, vfp_provenance()).unwrap();
assert_eq!(
f.clone().try_with_dimensions(0).unwrap_err(),
VoiceFingerprintError::ZeroDimensions
);
let f = f.try_with_dimensions(256).unwrap();
assert_eq!(f.dimensions(), 256);
}
#[test]
fn voice_fingerprint_with_extracted_at_replaces_field() {
let f = VoiceFingerprint::try_new(Uuid7::new(), 192, vfp_ts(), None, vfp_provenance()).unwrap();
let later = JiffTimestamp::from_millisecond(1_800_000_000_000).unwrap();
let f = f.with_extracted_at(later);
assert_eq!(f.extracted_at(), later);
}
#[test]
fn voice_fingerprint_try_with_confidence_validates_and_clear() {
let f = VoiceFingerprint::try_new(Uuid7::new(), 192, vfp_ts(), None, vfp_provenance()).unwrap();
assert_eq!(
f.clone().try_with_confidence(f32::NAN).unwrap_err(),
VoiceFingerprintError::ConfidenceOutOfRange
);
assert_eq!(
f.clone().try_with_confidence(1.5).unwrap_err(),
VoiceFingerprintError::ConfidenceOutOfRange
);
let f = f.try_with_confidence(0.5).unwrap();
assert_eq!(f.confidence(), Some(0.5));
let mut f = f;
f.clear_confidence();
assert_eq!(f.confidence(), None);
}
#[test]
fn voice_fingerprint_with_provenance_replaces_field() {
let f =
VoiceFingerprint::try_new(Uuid7::new(), 192, vfp_ts(), None, Provenance::default()).unwrap();
let p2 = vfp_provenance();
let f = f.with_provenance(p2.clone());
assert_eq!(f.provenance_ref(), &p2);
}
#[test]
fn voice_fingerprint_in_place_setters_chain() {
let mut f =
VoiceFingerprint::try_new(Uuid7::new(), 192, vfp_ts(), None, Provenance::default()).unwrap();
let later = JiffTimestamp::from_millisecond(1_800_000_000_000).unwrap();
let new_id = Uuid7::new();
let p2 = vfp_provenance();
f.set_vector_id(new_id)
.set_extracted_at(later)
.set_provenance(p2.clone())
.try_set_dimensions(256)
.unwrap()
.try_set_confidence(0.75)
.unwrap();
assert_eq!(f.vector_id_ref(), &new_id);
assert_eq!(f.dimensions(), 256);
assert_eq!(f.extracted_at(), later);
assert_eq!(f.confidence(), Some(0.75));
assert_eq!(f.provenance_ref(), &p2);
assert_eq!(
f.try_set_dimensions(0).unwrap_err(),
VoiceFingerprintError::ZeroDimensions
);
assert_eq!(f.dimensions(), 256, "rejected setter must not mutate");
assert_eq!(
f.try_set_confidence(f32::INFINITY).unwrap_err(),
VoiceFingerprintError::ConfidenceOutOfRange
);
assert_eq!(
f.confidence(),
Some(0.75),
"rejected setter must not mutate"
);
}
}