1use crate::database::AnalyticsDatabase;
7use crate::error::Result;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
14#[serde(rename_all = "lowercase")]
15pub enum Pillar {
16 Reality,
18 Contracts,
20 DevX,
22 Cloud,
24 Ai,
26}
27
28impl Pillar {
29 #[must_use]
31 pub const fn as_str(&self) -> &'static str {
32 match self {
33 Self::Reality => "reality",
34 Self::Contracts => "contracts",
35 Self::DevX => "devx",
36 Self::Cloud => "cloud",
37 Self::Ai => "ai",
38 }
39 }
40
41 #[must_use]
43 pub fn parse(s: &str) -> Option<Self> {
44 match s.to_lowercase().as_str() {
45 "reality" => Some(Self::Reality),
46 "contracts" => Some(Self::Contracts),
47 "devx" => Some(Self::DevX),
48 "cloud" => Some(Self::Cloud),
49 "ai" => Some(Self::Ai),
50 _ => None,
51 }
52 }
53}
54
55impl std::fmt::Display for Pillar {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "{}", self.as_str())
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct PillarUsageEvent {
64 pub workspace_id: Option<String>,
66 pub org_id: Option<String>,
68 pub pillar: Pillar,
70 pub metric_name: String,
72 pub metric_value: Value,
74 pub timestamp: DateTime<Utc>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct PillarUsageMetrics {
81 pub workspace_id: Option<String>,
83 pub org_id: Option<String>,
85 pub time_range: String,
87 pub reality: Option<RealityPillarMetrics>,
89 pub contracts: Option<ContractsPillarMetrics>,
91 pub devx: Option<DevXPillarMetrics>,
93 pub cloud: Option<CloudPillarMetrics>,
95 pub ai: Option<AiPillarMetrics>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct RealityPillarMetrics {
102 pub blended_reality_percent: f64,
104 pub smart_personas_percent: f64,
106 pub static_fixtures_percent: f64,
108 pub avg_reality_level: f64,
110 pub chaos_enabled_count: u64,
112 pub total_scenarios: u64,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct ContractsPillarMetrics {
119 pub validation_disabled_percent: f64,
121 pub validation_warn_percent: f64,
123 pub validation_enforce_percent: f64,
125 pub drift_budget_configured_count: u64,
127 pub drift_incidents_count: u64,
129 pub contract_sync_cycles: u64,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct DevXPillarMetrics {
136 pub sdk_installations: u64,
138 pub client_generations: u64,
140 pub playground_sessions: u64,
142 pub cli_commands: u64,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct CloudPillarMetrics {
149 pub shared_scenarios_count: u64,
151 pub marketplace_downloads: u64,
153 pub org_templates_used: u64,
155 pub collaborative_workspaces: u64,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct AiPillarMetrics {
162 pub ai_generated_mocks: u64,
164 pub ai_contract_diffs: u64,
166 pub voice_commands: u64,
168 pub llm_assisted_operations: u64,
170}
171
172impl AnalyticsDatabase {
173 pub async fn record_pillar_usage(&self, event: &PillarUsageEvent) -> Result<()> {
179 let timestamp = event.timestamp.timestamp();
180 let metric_value_json = serde_json::to_string(&event.metric_value)?;
181
182 sqlx::query(
183 r"
184 INSERT INTO pillar_usage_events (
185 workspace_id, org_id, pillar, metric_name, metric_value, timestamp
186 )
187 VALUES ($1, $2, $3, $4, $5, $6)
188 ",
189 )
190 .bind(event.workspace_id.as_deref())
191 .bind(event.org_id.as_deref())
192 .bind(event.pillar.as_str())
193 .bind(&event.metric_name)
194 .bind(&metric_value_json)
195 .bind(timestamp)
196 .execute(self.pool())
197 .await?;
198
199 Ok(())
200 }
201
202 pub async fn get_workspace_pillar_metrics(
208 &self,
209 workspace_id: &str,
210 duration_seconds: i64,
211 ) -> Result<PillarUsageMetrics> {
212 let end_time = Utc::now().timestamp();
213 let start_time = end_time - duration_seconds;
214
215 let reality = self
217 .get_reality_pillar_metrics(Some(workspace_id), None, start_time, end_time)
218 .await?;
219
220 let contracts = self
222 .get_contracts_pillar_metrics(Some(workspace_id), None, start_time, end_time)
223 .await?;
224
225 let devx = self
227 .get_devx_pillar_metrics(Some(workspace_id), None, start_time, end_time)
228 .await?;
229
230 let cloud = self
232 .get_cloud_pillar_metrics(Some(workspace_id), None, start_time, end_time)
233 .await?;
234
235 let ai = self
237 .get_ai_pillar_metrics(Some(workspace_id), None, start_time, end_time)
238 .await?;
239
240 Ok(PillarUsageMetrics {
241 workspace_id: Some(workspace_id.to_string()),
242 org_id: None,
243 time_range: format!("{duration_seconds}s"),
244 reality: Some(reality),
245 contracts: Some(contracts),
246 devx: Some(devx),
247 cloud: Some(cloud),
248 ai: Some(ai),
249 })
250 }
251
252 pub async fn get_org_pillar_metrics(
258 &self,
259 org_id: &str,
260 duration_seconds: i64,
261 ) -> Result<PillarUsageMetrics> {
262 let end_time = Utc::now().timestamp();
263 let start_time = end_time - duration_seconds;
264
265 let reality = self
267 .get_reality_pillar_metrics(None, Some(org_id), start_time, end_time)
268 .await?;
269
270 let contracts = self
272 .get_contracts_pillar_metrics(None, Some(org_id), start_time, end_time)
273 .await?;
274
275 let devx = self.get_devx_pillar_metrics(None, Some(org_id), start_time, end_time).await?;
277
278 let cloud = self.get_cloud_pillar_metrics(None, Some(org_id), start_time, end_time).await?;
280
281 let ai = self.get_ai_pillar_metrics(None, Some(org_id), start_time, end_time).await?;
283
284 Ok(PillarUsageMetrics {
285 workspace_id: None,
286 org_id: Some(org_id.to_string()),
287 time_range: format!("{duration_seconds}s"),
288 reality: Some(reality),
289 contracts: Some(contracts),
290 devx: Some(devx),
291 cloud: Some(cloud),
292 ai: Some(ai),
293 })
294 }
295
296 #[allow(
298 clippy::too_many_lines,
299 clippy::cast_precision_loss,
300 clippy::cast_sign_loss
301 )]
302 async fn get_reality_pillar_metrics(
303 &self,
304 workspace_id: Option<&str>,
305 org_id: Option<&str>,
306 start_time: i64,
307 end_time: i64,
308 ) -> Result<RealityPillarMetrics> {
309 let blended_reality_query = if let Some(ws_id) = workspace_id {
311 sqlx::query_scalar::<_, f64>(
312 r"
313 SELECT AVG(CAST(json_extract(metric_value, '$.ratio') AS REAL)) * 100.0
314 FROM pillar_usage_events
315 WHERE pillar = 'reality'
316 AND metric_name = 'blended_reality_ratio'
317 AND workspace_id = $1
318 AND timestamp >= $2 AND timestamp <= $3
319 ",
320 )
321 .bind(ws_id)
322 .bind(start_time)
323 .bind(end_time)
324 } else if let Some(org) = org_id {
325 sqlx::query_scalar::<_, f64>(
326 r"
327 SELECT AVG(CAST(json_extract(metric_value, '$.ratio') AS REAL)) * 100.0
328 FROM pillar_usage_events
329 WHERE pillar = 'reality'
330 AND metric_name = 'blended_reality_ratio'
331 AND org_id = $1
332 AND timestamp >= $2 AND timestamp <= $3
333 ",
334 )
335 .bind(org)
336 .bind(start_time)
337 .bind(end_time)
338 } else {
339 return Err(crate::error::AnalyticsError::InvalidInput(
340 "Either workspace_id or org_id must be provided".to_string(),
341 ));
342 };
343
344 let blended_reality_percent =
345 blended_reality_query.fetch_one(self.pool()).await.unwrap_or(0.0);
346
347 let smart_personas_query = if let Some(ws_id) = workspace_id {
349 sqlx::query_scalar::<_, i64>(
350 r"
351 SELECT COUNT(*) FROM pillar_usage_events
352 WHERE pillar = 'reality'
353 AND metric_name = 'persona_usage'
354 AND json_extract(metric_value, '$.type') = 'smart'
355 AND workspace_id = $1
356 AND timestamp >= $2 AND timestamp <= $3
357 ",
358 )
359 .bind(ws_id)
360 .bind(start_time)
361 .bind(end_time)
362 } else if let Some(org) = org_id {
363 sqlx::query_scalar::<_, i64>(
364 r"
365 SELECT COUNT(*) FROM pillar_usage_events
366 WHERE pillar = 'reality'
367 AND metric_name = 'persona_usage'
368 AND json_extract(metric_value, '$.type') = 'smart'
369 AND org_id = $1
370 AND timestamp >= $2 AND timestamp <= $3
371 ",
372 )
373 .bind(org)
374 .bind(start_time)
375 .bind(end_time)
376 } else {
377 return Err(crate::error::AnalyticsError::InvalidInput(
378 "Either workspace_id or org_id must be provided".to_string(),
379 ));
380 };
381
382 let smart_personas_count = smart_personas_query.fetch_one(self.pool()).await.unwrap_or(0);
383
384 let static_fixtures_query = if let Some(ws_id) = workspace_id {
385 sqlx::query_scalar::<_, i64>(
386 r"
387 SELECT COUNT(*) FROM pillar_usage_events
388 WHERE pillar = 'reality'
389 AND metric_name = 'persona_usage'
390 AND json_extract(metric_value, '$.type') = 'static'
391 AND workspace_id = $1
392 AND timestamp >= $2 AND timestamp <= $3
393 ",
394 )
395 .bind(ws_id)
396 .bind(start_time)
397 .bind(end_time)
398 } else if let Some(org) = org_id {
399 sqlx::query_scalar::<_, i64>(
400 r"
401 SELECT COUNT(*) FROM pillar_usage_events
402 WHERE pillar = 'reality'
403 AND metric_name = 'persona_usage'
404 AND json_extract(metric_value, '$.type') = 'static'
405 AND org_id = $1
406 AND timestamp >= $2 AND timestamp <= $3
407 ",
408 )
409 .bind(org)
410 .bind(start_time)
411 .bind(end_time)
412 } else {
413 return Err(crate::error::AnalyticsError::InvalidInput(
414 "Either workspace_id or org_id must be provided".to_string(),
415 ));
416 };
417
418 let static_fixtures_count = static_fixtures_query.fetch_one(self.pool()).await.unwrap_or(0);
419
420 let total = smart_personas_count + static_fixtures_count;
421 let smart_personas_percent = if total > 0 {
422 (smart_personas_count as f64 / total as f64) * 100.0
423 } else {
424 0.0
425 };
426 let static_fixtures_percent = if total > 0 {
427 (static_fixtures_count as f64 / total as f64) * 100.0
428 } else {
429 0.0
430 };
431
432 let avg_reality_level = if let Some(ws_id) = workspace_id {
434 sqlx::query_scalar::<_, f64>(
435 r"
436 SELECT AVG(CAST(json_extract(metric_value, '$.level') AS REAL))
437 FROM pillar_usage_events
438 WHERE pillar = 'reality'
439 AND metric_name = 'reality_level'
440 AND workspace_id = $1
441 AND timestamp >= $2 AND timestamp <= $3
442 ",
443 )
444 .bind(ws_id)
445 .bind(start_time)
446 .bind(end_time)
447 .fetch_one(self.pool())
448 .await
449 .unwrap_or(0.0)
450 } else if let Some(org) = org_id {
451 sqlx::query_scalar::<_, f64>(
452 r"
453 SELECT AVG(CAST(json_extract(metric_value, '$.level') AS REAL))
454 FROM pillar_usage_events
455 WHERE pillar = 'reality'
456 AND metric_name = 'reality_level'
457 AND org_id = $1
458 AND timestamp >= $2 AND timestamp <= $3
459 ",
460 )
461 .bind(org)
462 .bind(start_time)
463 .bind(end_time)
464 .fetch_one(self.pool())
465 .await
466 .unwrap_or(0.0)
467 } else {
468 0.0
469 };
470
471 let chaos_enabled_count = if let Some(ws_id) = workspace_id {
473 sqlx::query_scalar::<_, i64>(
474 r"
475 SELECT COUNT(DISTINCT json_extract(metadata, '$.scenario_id'))
476 FROM pillar_usage_events
477 WHERE pillar = 'reality'
478 AND metric_name = 'chaos_injection'
479 AND json_extract(metric_value, '$.enabled') = 1
480 AND workspace_id = $1
481 AND timestamp >= $2 AND timestamp <= $3
482 ",
483 )
484 .bind(ws_id)
485 .bind(start_time)
486 .bind(end_time)
487 .fetch_one(self.pool())
488 .await
489 .unwrap_or(0)
490 } else if let Some(org) = org_id {
491 sqlx::query_scalar::<_, i64>(
492 r"
493 SELECT COUNT(DISTINCT json_extract(metadata, '$.scenario_id'))
494 FROM pillar_usage_events
495 WHERE pillar = 'reality'
496 AND metric_name = 'chaos_injection'
497 AND json_extract(metric_value, '$.enabled') = 1
498 AND org_id = $1
499 AND timestamp >= $2 AND timestamp <= $3
500 ",
501 )
502 .bind(org)
503 .bind(start_time)
504 .bind(end_time)
505 .fetch_one(self.pool())
506 .await
507 .unwrap_or(0)
508 } else {
509 0
510 };
511
512 Ok(RealityPillarMetrics {
513 blended_reality_percent,
514 smart_personas_percent,
515 static_fixtures_percent,
516 avg_reality_level,
517 chaos_enabled_count: chaos_enabled_count as u64,
518 total_scenarios: total as u64,
519 })
520 }
521
522 #[allow(
524 clippy::too_many_lines,
525 clippy::cast_precision_loss,
526 clippy::cast_sign_loss
527 )]
528 async fn get_contracts_pillar_metrics(
529 &self,
530 workspace_id: Option<&str>,
531 org_id: Option<&str>,
532 start_time: i64,
533 end_time: i64,
534 ) -> Result<ContractsPillarMetrics> {
535 let validation_disabled_query = if let Some(ws_id) = workspace_id {
537 sqlx::query_scalar::<_, i64>(
538 r"
539 SELECT COUNT(*) FROM pillar_usage_events
540 WHERE pillar = 'contracts'
541 AND metric_name = 'validation_mode'
542 AND json_extract(metric_value, '$.mode') = 'disabled'
543 AND workspace_id = $1
544 AND timestamp >= $2 AND timestamp <= $3
545 ",
546 )
547 .bind(ws_id)
548 .bind(start_time)
549 .bind(end_time)
550 } else if let Some(org) = org_id {
551 sqlx::query_scalar::<_, i64>(
552 r"
553 SELECT COUNT(*) FROM pillar_usage_events
554 WHERE pillar = 'contracts'
555 AND metric_name = 'validation_mode'
556 AND json_extract(metric_value, '$.mode') = 'disabled'
557 AND org_id = $1
558 AND timestamp >= $2 AND timestamp <= $3
559 ",
560 )
561 .bind(org)
562 .bind(start_time)
563 .bind(end_time)
564 } else {
565 return Err(crate::error::AnalyticsError::InvalidInput(
566 "Either workspace_id or org_id must be provided".to_string(),
567 ));
568 };
569
570 let validation_disabled_count =
571 validation_disabled_query.fetch_one(self.pool()).await.unwrap_or(0);
572
573 let validation_warn_query = if let Some(ws_id) = workspace_id {
574 sqlx::query_scalar::<_, i64>(
575 r"
576 SELECT COUNT(*) FROM pillar_usage_events
577 WHERE pillar = 'contracts'
578 AND metric_name = 'validation_mode'
579 AND json_extract(metric_value, '$.mode') = 'warn'
580 AND workspace_id = $1
581 AND timestamp >= $2 AND timestamp <= $3
582 ",
583 )
584 .bind(ws_id)
585 .bind(start_time)
586 .bind(end_time)
587 } else if let Some(org) = org_id {
588 sqlx::query_scalar::<_, i64>(
589 r"
590 SELECT COUNT(*) FROM pillar_usage_events
591 WHERE pillar = 'contracts'
592 AND metric_name = 'validation_mode'
593 AND json_extract(metric_value, '$.mode') = 'warn'
594 AND org_id = $1
595 AND timestamp >= $2 AND timestamp <= $3
596 ",
597 )
598 .bind(org)
599 .bind(start_time)
600 .bind(end_time)
601 } else {
602 return Err(crate::error::AnalyticsError::InvalidInput(
603 "Either workspace_id or org_id must be provided".to_string(),
604 ));
605 };
606
607 let validation_warn_count = validation_warn_query.fetch_one(self.pool()).await.unwrap_or(0);
608
609 let validation_enforce_query = if let Some(ws_id) = workspace_id {
610 sqlx::query_scalar::<_, i64>(
611 r"
612 SELECT COUNT(*) FROM pillar_usage_events
613 WHERE pillar = 'contracts'
614 AND metric_name = 'validation_mode'
615 AND json_extract(metric_value, '$.mode') = 'enforce'
616 AND workspace_id = $1
617 AND timestamp >= $2 AND timestamp <= $3
618 ",
619 )
620 .bind(ws_id)
621 .bind(start_time)
622 .bind(end_time)
623 } else if let Some(org) = org_id {
624 sqlx::query_scalar::<_, i64>(
625 r"
626 SELECT COUNT(*) FROM pillar_usage_events
627 WHERE pillar = 'contracts'
628 AND metric_name = 'validation_mode'
629 AND json_extract(metric_value, '$.mode') = 'enforce'
630 AND org_id = $1
631 AND timestamp >= $2 AND timestamp <= $3
632 ",
633 )
634 .bind(org)
635 .bind(start_time)
636 .bind(end_time)
637 } else {
638 return Err(crate::error::AnalyticsError::InvalidInput(
639 "Either workspace_id or org_id must be provided".to_string(),
640 ));
641 };
642
643 let validation_enforce_count =
644 validation_enforce_query.fetch_one(self.pool()).await.unwrap_or(0);
645
646 let total_validation_events =
647 validation_disabled_count + validation_warn_count + validation_enforce_count;
648 let validation_disabled_percent = if total_validation_events > 0 {
649 (validation_disabled_count as f64 / total_validation_events as f64) * 100.0
650 } else {
651 0.0
652 };
653 let validation_warn_percent = if total_validation_events > 0 {
654 (validation_warn_count as f64 / total_validation_events as f64) * 100.0
655 } else {
656 0.0
657 };
658 let validation_enforce_percent = if total_validation_events > 0 {
659 (validation_enforce_count as f64 / total_validation_events as f64) * 100.0
660 } else {
661 0.0
662 };
663
664 let drift_budget_configured_count = if let Some(ws_id) = workspace_id {
666 sqlx::query_scalar::<_, i64>(
667 r"
668 SELECT COUNT(DISTINCT json_extract(metadata, '$.endpoint'))
669 FROM pillar_usage_events
670 WHERE pillar = 'contracts'
671 AND metric_name = 'drift_budget_configured'
672 AND workspace_id = $1
673 AND timestamp >= $2 AND timestamp <= $3
674 ",
675 )
676 .bind(ws_id)
677 .bind(start_time)
678 .bind(end_time)
679 .fetch_one(self.pool())
680 .await
681 .unwrap_or(0)
682 } else if let Some(org) = org_id {
683 sqlx::query_scalar::<_, i64>(
684 r"
685 SELECT COUNT(DISTINCT json_extract(metadata, '$.endpoint'))
686 FROM pillar_usage_events
687 WHERE pillar = 'contracts'
688 AND metric_name = 'drift_budget_configured'
689 AND org_id = $1
690 AND timestamp >= $2 AND timestamp <= $3
691 ",
692 )
693 .bind(org)
694 .bind(start_time)
695 .bind(end_time)
696 .fetch_one(self.pool())
697 .await
698 .unwrap_or(0)
699 } else {
700 0
701 };
702
703 let drift_incidents_count = if let Some(ws_id) = workspace_id {
705 sqlx::query_scalar::<_, i64>(
706 r"
707 SELECT COUNT(*)
708 FROM pillar_usage_events
709 WHERE pillar = 'contracts'
710 AND metric_name = 'drift_detection'
711 AND json_extract(metric_value, '$.incident') = 1
712 AND workspace_id = $1
713 AND timestamp >= $2 AND timestamp <= $3
714 ",
715 )
716 .bind(ws_id)
717 .bind(start_time)
718 .bind(end_time)
719 .fetch_one(self.pool())
720 .await
721 .unwrap_or(0)
722 } else if let Some(org) = org_id {
723 sqlx::query_scalar::<_, i64>(
724 r"
725 SELECT COUNT(*)
726 FROM pillar_usage_events
727 WHERE pillar = 'contracts'
728 AND metric_name = 'drift_detection'
729 AND json_extract(metric_value, '$.incident') = 1
730 AND org_id = $1
731 AND timestamp >= $2 AND timestamp <= $3
732 ",
733 )
734 .bind(org)
735 .bind(start_time)
736 .bind(end_time)
737 .fetch_one(self.pool())
738 .await
739 .unwrap_or(0)
740 } else {
741 0
742 };
743
744 let contract_sync_cycles = if let Some(ws_id) = workspace_id {
746 sqlx::query_scalar::<_, i64>(
747 r"
748 SELECT COUNT(DISTINCT json_extract(metadata, '$.sync_id'))
749 FROM pillar_usage_events
750 WHERE pillar = 'contracts'
751 AND metric_name = 'contract_sync'
752 AND workspace_id = $1
753 AND timestamp >= $2 AND timestamp <= $3
754 ",
755 )
756 .bind(ws_id)
757 .bind(start_time)
758 .bind(end_time)
759 .fetch_one(self.pool())
760 .await
761 .unwrap_or(0)
762 } else if let Some(org) = org_id {
763 sqlx::query_scalar::<_, i64>(
764 r"
765 SELECT COUNT(DISTINCT json_extract(metadata, '$.sync_id'))
766 FROM pillar_usage_events
767 WHERE pillar = 'contracts'
768 AND metric_name = 'contract_sync'
769 AND org_id = $1
770 AND timestamp >= $2 AND timestamp <= $3
771 ",
772 )
773 .bind(org)
774 .bind(start_time)
775 .bind(end_time)
776 .fetch_one(self.pool())
777 .await
778 .unwrap_or(0)
779 } else {
780 0
781 };
782
783 Ok(ContractsPillarMetrics {
784 validation_disabled_percent,
785 validation_warn_percent,
786 validation_enforce_percent,
787 drift_budget_configured_count: drift_budget_configured_count as u64,
788 drift_incidents_count: drift_incidents_count as u64,
789 contract_sync_cycles: contract_sync_cycles as u64,
790 })
791 }
792
793 #[allow(clippy::too_many_lines, clippy::cast_sign_loss)]
795 async fn get_devx_pillar_metrics(
796 &self,
797 workspace_id: Option<&str>,
798 org_id: Option<&str>,
799 start_time: i64,
800 end_time: i64,
801 ) -> Result<DevXPillarMetrics> {
802 let sdk_installations = if let Some(ws_id) = workspace_id {
804 sqlx::query_scalar::<_, i64>(
805 r"
806 SELECT COUNT(DISTINCT json_extract(metadata, '$.sdk_type'))
807 FROM pillar_usage_events
808 WHERE pillar = 'devx'
809 AND metric_name = 'sdk_installation'
810 AND workspace_id = $1
811 AND timestamp >= $2 AND timestamp <= $3
812 ",
813 )
814 .bind(ws_id)
815 .bind(start_time)
816 .bind(end_time)
817 .fetch_one(self.pool())
818 .await
819 .unwrap_or(0)
820 } else if let Some(org) = org_id {
821 sqlx::query_scalar::<_, i64>(
822 r"
823 SELECT COUNT(DISTINCT json_extract(metadata, '$.sdk_type'))
824 FROM pillar_usage_events
825 WHERE pillar = 'devx'
826 AND metric_name = 'sdk_installation'
827 AND org_id = $1
828 AND timestamp >= $2 AND timestamp <= $3
829 ",
830 )
831 .bind(org)
832 .bind(start_time)
833 .bind(end_time)
834 .fetch_one(self.pool())
835 .await
836 .unwrap_or(0)
837 } else {
838 0
839 };
840
841 let client_generations = if let Some(ws_id) = workspace_id {
843 sqlx::query_scalar::<_, i64>(
844 r"
845 SELECT COUNT(*)
846 FROM pillar_usage_events
847 WHERE pillar = 'devx'
848 AND metric_name = 'client_generation'
849 AND workspace_id = $1
850 AND timestamp >= $2 AND timestamp <= $3
851 ",
852 )
853 .bind(ws_id)
854 .bind(start_time)
855 .bind(end_time)
856 .fetch_one(self.pool())
857 .await
858 .unwrap_or(0)
859 } else if let Some(org) = org_id {
860 sqlx::query_scalar::<_, i64>(
861 r"
862 SELECT COUNT(*)
863 FROM pillar_usage_events
864 WHERE pillar = 'devx'
865 AND metric_name = 'client_generation'
866 AND org_id = $1
867 AND timestamp >= $2 AND timestamp <= $3
868 ",
869 )
870 .bind(org)
871 .bind(start_time)
872 .bind(end_time)
873 .fetch_one(self.pool())
874 .await
875 .unwrap_or(0)
876 } else {
877 0
878 };
879
880 let playground_sessions = if let Some(ws_id) = workspace_id {
882 sqlx::query_scalar::<_, i64>(
883 r"
884 SELECT COUNT(DISTINCT json_extract(metadata, '$.session_id'))
885 FROM pillar_usage_events
886 WHERE pillar = 'devx'
887 AND metric_name = 'playground_session'
888 AND workspace_id = $1
889 AND timestamp >= $2 AND timestamp <= $3
890 ",
891 )
892 .bind(ws_id)
893 .bind(start_time)
894 .bind(end_time)
895 .fetch_one(self.pool())
896 .await
897 .unwrap_or(0)
898 } else if let Some(org) = org_id {
899 sqlx::query_scalar::<_, i64>(
900 r"
901 SELECT COUNT(DISTINCT json_extract(metadata, '$.session_id'))
902 FROM pillar_usage_events
903 WHERE pillar = 'devx'
904 AND metric_name = 'playground_session'
905 AND org_id = $1
906 AND timestamp >= $2 AND timestamp <= $3
907 ",
908 )
909 .bind(org)
910 .bind(start_time)
911 .bind(end_time)
912 .fetch_one(self.pool())
913 .await
914 .unwrap_or(0)
915 } else {
916 0
917 };
918
919 let cli_commands = if let Some(ws_id) = workspace_id {
921 sqlx::query_scalar::<_, i64>(
922 r"
923 SELECT COUNT(*)
924 FROM pillar_usage_events
925 WHERE pillar = 'devx'
926 AND metric_name = 'cli_command'
927 AND workspace_id = $1
928 AND timestamp >= $2 AND timestamp <= $3
929 ",
930 )
931 .bind(ws_id)
932 .bind(start_time)
933 .bind(end_time)
934 .fetch_one(self.pool())
935 .await
936 .unwrap_or(0)
937 } else if let Some(org) = org_id {
938 sqlx::query_scalar::<_, i64>(
939 r"
940 SELECT COUNT(*)
941 FROM pillar_usage_events
942 WHERE pillar = 'devx'
943 AND metric_name = 'cli_command'
944 AND org_id = $1
945 AND timestamp >= $2 AND timestamp <= $3
946 ",
947 )
948 .bind(org)
949 .bind(start_time)
950 .bind(end_time)
951 .fetch_one(self.pool())
952 .await
953 .unwrap_or(0)
954 } else {
955 0
956 };
957
958 Ok(DevXPillarMetrics {
959 sdk_installations: sdk_installations as u64,
960 client_generations: client_generations as u64,
961 playground_sessions: playground_sessions as u64,
962 cli_commands: cli_commands as u64,
963 })
964 }
965
966 #[allow(clippy::too_many_lines, clippy::cast_sign_loss)]
968 async fn get_cloud_pillar_metrics(
969 &self,
970 workspace_id: Option<&str>,
971 org_id: Option<&str>,
972 start_time: i64,
973 end_time: i64,
974 ) -> Result<CloudPillarMetrics> {
975 let shared_scenarios_count = if let Some(ws_id) = workspace_id {
977 sqlx::query_scalar::<_, i64>(
978 r"
979 SELECT COUNT(DISTINCT json_extract(metadata, '$.scenario_id'))
980 FROM pillar_usage_events
981 WHERE pillar = 'cloud'
982 AND metric_name = 'scenario_shared'
983 AND workspace_id = $1
984 AND timestamp >= $2 AND timestamp <= $3
985 ",
986 )
987 .bind(ws_id)
988 .bind(start_time)
989 .bind(end_time)
990 .fetch_one(self.pool())
991 .await
992 .unwrap_or(0)
993 } else if let Some(org) = org_id {
994 sqlx::query_scalar::<_, i64>(
995 r"
996 SELECT COUNT(DISTINCT json_extract(metadata, '$.scenario_id'))
997 FROM pillar_usage_events
998 WHERE pillar = 'cloud'
999 AND metric_name = 'scenario_shared'
1000 AND org_id = $1
1001 AND timestamp >= $2 AND timestamp <= $3
1002 ",
1003 )
1004 .bind(org)
1005 .bind(start_time)
1006 .bind(end_time)
1007 .fetch_one(self.pool())
1008 .await
1009 .unwrap_or(0)
1010 } else {
1011 0
1012 };
1013
1014 let marketplace_downloads = if let Some(ws_id) = workspace_id {
1016 sqlx::query_scalar::<_, i64>(
1017 r"
1018 SELECT COUNT(*)
1019 FROM pillar_usage_events
1020 WHERE pillar = 'cloud'
1021 AND metric_name = 'marketplace_download'
1022 AND workspace_id = $1
1023 AND timestamp >= $2 AND timestamp <= $3
1024 ",
1025 )
1026 .bind(ws_id)
1027 .bind(start_time)
1028 .bind(end_time)
1029 .fetch_one(self.pool())
1030 .await
1031 .unwrap_or(0)
1032 } else if let Some(org) = org_id {
1033 sqlx::query_scalar::<_, i64>(
1034 r"
1035 SELECT COUNT(*)
1036 FROM pillar_usage_events
1037 WHERE pillar = 'cloud'
1038 AND metric_name = 'marketplace_download'
1039 AND org_id = $1
1040 AND timestamp >= $2 AND timestamp <= $3
1041 ",
1042 )
1043 .bind(org)
1044 .bind(start_time)
1045 .bind(end_time)
1046 .fetch_one(self.pool())
1047 .await
1048 .unwrap_or(0)
1049 } else {
1050 0
1051 };
1052
1053 let org_templates_used = if let Some(ws_id) = workspace_id {
1055 sqlx::query_scalar::<_, i64>(
1056 r"
1057 SELECT COUNT(DISTINCT json_extract(metadata, '$.template_id'))
1058 FROM pillar_usage_events
1059 WHERE pillar = 'cloud'
1060 AND metric_name = 'template_use'
1061 AND workspace_id = $1
1062 AND timestamp >= $2 AND timestamp <= $3
1063 ",
1064 )
1065 .bind(ws_id)
1066 .bind(start_time)
1067 .bind(end_time)
1068 .fetch_one(self.pool())
1069 .await
1070 .unwrap_or(0)
1071 } else if let Some(org) = org_id {
1072 sqlx::query_scalar::<_, i64>(
1073 r"
1074 SELECT COUNT(DISTINCT json_extract(metadata, '$.template_id'))
1075 FROM pillar_usage_events
1076 WHERE pillar = 'cloud'
1077 AND metric_name = 'template_use'
1078 AND org_id = $1
1079 AND timestamp >= $2 AND timestamp <= $3
1080 ",
1081 )
1082 .bind(org)
1083 .bind(start_time)
1084 .bind(end_time)
1085 .fetch_one(self.pool())
1086 .await
1087 .unwrap_or(0)
1088 } else {
1089 0
1090 };
1091
1092 let collaborative_workspaces = if let Some(ws_id) = workspace_id {
1094 sqlx::query_scalar::<_, i64>(
1095 r"
1096 SELECT COUNT(DISTINCT json_extract(metadata, '$.workspace_id'))
1097 FROM pillar_usage_events
1098 WHERE pillar = 'cloud'
1099 AND metric_name = 'workspace_creation'
1100 AND json_extract(metric_value, '$.collaborative') = 1
1101 AND workspace_id = $1
1102 AND timestamp >= $2 AND timestamp <= $3
1103 ",
1104 )
1105 .bind(ws_id)
1106 .bind(start_time)
1107 .bind(end_time)
1108 .fetch_one(self.pool())
1109 .await
1110 .unwrap_or(0)
1111 } else if let Some(org) = org_id {
1112 sqlx::query_scalar::<_, i64>(
1113 r"
1114 SELECT COUNT(DISTINCT json_extract(metadata, '$.workspace_id'))
1115 FROM pillar_usage_events
1116 WHERE pillar = 'cloud'
1117 AND metric_name = 'workspace_creation'
1118 AND json_extract(metric_value, '$.collaborative') = 1
1119 AND org_id = $1
1120 AND timestamp >= $2 AND timestamp <= $3
1121 ",
1122 )
1123 .bind(org)
1124 .bind(start_time)
1125 .bind(end_time)
1126 .fetch_one(self.pool())
1127 .await
1128 .unwrap_or(0)
1129 } else {
1130 0
1131 };
1132
1133 Ok(CloudPillarMetrics {
1134 shared_scenarios_count: shared_scenarios_count as u64,
1135 marketplace_downloads: marketplace_downloads as u64,
1136 org_templates_used: org_templates_used as u64,
1137 collaborative_workspaces: collaborative_workspaces as u64,
1138 })
1139 }
1140
1141 #[allow(clippy::too_many_lines, clippy::cast_sign_loss)]
1143 async fn get_ai_pillar_metrics(
1144 &self,
1145 workspace_id: Option<&str>,
1146 org_id: Option<&str>,
1147 start_time: i64,
1148 end_time: i64,
1149 ) -> Result<AiPillarMetrics> {
1150 let ai_generated_mocks = if let Some(ws_id) = workspace_id {
1152 sqlx::query_scalar::<_, i64>(
1153 r"
1154 SELECT COUNT(*)
1155 FROM pillar_usage_events
1156 WHERE pillar = 'ai'
1157 AND metric_name = 'ai_generation'
1158 AND json_extract(metric_value, '$.type') = 'mock'
1159 AND workspace_id = $1
1160 AND timestamp >= $2 AND timestamp <= $3
1161 ",
1162 )
1163 .bind(ws_id)
1164 .bind(start_time)
1165 .bind(end_time)
1166 .fetch_one(self.pool())
1167 .await
1168 .unwrap_or(0)
1169 } else if let Some(org) = org_id {
1170 sqlx::query_scalar::<_, i64>(
1171 r"
1172 SELECT COUNT(*)
1173 FROM pillar_usage_events
1174 WHERE pillar = 'ai'
1175 AND metric_name = 'ai_generation'
1176 AND json_extract(metric_value, '$.type') = 'mock'
1177 AND org_id = $1
1178 AND timestamp >= $2 AND timestamp <= $3
1179 ",
1180 )
1181 .bind(org)
1182 .bind(start_time)
1183 .bind(end_time)
1184 .fetch_one(self.pool())
1185 .await
1186 .unwrap_or(0)
1187 } else {
1188 0
1189 };
1190
1191 let ai_contract_diffs = if let Some(ws_id) = workspace_id {
1193 sqlx::query_scalar::<_, i64>(
1194 r"
1195 SELECT COUNT(*)
1196 FROM pillar_usage_events
1197 WHERE pillar = 'ai'
1198 AND metric_name = 'ai_generation'
1199 AND json_extract(metric_value, '$.type') = 'contract_diff'
1200 AND workspace_id = $1
1201 AND timestamp >= $2 AND timestamp <= $3
1202 ",
1203 )
1204 .bind(ws_id)
1205 .bind(start_time)
1206 .bind(end_time)
1207 .fetch_one(self.pool())
1208 .await
1209 .unwrap_or(0)
1210 } else if let Some(org) = org_id {
1211 sqlx::query_scalar::<_, i64>(
1212 r"
1213 SELECT COUNT(*)
1214 FROM pillar_usage_events
1215 WHERE pillar = 'ai'
1216 AND metric_name = 'ai_generation'
1217 AND json_extract(metric_value, '$.type') = 'contract_diff'
1218 AND org_id = $1
1219 AND timestamp >= $2 AND timestamp <= $3
1220 ",
1221 )
1222 .bind(org)
1223 .bind(start_time)
1224 .bind(end_time)
1225 .fetch_one(self.pool())
1226 .await
1227 .unwrap_or(0)
1228 } else {
1229 0
1230 };
1231
1232 let ai_refinements = if let Some(ws_id) = workspace_id {
1234 sqlx::query_scalar::<_, i64>(
1235 r"
1236 SELECT COUNT(*)
1237 FROM pillar_usage_events
1238 WHERE pillar = 'ai'
1239 AND metric_name = 'ai_refinement'
1240 AND workspace_id = $1
1241 AND timestamp >= $2 AND timestamp <= $3
1242 ",
1243 )
1244 .bind(ws_id)
1245 .bind(start_time)
1246 .bind(end_time)
1247 .fetch_one(self.pool())
1248 .await
1249 .unwrap_or(0)
1250 } else if let Some(org) = org_id {
1251 sqlx::query_scalar::<_, i64>(
1252 r"
1253 SELECT COUNT(*)
1254 FROM pillar_usage_events
1255 WHERE pillar = 'ai'
1256 AND metric_name = 'ai_refinement'
1257 AND org_id = $1
1258 AND timestamp >= $2 AND timestamp <= $3
1259 ",
1260 )
1261 .bind(org)
1262 .bind(start_time)
1263 .bind(end_time)
1264 .fetch_one(self.pool())
1265 .await
1266 .unwrap_or(0)
1267 } else {
1268 0
1269 };
1270
1271 let voice_commands = if let Some(ws_id) = workspace_id {
1273 sqlx::query_scalar::<_, i64>(
1274 r"
1275 SELECT COUNT(*)
1276 FROM pillar_usage_events
1277 WHERE pillar = 'ai'
1278 AND metric_name = 'voice_command'
1279 AND workspace_id = $1
1280 AND timestamp >= $2 AND timestamp <= $3
1281 ",
1282 )
1283 .bind(ws_id)
1284 .bind(start_time)
1285 .bind(end_time)
1286 .fetch_one(self.pool())
1287 .await
1288 .unwrap_or(0)
1289 } else if let Some(org) = org_id {
1290 sqlx::query_scalar::<_, i64>(
1291 r"
1292 SELECT COUNT(*)
1293 FROM pillar_usage_events
1294 WHERE pillar = 'ai'
1295 AND metric_name = 'voice_command'
1296 AND org_id = $1
1297 AND timestamp >= $2 AND timestamp <= $3
1298 ",
1299 )
1300 .bind(org)
1301 .bind(start_time)
1302 .bind(end_time)
1303 .fetch_one(self.pool())
1304 .await
1305 .unwrap_or(0)
1306 } else {
1307 0
1308 };
1309
1310 let llm_assisted_operations = ai_generated_mocks + ai_contract_diffs + ai_refinements;
1312
1313 Ok(AiPillarMetrics {
1314 ai_generated_mocks: ai_generated_mocks as u64,
1315 ai_contract_diffs: ai_contract_diffs as u64,
1316 voice_commands: voice_commands as u64,
1317 llm_assisted_operations: llm_assisted_operations as u64,
1318 })
1319 }
1320}
1321
1322#[cfg(test)]
1323mod tests {
1324 use super::*;
1325
1326 #[test]
1327 fn test_pillar_as_str() {
1328 assert_eq!(Pillar::Reality.as_str(), "reality");
1329 assert_eq!(Pillar::Contracts.as_str(), "contracts");
1330 assert_eq!(Pillar::DevX.as_str(), "devx");
1331 assert_eq!(Pillar::Cloud.as_str(), "cloud");
1332 assert_eq!(Pillar::Ai.as_str(), "ai");
1333 }
1334
1335 #[test]
1336 fn test_pillar_from_str() {
1337 assert_eq!(Pillar::parse("reality"), Some(Pillar::Reality));
1338 assert_eq!(Pillar::parse("contracts"), Some(Pillar::Contracts));
1339 assert_eq!(Pillar::parse("devx"), Some(Pillar::DevX));
1340 assert_eq!(Pillar::parse("cloud"), Some(Pillar::Cloud));
1341 assert_eq!(Pillar::parse("ai"), Some(Pillar::Ai));
1342 assert_eq!(Pillar::parse("unknown"), None);
1343 }
1344
1345 #[test]
1346 fn test_pillar_from_str_case_insensitive() {
1347 assert_eq!(Pillar::parse("REALITY"), Some(Pillar::Reality));
1348 assert_eq!(Pillar::parse("Reality"), Some(Pillar::Reality));
1349 assert_eq!(Pillar::parse("DEVX"), Some(Pillar::DevX));
1350 assert_eq!(Pillar::parse("AI"), Some(Pillar::Ai));
1351 }
1352
1353 #[test]
1354 fn test_pillar_display() {
1355 assert_eq!(format!("{}", Pillar::Reality), "reality");
1356 assert_eq!(format!("{}", Pillar::Contracts), "contracts");
1357 assert_eq!(format!("{}", Pillar::DevX), "devx");
1358 assert_eq!(format!("{}", Pillar::Cloud), "cloud");
1359 assert_eq!(format!("{}", Pillar::Ai), "ai");
1360 }
1361
1362 #[test]
1363 fn test_pillar_serialize() {
1364 let pillar = Pillar::Reality;
1365 let json = serde_json::to_string(&pillar).unwrap();
1366 assert_eq!(json, "\"reality\"");
1367 }
1368
1369 #[test]
1370 fn test_pillar_deserialize() {
1371 let pillar: Pillar = serde_json::from_str("\"contracts\"").unwrap();
1372 assert_eq!(pillar, Pillar::Contracts);
1373 }
1374
1375 #[test]
1376 fn test_pillar_clone() {
1377 let pillar = Pillar::DevX;
1378 let cloned = pillar;
1379 assert_eq!(pillar, cloned);
1380 }
1381
1382 #[test]
1383 fn test_pillar_hash() {
1384 use std::collections::HashSet;
1385 let mut set = HashSet::new();
1386 set.insert(Pillar::Reality);
1387 set.insert(Pillar::Contracts);
1388 set.insert(Pillar::Reality); assert_eq!(set.len(), 2);
1390 }
1391
1392 #[test]
1393 fn test_pillar_usage_event_serialize() {
1394 let event = PillarUsageEvent {
1395 workspace_id: Some("ws-123".to_string()),
1396 org_id: None,
1397 pillar: Pillar::Reality,
1398 metric_name: "blended_reality_ratio".to_string(),
1399 metric_value: serde_json::json!({"ratio": 0.75}),
1400 timestamp: Utc::now(),
1401 };
1402 let json = serde_json::to_string(&event).unwrap();
1403 assert!(json.contains("ws-123"));
1404 assert!(json.contains("reality"));
1405 assert!(json.contains("blended_reality_ratio"));
1406 }
1407
1408 #[test]
1409 fn test_pillar_usage_event_deserialize() {
1410 let json = r#"{
1411 "workspace_id": "ws-456",
1412 "org_id": null,
1413 "pillar": "contracts",
1414 "metric_name": "validation_mode",
1415 "metric_value": {"mode": "enforce"},
1416 "timestamp": "2024-01-15T12:00:00Z"
1417 }"#;
1418 let event: PillarUsageEvent = serde_json::from_str(json).unwrap();
1419 assert_eq!(event.workspace_id, Some("ws-456".to_string()));
1420 assert_eq!(event.pillar, Pillar::Contracts);
1421 assert_eq!(event.metric_name, "validation_mode");
1422 }
1423
1424 #[test]
1425 fn test_reality_pillar_metrics_serialize() {
1426 let metrics = RealityPillarMetrics {
1427 blended_reality_percent: 75.0,
1428 smart_personas_percent: 60.0,
1429 static_fixtures_percent: 40.0,
1430 avg_reality_level: 3.5,
1431 chaos_enabled_count: 5,
1432 total_scenarios: 100,
1433 };
1434 let json = serde_json::to_string(&metrics).unwrap();
1435 assert!(json.contains("blended_reality_percent"));
1436 assert!(json.contains("75.0"));
1437 }
1438
1439 #[test]
1440 fn test_contracts_pillar_metrics_serialize() {
1441 let metrics = ContractsPillarMetrics {
1442 validation_disabled_percent: 10.0,
1443 validation_warn_percent: 30.0,
1444 validation_enforce_percent: 60.0,
1445 drift_budget_configured_count: 15,
1446 drift_incidents_count: 3,
1447 contract_sync_cycles: 42,
1448 };
1449 let json = serde_json::to_string(&metrics).unwrap();
1450 assert!(json.contains("validation_enforce_percent"));
1451 assert!(json.contains("60.0"));
1452 }
1453
1454 #[test]
1455 fn test_devx_pillar_metrics_serialize() {
1456 let metrics = DevXPillarMetrics {
1457 sdk_installations: 100,
1458 client_generations: 50,
1459 playground_sessions: 200,
1460 cli_commands: 1000,
1461 };
1462 let json = serde_json::to_string(&metrics).unwrap();
1463 assert!(json.contains("sdk_installations"));
1464 assert!(json.contains("100"));
1465 }
1466
1467 #[test]
1468 fn test_cloud_pillar_metrics_serialize() {
1469 let metrics = CloudPillarMetrics {
1470 shared_scenarios_count: 25,
1471 marketplace_downloads: 500,
1472 org_templates_used: 10,
1473 collaborative_workspaces: 5,
1474 };
1475 let json = serde_json::to_string(&metrics).unwrap();
1476 assert!(json.contains("marketplace_downloads"));
1477 assert!(json.contains("500"));
1478 }
1479
1480 #[test]
1481 fn test_ai_pillar_metrics_serialize() {
1482 let metrics = AiPillarMetrics {
1483 ai_generated_mocks: 100,
1484 ai_contract_diffs: 50,
1485 voice_commands: 25,
1486 llm_assisted_operations: 175,
1487 };
1488 let json = serde_json::to_string(&metrics).unwrap();
1489 assert!(json.contains("ai_generated_mocks"));
1490 assert!(json.contains("100"));
1491 }
1492
1493 #[test]
1494 fn test_pillar_usage_metrics_serialize() {
1495 let metrics = PillarUsageMetrics {
1496 workspace_id: Some("ws-123".to_string()),
1497 org_id: None,
1498 time_range: "3600s".to_string(),
1499 reality: Some(RealityPillarMetrics {
1500 blended_reality_percent: 50.0,
1501 smart_personas_percent: 75.0,
1502 static_fixtures_percent: 25.0,
1503 avg_reality_level: 4.0,
1504 chaos_enabled_count: 2,
1505 total_scenarios: 50,
1506 }),
1507 contracts: None,
1508 devx: None,
1509 cloud: None,
1510 ai: None,
1511 };
1512 let json = serde_json::to_string(&metrics).unwrap();
1513 assert!(json.contains("ws-123"));
1514 assert!(json.contains("3600s"));
1515 assert!(json.contains("blended_reality_percent"));
1516 }
1517
1518 #[test]
1519 fn test_pillar_usage_metrics_all_pillars() {
1520 let metrics = PillarUsageMetrics {
1521 workspace_id: Some("ws-all".to_string()),
1522 org_id: Some("org-1".to_string()),
1523 time_range: "86400s".to_string(),
1524 reality: Some(RealityPillarMetrics {
1525 blended_reality_percent: 80.0,
1526 smart_personas_percent: 90.0,
1527 static_fixtures_percent: 10.0,
1528 avg_reality_level: 4.5,
1529 chaos_enabled_count: 10,
1530 total_scenarios: 200,
1531 }),
1532 contracts: Some(ContractsPillarMetrics {
1533 validation_disabled_percent: 5.0,
1534 validation_warn_percent: 15.0,
1535 validation_enforce_percent: 80.0,
1536 drift_budget_configured_count: 30,
1537 drift_incidents_count: 2,
1538 contract_sync_cycles: 100,
1539 }),
1540 devx: Some(DevXPillarMetrics {
1541 sdk_installations: 500,
1542 client_generations: 200,
1543 playground_sessions: 1000,
1544 cli_commands: 5000,
1545 }),
1546 cloud: Some(CloudPillarMetrics {
1547 shared_scenarios_count: 50,
1548 marketplace_downloads: 1000,
1549 org_templates_used: 20,
1550 collaborative_workspaces: 15,
1551 }),
1552 ai: Some(AiPillarMetrics {
1553 ai_generated_mocks: 300,
1554 ai_contract_diffs: 100,
1555 voice_commands: 50,
1556 llm_assisted_operations: 450,
1557 }),
1558 };
1559 let json = serde_json::to_string(&metrics).unwrap();
1560 assert!(json.contains("org-1"));
1561 assert!(json.contains("reality"));
1563 assert!(json.contains("contracts"));
1564 assert!(json.contains("devx"));
1565 assert!(json.contains("cloud"));
1566 assert!(json.contains("ai"));
1567 }
1568
1569 #[test]
1570 fn test_pillar_debug() {
1571 let pillar = Pillar::Reality;
1572 let debug = format!("{pillar:?}");
1573 assert!(debug.contains("Reality"));
1574 }
1575
1576 #[test]
1577 fn test_reality_pillar_metrics_clone() {
1578 let metrics = RealityPillarMetrics {
1579 blended_reality_percent: 50.0,
1580 smart_personas_percent: 60.0,
1581 static_fixtures_percent: 40.0,
1582 avg_reality_level: 3.0,
1583 chaos_enabled_count: 1,
1584 total_scenarios: 10,
1585 };
1586 let cloned = metrics.clone();
1587 assert!(
1588 (metrics.blended_reality_percent - cloned.blended_reality_percent).abs() < f64::EPSILON
1589 );
1590 assert_eq!(metrics.total_scenarios, cloned.total_scenarios);
1591 }
1592
1593 #[test]
1594 fn test_pillar_usage_event_clone() {
1595 let event = PillarUsageEvent {
1596 workspace_id: Some("ws-test".to_string()),
1597 org_id: None,
1598 pillar: Pillar::Ai,
1599 metric_name: "ai_generation".to_string(),
1600 metric_value: serde_json::json!({"type": "mock"}),
1601 timestamp: Utc::now(),
1602 };
1603 let cloned = event.clone();
1604 assert_eq!(event.workspace_id, cloned.workspace_id);
1605 assert_eq!(event.pillar, cloned.pillar);
1606 assert_eq!(event.metric_name, cloned.metric_name);
1607 }
1608}