use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::degradation::DegradationReceipt;
use crate::receipt::ExactFallbackReceipt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CodecProfile {
Raw,
Q8,
Q4,
Turbo,
Fib,
Polar,
Qjl,
}
impl CodecProfile {
pub fn default_degradation_threshold(&self) -> f64 {
match self {
CodecProfile::Raw => 0.0,
CodecProfile::Q8 => 0.05,
CodecProfile::Q4 => 0.10,
CodecProfile::Turbo => 0.08,
CodecProfile::Fib => 0.03,
CodecProfile::Polar => 0.06,
CodecProfile::Qjl => 0.04,
}
}
pub fn is_high_fidelity(&self) -> bool {
matches!(self, CodecProfile::Raw | CodecProfile::Fib)
}
pub fn is_asymmetric(&self) -> bool {
matches!(self, CodecProfile::Polar | CodecProfile::Qjl)
}
pub fn estimated_compression_ratio(&self) -> f64 {
match self {
CodecProfile::Raw => 1.0,
CodecProfile::Q8 => 2.0,
CodecProfile::Q4 => 4.0,
CodecProfile::Turbo => 3.0,
CodecProfile::Fib => 2.5,
CodecProfile::Polar => 1.0,
CodecProfile::Qjl => 8.0,
}
}
}
impl std::fmt::Display for CodecProfile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CodecProfile::Raw => write!(f, "raw"),
CodecProfile::Q8 => write!(f, "q8"),
CodecProfile::Q4 => write!(f, "q4"),
CodecProfile::Turbo => write!(f, "turbo"),
CodecProfile::Fib => write!(f, "fib"),
CodecProfile::Polar => write!(f, "polar"),
CodecProfile::Qjl => write!(f, "qjl"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodecDecision {
pub codec: CodecProfile,
pub exact_fallback: bool,
pub degradation_budget: f64,
pub receipt: CodecReceipt,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CodecReceipt {
ExactFallback(ExactFallbackReceipt),
Degradation(DegradationReceipt),
Direct {
timestamp: DateTime<Utc>,
profile: CodecProfile,
},
}
impl CodecDecision {
pub fn direct(codec: CodecProfile, degradation_budget: f64) -> Self {
Self {
codec,
exact_fallback: false,
degradation_budget,
receipt: CodecReceipt::Direct {
timestamp: Utc::now(),
profile: codec,
},
}
}
pub fn with_exact_fallback(
codec: CodecProfile,
degradation_budget: f64,
receipt: ExactFallbackReceipt,
) -> Self {
Self {
codec,
exact_fallback: true,
degradation_budget,
receipt: CodecReceipt::ExactFallback(receipt),
}
}
pub fn with_degradation(
codec: CodecProfile,
degradation_budget: f64,
receipt: DegradationReceipt,
) -> Self {
Self {
codec,
exact_fallback: false,
degradation_budget,
receipt: CodecReceipt::Degradation(receipt),
}
}
pub fn had_fallback(&self) -> bool {
self.exact_fallback || matches!(self.receipt, CodecReceipt::Degradation(_))
}
pub fn effective_profile(&self) -> CodecProfile {
self.codec
}
}
impl Default for CodecDecision {
fn default() -> Self {
Self::direct(CodecProfile::Raw, 0.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn codec_profile_ordering() {
assert!(CodecProfile::Raw.is_high_fidelity());
assert!(CodecProfile::Fib.is_high_fidelity());
assert!(!CodecProfile::Q4.is_high_fidelity());
}
#[test]
fn codec_decision_direct() {
let decision = CodecDecision::direct(CodecProfile::Q8, 0.05);
assert_eq!(decision.codec, CodecProfile::Q8);
assert!(!decision.exact_fallback);
assert!(!decision.had_fallback());
}
#[test]
fn compression_ratios() {
assert_eq!(CodecProfile::Raw.estimated_compression_ratio(), 1.0);
assert_eq!(CodecProfile::Q4.estimated_compression_ratio(), 4.0);
assert_eq!(CodecProfile::Turbo.estimated_compression_ratio(), 3.0);
}
}