1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
19pub struct BackupSize(u64);
20
21impl BackupSize {
22 #[must_use]
24 pub const fn new(bytes: u64) -> Self {
25 Self(bytes)
26 }
27
28 #[must_use]
30 pub const fn get(self) -> u64 {
31 self.0
32 }
33
34 #[must_use]
36 pub const fn as_f64(self) -> f64 {
37 self.0 as f64
38 }
39
40 #[must_use]
42 pub fn as_mb(self) -> f64 {
43 self.0 as f64 / 1_048_576.0
44 }
45
46 #[must_use]
48 pub fn as_gb(self) -> f64 {
49 self.0 as f64 / 1_073_741_824.0
50 }
51}
52
53impl From<u64> for BackupSize {
54 fn from(bytes: u64) -> Self {
55 Self(bytes)
56 }
57}
58
59impl From<BackupSize> for u64 {
60 fn from(size: BackupSize) -> Self {
61 size.0
62 }
63}
64
65impl std::fmt::Display for BackupSize {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 if self.0 >= 1_073_741_824 {
68 write!(f, "{:.2} GB", self.as_gb())
69 } else if self.0 >= 1_048_576 {
70 write!(f, "{:.2} MB", self.as_mb())
71 } else if self.0 >= 1024 {
72 write!(f, "{:.2} KB", self.0 as f64 / 1024.0)
73 } else {
74 write!(f, "{} B", self.0)
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
91pub struct PredictionConfidence(f64);
92
93impl PredictionConfidence {
94 pub fn new(value: f64) -> Result<Self, String> {
100 if !(0.0..=1.0).contains(&value) {
101 return Err(format!(
102 "信頼度は0.0から1.0の範囲である必要があります: {}",
103 value
104 ));
105 }
106 Ok(Self(value))
107 }
108
109 #[must_use]
111 pub const fn get(self) -> f64 {
112 self.0
113 }
114
115 #[must_use]
117 pub fn as_percentage(self) -> f64 {
118 self.0 * 100.0
119 }
120
121 #[must_use]
123 pub fn is_high(self) -> bool {
124 self.0 >= 0.8
125 }
126
127 #[must_use]
129 pub fn is_medium(self) -> bool {
130 (0.5..0.8).contains(&self.0)
131 }
132
133 #[must_use]
135 pub fn is_low(self) -> bool {
136 self.0 < 0.5
137 }
138}
139
140impl std::fmt::Display for PredictionConfidence {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 write!(f, "{:.1}%", self.as_percentage())
143 }
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
158pub struct FileImportance(u8);
159
160impl FileImportance {
161 pub fn new(value: u8) -> Result<Self, String> {
167 if value > 100 {
168 return Err(format!(
169 "重要度は0から100の範囲である必要があります: {}",
170 value
171 ));
172 }
173 Ok(Self(value))
174 }
175
176 #[must_use]
178 pub const fn get(self) -> u8 {
179 self.0
180 }
181
182 #[must_use]
184 pub const fn is_high(self) -> bool {
185 self.0 >= 80
186 }
187
188 #[must_use]
190 pub const fn is_medium(self) -> bool {
191 self.0 >= 40 && self.0 < 80
192 }
193
194 #[must_use]
196 pub const fn is_low(self) -> bool {
197 self.0 < 40
198 }
199}
200
201impl std::fmt::Display for FileImportance {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 let label = if self.is_high() {
204 "高"
205 } else if self.is_medium() {
206 "中"
207 } else {
208 "低"
209 };
210 write!(f, "{}/100 ({})", self.0, label)
211 }
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
225pub struct DiskCapacity(u64);
226
227impl DiskCapacity {
228 #[must_use]
230 pub const fn new(bytes: u64) -> Self {
231 Self(bytes)
232 }
233
234 #[must_use]
236 pub const fn get(self) -> u64 {
237 self.0
238 }
239
240 #[must_use]
242 pub fn as_gb(self) -> f64 {
243 self.0 as f64 / 1_073_741_824.0
244 }
245
246 #[must_use]
248 pub fn as_tb(self) -> f64 {
249 self.0 as f64 / 1_099_511_627_776.0
250 }
251
252 #[must_use]
254 pub fn usage_ratio(self, used: DiskCapacity) -> f64 {
255 if self.0 == 0 {
256 return 0.0;
257 }
258 used.0 as f64 / self.0 as f64
259 }
260}
261
262impl From<u64> for DiskCapacity {
263 fn from(bytes: u64) -> Self {
264 Self(bytes)
265 }
266}
267
268impl From<DiskCapacity> for u64 {
269 fn from(capacity: DiskCapacity) -> Self {
270 capacity.0
271 }
272}
273
274impl std::fmt::Display for DiskCapacity {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 if self.0 >= 1_099_511_627_776 {
277 write!(f, "{:.2} TB", self.as_tb())
278 } else if self.0 >= 1_073_741_824 {
279 write!(f, "{:.2} GB", self.as_gb())
280 } else if self.0 >= 1_048_576 {
281 write!(f, "{:.2} MB", self.0 as f64 / 1_048_576.0)
282 } else {
283 write!(f, "{} bytes", self.0)
284 }
285 }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
300pub struct FailureRate(f64);
301
302impl FailureRate {
303 pub fn new(value: f64) -> Result<Self, String> {
309 if !(0.0..=1.0).contains(&value) {
310 return Err(format!(
311 "失敗率は0.0から1.0の範囲である必要があります: {}",
312 value
313 ));
314 }
315 Ok(Self(value))
316 }
317
318 #[must_use]
320 pub const fn get(self) -> f64 {
321 self.0
322 }
323
324 #[must_use]
326 pub fn as_percentage(self) -> f64 {
327 self.0 * 100.0
328 }
329
330 #[must_use]
332 pub fn is_high_risk(self) -> bool {
333 self.0 >= 0.2
334 }
335
336 #[must_use]
338 pub fn is_medium_risk(self) -> bool {
339 (0.05..0.2).contains(&self.0)
340 }
341
342 #[must_use]
344 pub fn is_low_risk(self) -> bool {
345 self.0 < 0.05
346 }
347}
348
349impl std::fmt::Display for FailureRate {
350 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351 write!(f, "{:.2}%", self.as_percentage())
352 }
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct TimeSeriesPoint {
368 timestamp: chrono::DateTime<chrono::Utc>,
369 value: f64,
370}
371
372impl TimeSeriesPoint {
373 #[must_use]
375 pub const fn new(timestamp: chrono::DateTime<chrono::Utc>, value: f64) -> Self {
376 Self { timestamp, value }
377 }
378
379 #[must_use]
381 pub const fn timestamp(&self) -> &chrono::DateTime<chrono::Utc> {
382 &self.timestamp
383 }
384
385 #[must_use]
387 pub const fn value(&self) -> f64 {
388 self.value
389 }
390}
391
392impl std::fmt::Display for TimeSeriesPoint {
393 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394 write!(
395 f,
396 "{}: {:.2}",
397 self.timestamp.format("%Y-%m-%d %H:%M:%S"),
398 self.value
399 )
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn test_backup_size() {
409 let size = BackupSize::new(1_048_576);
410 assert_eq!(size.get(), 1_048_576);
411 assert_eq!(size.as_mb(), 1.0);
412 assert_eq!(size.as_gb(), 1.0 / 1024.0);
413 }
414
415 #[test]
416 fn test_prediction_confidence_valid() {
417 let conf = PredictionConfidence::new(0.95).unwrap();
418 assert_eq!(conf.get(), 0.95);
419 assert_eq!(conf.as_percentage(), 95.0);
420 assert!(conf.is_high());
421 }
422
423 #[test]
424 fn test_prediction_confidence_invalid() {
425 assert!(PredictionConfidence::new(-0.1).is_err());
426 assert!(PredictionConfidence::new(1.5).is_err());
427 }
428
429 #[test]
430 fn test_file_importance() {
431 let high = FileImportance::new(85).unwrap();
432 assert!(high.is_high());
433 assert!(!high.is_medium());
434
435 let medium = FileImportance::new(50).unwrap();
436 assert!(medium.is_medium());
437
438 let low = FileImportance::new(20).unwrap();
439 assert!(low.is_low());
440 }
441
442 #[test]
443 fn test_file_importance_invalid() {
444 assert!(FileImportance::new(101).is_err());
445 }
446
447 #[test]
448 fn test_disk_capacity() {
449 let capacity = DiskCapacity::new(1_073_741_824); assert_eq!(capacity.as_gb(), 1.0);
451
452 let used = DiskCapacity::new(536_870_912); assert_eq!(capacity.usage_ratio(used), 0.5);
454 }
455
456 #[test]
457 fn test_failure_rate() {
458 let rate = FailureRate::new(0.05).unwrap();
459 assert_eq!(rate.get(), 0.05);
460 assert_eq!(rate.as_percentage(), 5.0);
461 assert!(rate.is_medium_risk());
462 }
463
464 #[test]
465 fn test_failure_rate_invalid() {
466 assert!(FailureRate::new(-0.1).is_err());
467 assert!(FailureRate::new(1.5).is_err());
468 }
469
470 #[test]
471 fn test_time_series_point() {
472 use chrono::Utc;
473 let now = Utc::now();
474 let point = TimeSeriesPoint::new(now, 1024.0);
475 assert_eq!(point.timestamp(), &now);
476 assert_eq!(point.value(), 1024.0);
477 }
478
479 #[test]
480 fn test_backup_size_display() {
481 assert_eq!(BackupSize::new(500).to_string(), "500 B");
482 assert_eq!(BackupSize::new(2048).to_string(), "2.00 KB");
483 assert_eq!(BackupSize::new(1_048_576).to_string(), "1.00 MB");
484 assert_eq!(BackupSize::new(1_073_741_824).to_string(), "1.00 GB");
485 }
486
487 #[test]
488 fn test_prediction_confidence_display() {
489 let conf = PredictionConfidence::new(0.85).unwrap();
490 assert_eq!(conf.to_string(), "85.0%");
491 }
492
493 #[test]
494 fn test_file_importance_display() {
495 let high = FileImportance::new(90).unwrap();
496 assert_eq!(high.to_string(), "90/100 (高)");
497
498 let medium = FileImportance::new(60).unwrap();
499 assert_eq!(medium.to_string(), "60/100 (中)");
500
501 let low = FileImportance::new(20).unwrap();
502 assert_eq!(low.to_string(), "20/100 (低)");
503 }
504
505 #[test]
506 fn test_disk_capacity_display() {
507 assert_eq!(DiskCapacity::new(500).to_string(), "500 bytes");
508 assert_eq!(DiskCapacity::new(1_048_576).to_string(), "1.00 MB");
509 assert_eq!(DiskCapacity::new(1_073_741_824).to_string(), "1.00 GB");
510 assert_eq!(DiskCapacity::new(1_099_511_627_776).to_string(), "1.00 TB");
511 }
512
513 #[test]
514 fn test_failure_rate_display() {
515 let rate = FailureRate::new(0.15).unwrap();
516 assert_eq!(rate.to_string(), "15.00%");
517 }
518
519 #[test]
520 fn test_time_series_point_display() {
521 use chrono::Utc;
522 let point = TimeSeriesPoint::new(Utc::now(), 123.456);
523 let display = point.to_string();
524 assert!(display.contains("123.46"));
525 }
526}