1use serde::{Deserialize, Serialize};
4
5use crate::decision::{CodecDecision, CodecProfile};
6use crate::error::GovernorError;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
10pub enum ContentType {
11 Text,
13 Image,
15 Audio,
17 Video,
19 Structured,
21 Model,
23 #[default]
25 Other,
26}
27
28impl std::fmt::Display for ContentType {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 ContentType::Text => write!(f, "text"),
32 ContentType::Image => write!(f, "image"),
33 ContentType::Audio => write!(f, "audio"),
34 ContentType::Video => write!(f, "video"),
35 ContentType::Structured => write!(f, "structured"),
36 ContentType::Model => write!(f, "model"),
37 ContentType::Other => write!(f, "other"),
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
44pub enum AdmissibilityClass {
45 Critical,
47 HighPriority,
49 #[default]
51 Standard,
52 Compressible,
54 BestEffort,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct GovernanceRequest {
61 pub content_type: ContentType,
63
64 pub size_bytes: u64,
66
67 pub accuracy_requirement: f64,
69
70 pub latency_tolerance_ms: u64,
72
73 pub admissibility: AdmissibilityClass,
75}
76
77impl Default for GovernanceRequest {
78 fn default() -> Self {
79 Self {
80 content_type: ContentType::Other,
81 size_bytes: 0,
82 accuracy_requirement: 0.95,
83 latency_tolerance_ms: 1000,
84 admissibility: AdmissibilityClass::Standard,
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct GovernancePolicy {
92 max_degradation: f64,
94
95 small_content_threshold: u64,
97
98 raw_min_accuracy: f64,
100
101 name: String,
103}
104
105impl Default for GovernancePolicy {
106 fn default() -> Self {
107 Self {
108 max_degradation: 0.1,
109 small_content_threshold: 256,
110 raw_min_accuracy: 0.99,
111 name: "default".to_string(),
112 }
113 }
114}
115
116impl GovernancePolicy {
117 pub fn new(max_degradation: f64, small_content_threshold: u64, raw_min_accuracy: f64) -> Self {
119 Self {
120 max_degradation,
121 small_content_threshold,
122 raw_min_accuracy,
123 name: "custom".to_string(),
124 }
125 }
126
127 pub fn storage_efficient() -> Self {
129 Self {
130 max_degradation: 0.15,
131 small_content_threshold: 512,
132 raw_min_accuracy: 0.90,
133 name: "storage_efficient".to_string(),
134 }
135 }
136
137 pub fn low_latency() -> Self {
139 Self {
140 max_degradation: 0.12,
141 small_content_threshold: 1024,
142 raw_min_accuracy: 0.92,
143 name: "low_latency".to_string(),
144 }
145 }
146
147 pub fn accuracy_oriented() -> Self {
149 Self {
150 max_degradation: 0.05,
151 small_content_threshold: 128,
152 raw_min_accuracy: 0.999,
153 name: "accuracy_oriented".to_string(),
154 }
155 }
156
157 pub fn evaluate(&self, request: GovernanceRequest) -> Result<CodecDecision, GovernorError> {
159 if request.size_bytes <= self.small_content_threshold
161 && request.admissibility != AdmissibilityClass::Critical
162 {
163 return Ok(CodecDecision::direct(
164 CodecProfile::Raw,
165 self.max_degradation,
166 ));
167 }
168
169 if request.accuracy_requirement >= self.raw_min_accuracy
171 || request.admissibility == AdmissibilityClass::Critical
172 {
173 return Ok(CodecDecision::direct(CodecProfile::Raw, 0.0));
174 }
175
176 let codec = self.select_codec(&request)?;
178 let degradation = codec.default_degradation_threshold();
179
180 Ok(CodecDecision::direct(codec, degradation))
181 }
182
183 fn select_codec(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
185 match request.content_type {
186 ContentType::Text => self.select_for_text(request),
187 ContentType::Image => self.select_for_image(request),
188 ContentType::Audio => self.select_for_audio(request),
189 ContentType::Video => self.select_for_video(request),
190 ContentType::Structured => self.select_for_structured(request),
191 ContentType::Model => self.select_for_model(request),
192 ContentType::Other => Ok(CodecProfile::Q8),
193 }
194 }
195
196 fn select_for_text(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
197 if request.accuracy_requirement >= 0.98 {
198 Ok(CodecProfile::Raw)
199 } else if request.size_bytes > 1_000_000 {
200 Ok(CodecProfile::Turbo)
201 } else if request.latency_tolerance_ms < 50 {
202 if request.size_bytes > 50_000 {
208 Ok(CodecProfile::Qjl)
209 } else {
210 Ok(CodecProfile::Polar)
211 }
212 } else {
213 Ok(CodecProfile::Q8)
214 }
215 }
216
217 fn select_for_image(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
218 if request.accuracy_requirement >= 0.95 {
219 Ok(CodecProfile::Q8)
220 } else if request.size_bytes > 5_000_000 {
221 Ok(CodecProfile::Q4)
222 } else {
223 Ok(CodecProfile::Q8)
224 }
225 }
226
227 fn select_for_audio(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
228 if request.latency_tolerance_ms < 100 {
229 Ok(CodecProfile::Turbo)
230 } else if request.accuracy_requirement >= 0.97 {
231 Ok(CodecProfile::Fib)
232 } else {
233 Ok(CodecProfile::Q8)
234 }
235 }
236
237 fn select_for_video(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
238 if request.latency_tolerance_ms < 50 {
239 Ok(CodecProfile::Turbo)
240 } else {
241 Ok(CodecProfile::Q4)
242 }
243 }
244
245 fn select_for_structured(
246 &self,
247 request: &GovernanceRequest,
248 ) -> Result<CodecProfile, GovernorError> {
249 if request.accuracy_requirement >= 0.99 {
250 Ok(CodecProfile::Raw)
251 } else {
252 Ok(CodecProfile::Q8)
253 }
254 }
255
256 fn select_for_model(&self, request: &GovernanceRequest) -> Result<CodecProfile, GovernorError> {
257 if request.admissibility == AdmissibilityClass::Critical {
258 Ok(CodecProfile::Raw)
259 } else if request.accuracy_requirement >= 0.98 {
260 Ok(CodecProfile::Fib)
261 } else if request.size_bytes > 100_000_000 {
262 Ok(CodecProfile::Q4)
263 } else {
264 Ok(CodecProfile::Q8)
265 }
266 }
267
268 pub fn name(&self) -> &str {
270 &self.name
271 }
272
273 pub fn max_degradation(&self) -> f64 {
275 self.max_degradation
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn default_policy_evaluation() {
285 let policy = GovernancePolicy::default();
286 let request = GovernanceRequest::default();
287
288 let result = policy.evaluate(request);
289 assert!(result.is_ok());
290 }
291
292 #[test]
293 fn small_content_bypass() {
294 let policy = GovernancePolicy::default();
295 let request = GovernanceRequest {
296 size_bytes: 100,
297 admissibility: AdmissibilityClass::Standard,
298 ..Default::default()
299 };
300
301 let decision = policy.evaluate(request).unwrap();
302 assert_eq!(decision.codec, CodecProfile::Raw);
303 }
304
305 #[test]
306 fn critical_content_gets_raw() {
307 let policy = GovernancePolicy::default();
308 let request = GovernanceRequest {
309 admissibility: AdmissibilityClass::Critical,
310 accuracy_requirement: 0.5,
311 ..Default::default()
312 };
313
314 let decision = policy.evaluate(request).unwrap();
315 assert_eq!(decision.codec, CodecProfile::Raw);
316 }
317
318 #[test]
319 fn image_content_routing() {
320 let policy = GovernancePolicy::default();
321
322 let request = GovernanceRequest {
324 content_type: ContentType::Image,
325 size_bytes: 10_000_000,
326 accuracy_requirement: 0.8,
327 ..Default::default()
328 };
329
330 let decision = policy.evaluate(request).unwrap();
331 assert_eq!(decision.codec, CodecProfile::Q4);
332 }
333
334 #[test]
335 fn model_content_routing() {
336 let policy = GovernancePolicy::default();
337
338 let request = GovernanceRequest {
340 content_type: ContentType::Model,
341 admissibility: AdmissibilityClass::Critical,
342 ..Default::default()
343 };
344
345 let decision = policy.evaluate(request).unwrap();
346 assert_eq!(decision.codec, CodecProfile::Raw);
347 }
348
349 #[test]
350 fn low_latency_audio_gets_turbo() {
351 let policy = GovernancePolicy::low_latency();
354 let request = GovernanceRequest {
355 content_type: ContentType::Audio,
356 size_bytes: 2000, latency_tolerance_ms: 50,
358 accuracy_requirement: 0.8,
359 ..Default::default()
360 };
361
362 let decision = policy.evaluate(request).unwrap();
363 assert_eq!(decision.codec, CodecProfile::Turbo);
364 }
365
366 #[test]
367 fn policy_presets() {
368 let storage = GovernancePolicy::storage_efficient();
369 assert_eq!(storage.name(), "storage_efficient");
370
371 let latency = GovernancePolicy::low_latency();
372 assert_eq!(latency.name(), "low_latency");
373
374 let accuracy = GovernancePolicy::accuracy_oriented();
375 assert_eq!(accuracy.name(), "accuracy_oriented");
376 }
377}