1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::degradation::DegradationReceipt;
7use crate::receipt::ExactFallbackReceipt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum CodecProfile {
13 Raw,
15 Q8,
17 Q4,
19 Turbo,
21 Fib,
23 Polar,
25 Qjl,
27}
28
29impl CodecProfile {
30 pub fn default_degradation_threshold(&self) -> f64 {
32 match self {
33 CodecProfile::Raw => 0.0,
34 CodecProfile::Q8 => 0.05,
35 CodecProfile::Q4 => 0.10,
36 CodecProfile::Turbo => 0.08,
37 CodecProfile::Fib => 0.03,
38 CodecProfile::Polar => 0.06,
39 CodecProfile::Qjl => 0.04,
40 }
41 }
42
43 pub fn is_high_fidelity(&self) -> bool {
45 matches!(self, CodecProfile::Raw | CodecProfile::Fib)
46 }
47
48 pub fn is_asymmetric(&self) -> bool {
53 matches!(self, CodecProfile::Polar | CodecProfile::Qjl)
54 }
55
56 pub fn estimated_compression_ratio(&self) -> f64 {
58 match self {
59 CodecProfile::Raw => 1.0,
60 CodecProfile::Q8 => 2.0,
61 CodecProfile::Q4 => 4.0,
62 CodecProfile::Turbo => 3.0,
63 CodecProfile::Fib => 2.5,
64 CodecProfile::Polar => 1.0,
68 CodecProfile::Qjl => 8.0,
73 }
74 }
75}
76
77impl std::fmt::Display for CodecProfile {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 CodecProfile::Raw => write!(f, "raw"),
81 CodecProfile::Q8 => write!(f, "q8"),
82 CodecProfile::Q4 => write!(f, "q4"),
83 CodecProfile::Turbo => write!(f, "turbo"),
84 CodecProfile::Fib => write!(f, "fib"),
85 CodecProfile::Polar => write!(f, "polar"),
86 CodecProfile::Qjl => write!(f, "qjl"),
87 }
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct CodecDecision {
94 pub codec: CodecProfile,
96
97 pub exact_fallback: bool,
99
100 pub degradation_budget: f64,
102
103 pub receipt: CodecReceipt,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(untagged)]
110pub enum CodecReceipt {
111 ExactFallback(ExactFallbackReceipt),
113
114 Degradation(DegradationReceipt),
116
117 Direct {
119 timestamp: DateTime<Utc>,
121 profile: CodecProfile,
123 },
124}
125
126impl CodecDecision {
127 pub fn direct(codec: CodecProfile, degradation_budget: f64) -> Self {
129 Self {
130 codec,
131 exact_fallback: false,
132 degradation_budget,
133 receipt: CodecReceipt::Direct {
134 timestamp: Utc::now(),
135 profile: codec,
136 },
137 }
138 }
139
140 pub fn with_exact_fallback(
142 codec: CodecProfile,
143 degradation_budget: f64,
144 receipt: ExactFallbackReceipt,
145 ) -> Self {
146 Self {
147 codec,
148 exact_fallback: true,
149 degradation_budget,
150 receipt: CodecReceipt::ExactFallback(receipt),
151 }
152 }
153
154 pub fn with_degradation(
156 codec: CodecProfile,
157 degradation_budget: f64,
158 receipt: DegradationReceipt,
159 ) -> Self {
160 Self {
161 codec,
162 exact_fallback: false,
163 degradation_budget,
164 receipt: CodecReceipt::Degradation(receipt),
165 }
166 }
167
168 pub fn had_fallback(&self) -> bool {
170 self.exact_fallback || matches!(self.receipt, CodecReceipt::Degradation(_))
171 }
172
173 pub fn effective_profile(&self) -> CodecProfile {
175 self.codec
176 }
177}
178
179impl Default for CodecDecision {
180 fn default() -> Self {
181 Self::direct(CodecProfile::Raw, 0.0)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn codec_profile_ordering() {
191 assert!(CodecProfile::Raw.is_high_fidelity());
192 assert!(CodecProfile::Fib.is_high_fidelity());
193 assert!(!CodecProfile::Q4.is_high_fidelity());
194 }
195
196 #[test]
197 fn codec_decision_direct() {
198 let decision = CodecDecision::direct(CodecProfile::Q8, 0.05);
199 assert_eq!(decision.codec, CodecProfile::Q8);
200 assert!(!decision.exact_fallback);
201 assert!(!decision.had_fallback());
202 }
203
204 #[test]
205 fn compression_ratios() {
206 assert_eq!(CodecProfile::Raw.estimated_compression_ratio(), 1.0);
207 assert_eq!(CodecProfile::Q4.estimated_compression_ratio(), 4.0);
208 assert_eq!(CodecProfile::Turbo.estimated_compression_ratio(), 3.0);
209 }
210}