1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use uuid::Uuid;
10
11pub const SCHEMA_VERSION: &str = "1.0.0";
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct CommonEventFields {
17 #[serde(default = "Uuid::new_v4")]
19 pub event_id: Uuid,
20
21 pub timestamp: DateTime<Utc>,
23
24 pub source_module: SourceModule,
26
27 pub event_type: EventType,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub correlation_id: Option<Uuid>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub parent_event_id: Option<Uuid>,
37
38 #[serde(default = "default_schema_version")]
40 pub schema_version: String,
41
42 pub severity: Severity,
44
45 pub environment: String,
47
48 #[serde(default)]
50 pub tags: HashMap<String, String>,
51}
52
53fn default_schema_version() -> String {
54 SCHEMA_VERSION.to_string()
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
59#[serde(rename_all = "kebab-case")]
60pub enum SourceModule {
61 LlmObservatory,
63
64 LlmSentinel,
66
67 LlmCostOps,
69
70 LlmGovernanceDashboard,
72
73 LlmRegistry,
75
76 LlmPolicyEngine,
78
79 LlmAnalyticsHub,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
85#[serde(rename_all = "snake_case")]
86pub enum EventType {
87 Telemetry,
89
90 Security,
92
93 Cost,
95
96 Governance,
98
99 Lifecycle,
101
102 Audit,
104
105 Alert,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
111#[serde(rename_all = "lowercase")]
112pub enum Severity {
113 Debug,
114 Info,
115 Warning,
116 Error,
117 Critical,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct AnalyticsEvent {
123 #[serde(flatten)]
125 pub common: CommonEventFields,
126
127 pub payload: EventPayload,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(tag = "payload_type", content = "data")]
134pub enum EventPayload {
135 #[serde(rename = "telemetry")]
137 Telemetry(TelemetryPayload),
138
139 #[serde(rename = "security")]
141 Security(SecurityPayload),
142
143 #[serde(rename = "cost")]
145 Cost(CostPayload),
146
147 #[serde(rename = "governance")]
149 Governance(GovernancePayload),
150
151 #[serde(rename = "custom")]
153 Custom(CustomPayload),
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(tag = "telemetry_type")]
163pub enum TelemetryPayload {
164 #[serde(rename = "latency")]
166 Latency(LatencyMetrics),
167
168 #[serde(rename = "throughput")]
170 Throughput(ThroughputMetrics),
171
172 #[serde(rename = "error_rate")]
174 ErrorRate(ErrorRateMetrics),
175
176 #[serde(rename = "token_usage")]
178 TokenUsage(TokenUsageMetrics),
179
180 #[serde(rename = "model_performance")]
182 ModelPerformance(ModelPerformanceMetrics),
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct LatencyMetrics {
187 pub model_id: String,
189
190 pub request_id: String,
192
193 pub total_latency_ms: f64,
195
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub ttft_ms: Option<f64>,
199
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub tokens_per_second: Option<f64>,
203
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub breakdown: Option<LatencyBreakdown>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct LatencyBreakdown {
211 pub queue_time_ms: f64,
212 pub processing_time_ms: f64,
213 pub network_time_ms: f64,
214 pub other_ms: f64,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct ThroughputMetrics {
219 pub model_id: String,
220 pub requests_per_second: f64,
221 pub tokens_per_second: f64,
222 pub concurrent_requests: u32,
223 pub window_duration_seconds: u32,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct ErrorRateMetrics {
228 pub model_id: String,
229 pub total_requests: u64,
230 pub failed_requests: u64,
231 pub error_rate_percent: f64,
232 pub error_breakdown: HashMap<String, u64>,
233 pub window_duration_seconds: u32,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct TokenUsageMetrics {
238 pub model_id: String,
239 pub request_id: String,
240 pub prompt_tokens: u32,
241 pub completion_tokens: u32,
242 pub total_tokens: u32,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct ModelPerformanceMetrics {
247 pub model_id: String,
248 pub accuracy: Option<f64>,
249 pub quality_score: Option<f64>,
250 pub user_satisfaction: Option<f64>,
251 pub custom_metrics: HashMap<String, f64>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
260#[serde(tag = "security_type")]
261pub enum SecurityPayload {
262 #[serde(rename = "threat")]
264 Threat(ThreatEvent),
265
266 #[serde(rename = "vulnerability")]
268 Vulnerability(VulnerabilityEvent),
269
270 #[serde(rename = "compliance_violation")]
272 ComplianceViolation(ComplianceViolationEvent),
273
274 #[serde(rename = "auth")]
276 Auth(AuthEvent),
277
278 #[serde(rename = "privacy")]
280 Privacy(PrivacyEvent),
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct ThreatEvent {
285 pub threat_id: String,
286 pub threat_type: ThreatType,
287 pub threat_level: ThreatLevel,
288 pub source_ip: Option<String>,
289 pub target_resource: String,
290 pub attack_vector: String,
291 pub mitigation_status: MitigationStatus,
292 pub indicators_of_compromise: Vec<String>,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
296#[serde(rename_all = "snake_case")]
297pub enum ThreatType {
298 PromptInjection,
299 DataExfiltration,
300 ModelPoisoning,
301 DenialOfService,
302 UnauthorizedAccess,
303 MaliciousInput,
304 Other(String),
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
308#[serde(rename_all = "lowercase")]
309pub enum ThreatLevel {
310 Low,
311 Medium,
312 High,
313 Critical,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
317#[serde(rename_all = "snake_case")]
318pub enum MitigationStatus {
319 Detected,
320 Blocked,
321 Mitigated,
322 Investigating,
323 Resolved,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct VulnerabilityEvent {
328 pub vulnerability_id: String,
329 pub cve_id: Option<String>,
330 pub severity_score: f64,
331 pub affected_component: String,
332 pub description: String,
333 pub remediation_status: RemediationStatus,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
337#[serde(rename_all = "snake_case")]
338pub enum RemediationStatus {
339 Identified,
340 PatchAvailable,
341 Patching,
342 Patched,
343 Accepted,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct ComplianceViolationEvent {
348 pub violation_id: String,
349 pub regulation: String,
350 pub requirement: String,
351 pub violation_description: String,
352 pub affected_data_types: Vec<String>,
353 pub remediation_required: bool,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct AuthEvent {
358 pub user_id: String,
359 pub action: AuthAction,
360 pub resource: String,
361 pub success: bool,
362 pub failure_reason: Option<String>,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
366#[serde(rename_all = "snake_case")]
367pub enum AuthAction {
368 Login,
369 Logout,
370 AccessAttempt,
371 PermissionDenied,
372 TokenGenerated,
373 TokenRevoked,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct PrivacyEvent {
378 pub data_type: String,
379 pub operation: PrivacyOperation,
380 pub user_consent: bool,
381 pub data_subjects: Vec<String>,
382 pub purpose: String,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
386#[serde(rename_all = "snake_case")]
387pub enum PrivacyOperation {
388 DataAccess,
389 DataCollection,
390 DataSharing,
391 DataDeletion,
392 ConsentUpdate,
393}
394
395#[derive(Debug, Clone, Serialize, Deserialize)]
401#[serde(tag = "cost_type")]
402pub enum CostPayload {
403 #[serde(rename = "token_cost")]
405 TokenCost(TokenCostEvent),
406
407 #[serde(rename = "api_cost")]
409 ApiCost(ApiCostEvent),
410
411 #[serde(rename = "resource_consumption")]
413 ResourceConsumption(ResourceConsumptionEvent),
414
415 #[serde(rename = "budget_alert")]
417 BudgetAlert(BudgetAlertEvent),
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct TokenCostEvent {
422 pub model_id: String,
423 pub request_id: String,
424 pub prompt_tokens: u32,
425 pub completion_tokens: u32,
426 pub total_tokens: u32,
427 pub cost_per_prompt_token: f64,
428 pub cost_per_completion_token: f64,
429 pub total_cost_usd: f64,
430 pub currency: String,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct ApiCostEvent {
435 pub provider: String,
436 pub api_endpoint: String,
437 pub request_count: u64,
438 pub cost_per_request: f64,
439 pub total_cost_usd: f64,
440 pub billing_period: String,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct ResourceConsumptionEvent {
445 pub resource_type: ResourceType,
446 pub resource_id: String,
447 pub quantity: f64,
448 pub unit: String,
449 pub cost_usd: f64,
450 pub utilization_percent: f64,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
454#[serde(rename_all = "snake_case")]
455pub enum ResourceType {
456 Compute,
457 Storage,
458 Network,
459 Memory,
460 Gpu,
461 Other(String),
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
465pub struct BudgetAlertEvent {
466 pub budget_id: String,
467 pub budget_name: String,
468 pub budget_limit_usd: f64,
469 pub current_spend_usd: f64,
470 pub threshold_percent: f64,
471 pub alert_type: BudgetAlertType,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
475#[serde(rename_all = "snake_case")]
476pub enum BudgetAlertType {
477 Warning,
478 Critical,
479 Exceeded,
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize)]
488#[serde(tag = "governance_type")]
489pub enum GovernancePayload {
490 #[serde(rename = "policy_violation")]
492 PolicyViolation(PolicyViolationEvent),
493
494 #[serde(rename = "audit_trail")]
496 AuditTrail(AuditTrailEvent),
497
498 #[serde(rename = "compliance_check")]
500 ComplianceCheck(ComplianceCheckEvent),
501
502 #[serde(rename = "data_lineage")]
504 DataLineage(DataLineageEvent),
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct PolicyViolationEvent {
509 pub policy_id: String,
510 pub policy_name: String,
511 pub violation_description: String,
512 pub violated_rules: Vec<String>,
513 pub resource_id: String,
514 pub user_id: Option<String>,
515 pub severity: PolicyViolationSeverity,
516 pub auto_remediated: bool,
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
520#[serde(rename_all = "lowercase")]
521pub enum PolicyViolationSeverity {
522 Low,
523 Medium,
524 High,
525 Critical,
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct AuditTrailEvent {
530 pub action: String,
531 pub actor: String,
532 pub resource_type: String,
533 pub resource_id: String,
534 pub changes: HashMap<String, serde_json::Value>,
535 pub ip_address: Option<String>,
536 pub user_agent: Option<String>,
537}
538
539#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct ComplianceCheckEvent {
541 pub check_id: String,
542 pub framework: String,
543 pub controls_checked: Vec<String>,
544 pub passed: bool,
545 pub findings: Vec<ComplianceFinding>,
546 pub score: f64,
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize)]
550pub struct ComplianceFinding {
551 pub control_id: String,
552 pub status: ComplianceStatus,
553 pub description: String,
554 pub evidence: Option<String>,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
558#[serde(rename_all = "snake_case")]
559pub enum ComplianceStatus {
560 Pass,
561 Fail,
562 NotApplicable,
563 Manual,
564}
565
566#[derive(Debug, Clone, Serialize, Deserialize)]
567pub struct DataLineageEvent {
568 pub data_asset_id: String,
569 pub operation: DataOperation,
570 pub source: Option<String>,
571 pub destination: Option<String>,
572 pub transformation: Option<String>,
573 pub lineage_path: Vec<String>,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
577#[serde(rename_all = "snake_case")]
578pub enum DataOperation {
579 Create,
580 Read,
581 Update,
582 Delete,
583 Transform,
584 Aggregate,
585}
586
587#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct CustomPayload {
594 pub custom_type: String,
595 pub data: serde_json::Value,
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601 use pretty_assertions::assert_eq;
602
603 #[test]
608 fn test_common_event_fields_default_values() {
609 let common = CommonEventFields {
610 event_id: Uuid::new_v4(),
611 timestamp: Utc::now(),
612 source_module: SourceModule::LlmAnalyticsHub,
613 event_type: EventType::Telemetry,
614 correlation_id: None,
615 parent_event_id: None,
616 schema_version: SCHEMA_VERSION.to_string(),
617 severity: Severity::Info,
618 environment: "test".to_string(),
619 tags: HashMap::new(),
620 };
621
622 assert_eq!(common.schema_version, "1.0.0");
623 assert!(common.correlation_id.is_none());
624 assert!(common.parent_event_id.is_none());
625 assert!(common.tags.is_empty());
626 }
627
628 #[test]
629 fn test_severity_ordering() {
630 assert!(Severity::Critical > Severity::Error);
631 assert!(Severity::Error > Severity::Warning);
632 assert!(Severity::Warning > Severity::Info);
633 assert!(Severity::Info > Severity::Debug);
634 }
635
636 #[test]
637 fn test_source_module_serialization() {
638 let modules = vec![
639 SourceModule::LlmObservatory,
640 SourceModule::LlmSentinel,
641 SourceModule::LlmCostOps,
642 SourceModule::LlmGovernanceDashboard,
643 ];
644
645 for module in modules {
646 let json = serde_json::to_string(&module).unwrap();
647 let deserialized: SourceModule = serde_json::from_str(&json).unwrap();
648 assert_eq!(module, deserialized);
649 }
650 }
651
652 #[test]
657 fn test_latency_metrics_complete() {
658 let latency = LatencyMetrics {
659 model_id: "gpt-4".to_string(),
660 request_id: "req-123".to_string(),
661 total_latency_ms: 1523.45,
662 ttft_ms: Some(234.12),
663 tokens_per_second: Some(45.6),
664 breakdown: Some(LatencyBreakdown {
665 queue_time_ms: 100.0,
666 processing_time_ms: 1200.0,
667 network_time_ms: 200.0,
668 other_ms: 23.45,
669 }),
670 };
671
672 let json = serde_json::to_string(&latency).unwrap();
673 let deserialized: LatencyMetrics = serde_json::from_str(&json).unwrap();
674
675 assert_eq!(deserialized.model_id, "gpt-4");
676 assert_eq!(deserialized.total_latency_ms, 1523.45);
677 assert!(deserialized.breakdown.is_some());
678 }
679
680 #[test]
681 fn test_throughput_metrics() {
682 let throughput = ThroughputMetrics {
683 model_id: "claude-3".to_string(),
684 requests_per_second: 100.5,
685 tokens_per_second: 5000.0,
686 concurrent_requests: 25,
687 window_duration_seconds: 60,
688 };
689
690 let json = serde_json::to_string(&throughput).unwrap();
691 assert!(json.contains("claude-3"));
692 assert!(json.contains("100.5"));
693 }
694
695 #[test]
696 fn test_error_rate_metrics() {
697 let mut error_breakdown = HashMap::new();
698 error_breakdown.insert("timeout".to_string(), 5);
699 error_breakdown.insert("rate_limit".to_string(), 3);
700
701 let error_rate = ErrorRateMetrics {
702 model_id: "gpt-4".to_string(),
703 total_requests: 1000,
704 failed_requests: 8,
705 error_rate_percent: 0.8,
706 error_breakdown,
707 window_duration_seconds: 300,
708 };
709
710 assert_eq!(error_rate.error_rate_percent, 0.8);
711 assert_eq!(error_rate.failed_requests, 8);
712 }
713
714 #[test]
715 fn test_token_usage_metrics() {
716 let token_usage = TokenUsageMetrics {
717 model_id: "gpt-4".to_string(),
718 request_id: "req-456".to_string(),
719 prompt_tokens: 100,
720 completion_tokens: 200,
721 total_tokens: 300,
722 };
723
724 assert_eq!(token_usage.total_tokens, token_usage.prompt_tokens + token_usage.completion_tokens);
725 }
726
727 #[test]
728 fn test_telemetry_event_serialization() {
729 let event = AnalyticsEvent {
730 common: CommonEventFields {
731 event_id: Uuid::new_v4(),
732 timestamp: Utc::now(),
733 source_module: SourceModule::LlmObservatory,
734 event_type: EventType::Telemetry,
735 correlation_id: Some(Uuid::new_v4()),
736 parent_event_id: None,
737 schema_version: SCHEMA_VERSION.to_string(),
738 severity: Severity::Info,
739 environment: "production".to_string(),
740 tags: HashMap::new(),
741 },
742 payload: EventPayload::Telemetry(TelemetryPayload::Latency(LatencyMetrics {
743 model_id: "gpt-4".to_string(),
744 request_id: "req-123".to_string(),
745 total_latency_ms: 1523.45,
746 ttft_ms: Some(234.12),
747 tokens_per_second: Some(45.6),
748 breakdown: None,
749 })),
750 };
751
752 let json = serde_json::to_string_pretty(&event).unwrap();
753 assert!(json.contains("telemetry"));
754 assert!(json.contains("gpt-4"));
755
756 let deserialized: AnalyticsEvent = serde_json::from_str(&json).unwrap();
758 match deserialized.payload {
759 EventPayload::Telemetry(TelemetryPayload::Latency(metrics)) => {
760 assert_eq!(metrics.model_id, "gpt-4");
761 },
762 _ => panic!("Wrong payload type"),
763 }
764 }
765
766 #[test]
771 fn test_threat_event_all_types() {
772 let threat_types = vec![
773 ThreatType::PromptInjection,
774 ThreatType::DataExfiltration,
775 ThreatType::ModelPoisoning,
776 ThreatType::DenialOfService,
777 ThreatType::UnauthorizedAccess,
778 ThreatType::MaliciousInput,
779 ];
780
781 for threat_type in threat_types {
782 let threat = ThreatEvent {
783 threat_id: format!("threat-{:?}", threat_type),
784 threat_type: threat_type.clone(),
785 threat_level: ThreatLevel::High,
786 source_ip: Some("192.168.1.1".to_string()),
787 target_resource: "test-resource".to_string(),
788 attack_vector: "test-vector".to_string(),
789 mitigation_status: MitigationStatus::Detected,
790 indicators_of_compromise: vec![],
791 };
792
793 let json = serde_json::to_string(&threat).unwrap();
794 let deserialized: ThreatEvent = serde_json::from_str(&json).unwrap();
795 assert_eq!(deserialized.threat_type, threat_type);
796 }
797 }
798
799 #[test]
800 fn test_threat_level_ordering() {
801 assert!(ThreatLevel::Critical > ThreatLevel::High);
802 assert!(ThreatLevel::High > ThreatLevel::Medium);
803 assert!(ThreatLevel::Medium > ThreatLevel::Low);
804 }
805
806 #[test]
807 fn test_vulnerability_event() {
808 let vuln = VulnerabilityEvent {
809 vulnerability_id: "vuln-123".to_string(),
810 cve_id: Some("CVE-2024-1234".to_string()),
811 severity_score: 7.5,
812 affected_component: "llm-model".to_string(),
813 description: "SQL injection vulnerability".to_string(),
814 remediation_status: RemediationStatus::PatchAvailable,
815 };
816
817 let json = serde_json::to_string(&vuln).unwrap();
818 assert!(json.contains("CVE-2024-1234"));
819 assert!(json.contains("7.5"));
820 }
821
822 #[test]
823 fn test_auth_event() {
824 let auth = AuthEvent {
825 user_id: "user-123".to_string(),
826 action: AuthAction::Login,
827 resource: "/api/models".to_string(),
828 success: true,
829 failure_reason: None,
830 };
831
832 assert!(auth.success);
833 assert!(auth.failure_reason.is_none());
834 }
835
836 #[test]
837 fn test_privacy_event() {
838 let privacy = PrivacyEvent {
839 data_type: "pii".to_string(),
840 operation: PrivacyOperation::DataAccess,
841 user_consent: true,
842 data_subjects: vec!["user-1".to_string(), "user-2".to_string()],
843 purpose: "analytics".to_string(),
844 };
845
846 assert_eq!(privacy.data_subjects.len(), 2);
847 assert!(privacy.user_consent);
848 }
849
850 #[test]
851 fn test_security_event_serialization() {
852 let event = AnalyticsEvent {
853 common: CommonEventFields {
854 event_id: Uuid::new_v4(),
855 timestamp: Utc::now(),
856 source_module: SourceModule::LlmSentinel,
857 event_type: EventType::Security,
858 correlation_id: None,
859 parent_event_id: None,
860 schema_version: SCHEMA_VERSION.to_string(),
861 severity: Severity::Critical,
862 environment: "production".to_string(),
863 tags: HashMap::new(),
864 },
865 payload: EventPayload::Security(SecurityPayload::Threat(ThreatEvent {
866 threat_id: "threat-456".to_string(),
867 threat_type: ThreatType::PromptInjection,
868 threat_level: ThreatLevel::High,
869 source_ip: Some("192.168.1.1".to_string()),
870 target_resource: "model-endpoint-1".to_string(),
871 attack_vector: "malicious prompt".to_string(),
872 mitigation_status: MitigationStatus::Blocked,
873 indicators_of_compromise: vec!["ioc1".to_string(), "ioc2".to_string()],
874 })),
875 };
876
877 let json = serde_json::to_string_pretty(&event).unwrap();
878 assert!(json.contains("security"));
879 assert!(json.contains("prompt_injection"));
880 }
881
882 #[test]
887 fn test_token_cost_event() {
888 let cost = TokenCostEvent {
889 model_id: "gpt-4".to_string(),
890 request_id: "req-123".to_string(),
891 prompt_tokens: 100,
892 completion_tokens: 200,
893 total_tokens: 300,
894 cost_per_prompt_token: 0.00003,
895 cost_per_completion_token: 0.00006,
896 total_cost_usd: 0.015,
897 currency: "USD".to_string(),
898 };
899
900 let calculated_cost = (100.0 * 0.00003) + (200.0 * 0.00006);
901 assert!((cost.total_cost_usd - calculated_cost).abs() < 0.001);
902 }
903
904 #[test]
905 fn test_budget_alert_event() {
906 let alert = BudgetAlertEvent {
907 budget_id: "budget-123".to_string(),
908 budget_name: "Q1 LLM Budget".to_string(),
909 budget_limit_usd: 10000.0,
910 current_spend_usd: 9500.0,
911 threshold_percent: 95.0,
912 alert_type: BudgetAlertType::Critical,
913 };
914
915 assert_eq!(alert.alert_type, BudgetAlertType::Critical);
916 assert!(alert.current_spend_usd > alert.budget_limit_usd * 0.9);
917 }
918
919 #[test]
920 fn test_resource_consumption_event() {
921 let resource = ResourceConsumptionEvent {
922 resource_type: ResourceType::Gpu,
923 resource_id: "gpu-1".to_string(),
924 quantity: 8.0,
925 unit: "hours".to_string(),
926 cost_usd: 24.0,
927 utilization_percent: 85.0,
928 };
929
930 assert_eq!(resource.resource_type, ResourceType::Gpu);
931 assert_eq!(resource.cost_usd, 24.0);
932 }
933
934 #[test]
939 fn test_policy_violation_event() {
940 let violation = PolicyViolationEvent {
941 policy_id: "pol-123".to_string(),
942 policy_name: "Data Retention Policy".to_string(),
943 violation_description: "Data retained beyond allowed period".to_string(),
944 violated_rules: vec!["rule-1".to_string(), "rule-2".to_string()],
945 resource_id: "dataset-456".to_string(),
946 user_id: Some("user-789".to_string()),
947 severity: PolicyViolationSeverity::High,
948 auto_remediated: false,
949 };
950
951 assert_eq!(violation.violated_rules.len(), 2);
952 assert!(!violation.auto_remediated);
953 }
954
955 #[test]
956 fn test_audit_trail_event() {
957 let mut changes = HashMap::new();
958 changes.insert("field1".to_string(), serde_json::json!("old_value"));
959
960 let audit = AuditTrailEvent {
961 action: "update".to_string(),
962 actor: "user-123".to_string(),
963 resource_type: "model".to_string(),
964 resource_id: "model-456".to_string(),
965 changes,
966 ip_address: Some("10.0.0.1".to_string()),
967 user_agent: Some("Mozilla/5.0".to_string()),
968 };
969
970 assert_eq!(audit.action, "update");
971 assert!(audit.changes.len() > 0);
972 }
973
974 #[test]
975 fn test_compliance_check_event() {
976 let findings = vec![
977 ComplianceFinding {
978 control_id: "SOC2-CC6.1".to_string(),
979 status: ComplianceStatus::Pass,
980 description: "Logical access controls implemented".to_string(),
981 evidence: Some("IAM policies configured".to_string()),
982 },
983 ComplianceFinding {
984 control_id: "SOC2-CC6.2".to_string(),
985 status: ComplianceStatus::Fail,
986 description: "MFA not enabled for all users".to_string(),
987 evidence: None,
988 },
989 ];
990
991 let check = ComplianceCheckEvent {
992 check_id: "check-123".to_string(),
993 framework: "SOC2".to_string(),
994 controls_checked: vec!["CC6.1".to_string(), "CC6.2".to_string()],
995 passed: false,
996 findings,
997 score: 0.5,
998 };
999
1000 assert!(!check.passed);
1001 assert_eq!(check.findings.len(), 2);
1002 }
1003
1004 #[test]
1005 fn test_data_lineage_event() {
1006 let lineage = DataLineageEvent {
1007 data_asset_id: "asset-123".to_string(),
1008 operation: DataOperation::Transform,
1009 source: Some("raw_data".to_string()),
1010 destination: Some("processed_data".to_string()),
1011 transformation: Some("anonymization".to_string()),
1012 lineage_path: vec!["raw".to_string(), "cleaned".to_string(), "anonymized".to_string()],
1013 };
1014
1015 assert_eq!(lineage.operation, DataOperation::Transform);
1016 assert_eq!(lineage.lineage_path.len(), 3);
1017 }
1018
1019 #[test]
1024 fn test_custom_payload() {
1025 let custom_data = serde_json::json!({
1026 "custom_field": "custom_value",
1027 "nested": {
1028 "field": 123
1029 }
1030 });
1031
1032 let custom = CustomPayload {
1033 custom_type: "test_custom".to_string(),
1034 data: custom_data,
1035 };
1036
1037 let json = serde_json::to_string(&custom).unwrap();
1038 assert!(json.contains("test_custom"));
1039 assert!(json.contains("custom_field"));
1040 }
1041
1042 #[test]
1047 fn test_event_with_correlation() {
1048 let correlation_id = Uuid::new_v4();
1049 let parent_id = Uuid::new_v4();
1050
1051 let event = AnalyticsEvent {
1052 common: CommonEventFields {
1053 event_id: Uuid::new_v4(),
1054 timestamp: Utc::now(),
1055 source_module: SourceModule::LlmAnalyticsHub,
1056 event_type: EventType::Audit,
1057 correlation_id: Some(correlation_id),
1058 parent_event_id: Some(parent_id),
1059 schema_version: SCHEMA_VERSION.to_string(),
1060 severity: Severity::Info,
1061 environment: "test".to_string(),
1062 tags: HashMap::new(),
1063 },
1064 payload: EventPayload::Custom(CustomPayload {
1065 custom_type: "test".to_string(),
1066 data: serde_json::json!({}),
1067 }),
1068 };
1069
1070 assert_eq!(event.common.correlation_id, Some(correlation_id));
1071 assert_eq!(event.common.parent_event_id, Some(parent_id));
1072 }
1073
1074 #[test]
1075 fn test_event_with_tags() {
1076 let mut tags = HashMap::new();
1077 tags.insert("environment".to_string(), "production".to_string());
1078 tags.insert("region".to_string(), "us-east-1".to_string());
1079 tags.insert("version".to_string(), "1.0.0".to_string());
1080
1081 let event = AnalyticsEvent {
1082 common: CommonEventFields {
1083 event_id: Uuid::new_v4(),
1084 timestamp: Utc::now(),
1085 source_module: SourceModule::LlmAnalyticsHub,
1086 event_type: EventType::Telemetry,
1087 correlation_id: None,
1088 parent_event_id: None,
1089 schema_version: SCHEMA_VERSION.to_string(),
1090 severity: Severity::Info,
1091 environment: "production".to_string(),
1092 tags: tags.clone(),
1093 },
1094 payload: EventPayload::Custom(CustomPayload {
1095 custom_type: "test".to_string(),
1096 data: serde_json::json!({}),
1097 }),
1098 };
1099
1100 assert_eq!(event.common.tags.len(), 3);
1101 assert_eq!(event.common.tags.get("region"), Some(&"us-east-1".to_string()));
1102 }
1103
1104 #[test]
1109 fn test_all_event_types_round_trip() {
1110 let event_types = vec![
1111 EventType::Telemetry,
1112 EventType::Security,
1113 EventType::Cost,
1114 EventType::Governance,
1115 EventType::Lifecycle,
1116 EventType::Audit,
1117 EventType::Alert,
1118 ];
1119
1120 for event_type in event_types {
1121 let json = serde_json::to_string(&event_type).unwrap();
1122 let deserialized: EventType = serde_json::from_str(&json).unwrap();
1123 assert_eq!(event_type, deserialized);
1124 }
1125 }
1126
1127 #[test]
1128 fn test_schema_version_compatibility() {
1129 assert_eq!(SCHEMA_VERSION, "1.0.0");
1130
1131 let common = CommonEventFields {
1132 event_id: Uuid::new_v4(),
1133 timestamp: Utc::now(),
1134 source_module: SourceModule::LlmAnalyticsHub,
1135 event_type: EventType::Telemetry,
1136 correlation_id: None,
1137 parent_event_id: None,
1138 schema_version: "1.0.0".to_string(),
1139 severity: Severity::Info,
1140 environment: "test".to_string(),
1141 tags: HashMap::new(),
1142 };
1143
1144 assert_eq!(common.schema_version, SCHEMA_VERSION);
1145 }
1146}