1use crate::database::AnalyticsDatabase;
7use crate::error::Result;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
15#[serde(rename_all = "lowercase")]
16pub enum Pillar {
17 Reality,
19 Contracts,
21 DevX,
23 Cloud,
25 Ai,
27}
28
29impl Pillar {
30 pub fn as_str(&self) -> &'static str {
32 match self {
33 Pillar::Reality => "reality",
34 Pillar::Contracts => "contracts",
35 Pillar::DevX => "devx",
36 Pillar::Cloud => "cloud",
37 Pillar::Ai => "ai",
38 }
39 }
40
41 pub fn from_str(s: &str) -> Option<Self> {
43 match s.to_lowercase().as_str() {
44 "reality" => Some(Pillar::Reality),
45 "contracts" => Some(Pillar::Contracts),
46 "devx" => Some(Pillar::DevX),
47 "cloud" => Some(Pillar::Cloud),
48 "ai" => Some(Pillar::Ai),
49 _ => None,
50 }
51 }
52}
53
54impl std::fmt::Display for Pillar {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "{}", self.as_str())
57 }
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct PillarUsageEvent {
63 pub workspace_id: Option<String>,
65 pub org_id: Option<String>,
67 pub pillar: Pillar,
69 pub metric_name: String,
71 pub metric_value: Value,
73 pub timestamp: DateTime<Utc>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct PillarUsageMetrics {
80 pub workspace_id: Option<String>,
82 pub org_id: Option<String>,
84 pub time_range: String,
86 pub reality: Option<RealityPillarMetrics>,
88 pub contracts: Option<ContractsPillarMetrics>,
90 pub devx: Option<DevXPillarMetrics>,
92 pub cloud: Option<CloudPillarMetrics>,
94 pub ai: Option<AiPillarMetrics>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct RealityPillarMetrics {
101 pub blended_reality_percent: f64,
103 pub smart_personas_percent: f64,
105 pub static_fixtures_percent: f64,
107 pub avg_reality_level: f64,
109 pub chaos_enabled_count: u64,
111 pub total_scenarios: u64,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct ContractsPillarMetrics {
118 pub validation_disabled_percent: f64,
120 pub validation_warn_percent: f64,
122 pub validation_enforce_percent: f64,
124 pub drift_budget_configured_count: u64,
126 pub drift_incidents_count: u64,
128 pub contract_sync_cycles: u64,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct DevXPillarMetrics {
135 pub sdk_installations: u64,
137 pub client_generations: u64,
139 pub playground_sessions: u64,
141 pub cli_commands: u64,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct CloudPillarMetrics {
148 pub shared_scenarios_count: u64,
150 pub marketplace_downloads: u64,
152 pub org_templates_used: u64,
154 pub collaborative_workspaces: u64,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct AiPillarMetrics {
161 pub ai_generated_mocks: u64,
163 pub ai_contract_diffs: u64,
165 pub voice_commands: u64,
167 pub llm_assisted_operations: u64,
169}
170
171impl AnalyticsDatabase {
172 pub async fn record_pillar_usage(&self, event: &PillarUsageEvent) -> Result<()> {
174 let timestamp = event.timestamp.timestamp();
175 let metric_value_json = serde_json::to_string(&event.metric_value)?;
176
177 sqlx::query(
178 r#"
179 INSERT INTO pillar_usage_events (
180 workspace_id, org_id, pillar, metric_name, metric_value, timestamp
181 )
182 VALUES ($1, $2, $3, $4, $5, $6)
183 "#,
184 )
185 .bind(event.workspace_id.as_deref())
186 .bind(event.org_id.as_deref())
187 .bind(event.pillar.as_str())
188 .bind(&event.metric_name)
189 .bind(&metric_value_json)
190 .bind(timestamp)
191 .execute(self.pool())
192 .await?;
193
194 Ok(())
195 }
196
197 pub async fn get_workspace_pillar_metrics(
199 &self,
200 workspace_id: &str,
201 duration_seconds: i64,
202 ) -> Result<PillarUsageMetrics> {
203 let end_time = Utc::now().timestamp();
204 let start_time = end_time - duration_seconds;
205
206 let reality = self
208 .get_reality_pillar_metrics(Some(workspace_id), None, start_time, end_time)
209 .await?;
210
211 let contracts = self
213 .get_contracts_pillar_metrics(Some(workspace_id), None, start_time, end_time)
214 .await?;
215
216 let devx = self
218 .get_devx_pillar_metrics(Some(workspace_id), None, start_time, end_time)
219 .await?;
220
221 let cloud = self
223 .get_cloud_pillar_metrics(Some(workspace_id), None, start_time, end_time)
224 .await?;
225
226 let ai = self
228 .get_ai_pillar_metrics(Some(workspace_id), None, start_time, end_time)
229 .await?;
230
231 Ok(PillarUsageMetrics {
232 workspace_id: Some(workspace_id.to_string()),
233 org_id: None,
234 time_range: format!("{}s", duration_seconds),
235 reality: Some(reality),
236 contracts: Some(contracts),
237 devx: Some(devx),
238 cloud: Some(cloud),
239 ai: Some(ai),
240 })
241 }
242
243 pub async fn get_org_pillar_metrics(
245 &self,
246 org_id: &str,
247 duration_seconds: i64,
248 ) -> Result<PillarUsageMetrics> {
249 let end_time = Utc::now().timestamp();
250 let start_time = end_time - duration_seconds;
251
252 let reality = self
254 .get_reality_pillar_metrics(None, Some(org_id), start_time, end_time)
255 .await?;
256
257 let contracts = self
259 .get_contracts_pillar_metrics(None, Some(org_id), start_time, end_time)
260 .await?;
261
262 let devx = self.get_devx_pillar_metrics(None, Some(org_id), start_time, end_time).await?;
264
265 let cloud = self.get_cloud_pillar_metrics(None, Some(org_id), start_time, end_time).await?;
267
268 let ai = self.get_ai_pillar_metrics(None, Some(org_id), start_time, end_time).await?;
270
271 Ok(PillarUsageMetrics {
272 workspace_id: None,
273 org_id: Some(org_id.to_string()),
274 time_range: format!("{}s", duration_seconds),
275 reality: Some(reality),
276 contracts: Some(contracts),
277 devx: Some(devx),
278 cloud: Some(cloud),
279 ai: Some(ai),
280 })
281 }
282
283 async fn get_reality_pillar_metrics(
285 &self,
286 workspace_id: Option<&str>,
287 org_id: Option<&str>,
288 start_time: i64,
289 end_time: i64,
290 ) -> Result<RealityPillarMetrics> {
291 let blended_reality_query = if let Some(ws_id) = workspace_id {
293 sqlx::query_scalar::<_, f64>(
294 r#"
295 SELECT AVG(CAST(json_extract(metric_value, '$.ratio') AS REAL)) * 100.0
296 FROM pillar_usage_events
297 WHERE pillar = 'reality'
298 AND metric_name = 'blended_reality_ratio'
299 AND workspace_id = $1
300 AND timestamp >= $2 AND timestamp <= $3
301 "#,
302 )
303 .bind(ws_id)
304 .bind(start_time)
305 .bind(end_time)
306 } else if let Some(org) = org_id {
307 sqlx::query_scalar::<_, f64>(
308 r#"
309 SELECT AVG(CAST(json_extract(metric_value, '$.ratio') AS REAL)) * 100.0
310 FROM pillar_usage_events
311 WHERE pillar = 'reality'
312 AND metric_name = 'blended_reality_ratio'
313 AND org_id = $1
314 AND timestamp >= $2 AND timestamp <= $3
315 "#,
316 )
317 .bind(org)
318 .bind(start_time)
319 .bind(end_time)
320 } else {
321 return Err(crate::error::AnalyticsError::InvalidInput(
322 "Either workspace_id or org_id must be provided".to_string(),
323 ));
324 };
325
326 let blended_reality_percent =
327 blended_reality_query.fetch_one(self.pool()).await.unwrap_or(0.0);
328
329 let smart_personas_query = if let Some(ws_id) = workspace_id {
331 sqlx::query_scalar::<_, i64>(
332 r#"
333 SELECT COUNT(*) FROM pillar_usage_events
334 WHERE pillar = 'reality'
335 AND metric_name = 'persona_usage'
336 AND json_extract(metric_value, '$.type') = 'smart'
337 AND workspace_id = $1
338 AND timestamp >= $2 AND timestamp <= $3
339 "#,
340 )
341 .bind(ws_id)
342 .bind(start_time)
343 .bind(end_time)
344 } else if let Some(org) = org_id {
345 sqlx::query_scalar::<_, i64>(
346 r#"
347 SELECT COUNT(*) FROM pillar_usage_events
348 WHERE pillar = 'reality'
349 AND metric_name = 'persona_usage'
350 AND json_extract(metric_value, '$.type') = 'smart'
351 AND org_id = $1
352 AND timestamp >= $2 AND timestamp <= $3
353 "#,
354 )
355 .bind(org)
356 .bind(start_time)
357 .bind(end_time)
358 } else {
359 return Err(crate::error::AnalyticsError::InvalidInput(
360 "Either workspace_id or org_id must be provided".to_string(),
361 ));
362 };
363
364 let smart_personas_count = smart_personas_query.fetch_one(self.pool()).await.unwrap_or(0);
365
366 let static_fixtures_query = if let Some(ws_id) = workspace_id {
367 sqlx::query_scalar::<_, i64>(
368 r#"
369 SELECT COUNT(*) FROM pillar_usage_events
370 WHERE pillar = 'reality'
371 AND metric_name = 'persona_usage'
372 AND json_extract(metric_value, '$.type') = 'static'
373 AND workspace_id = $1
374 AND timestamp >= $2 AND timestamp <= $3
375 "#,
376 )
377 .bind(ws_id)
378 .bind(start_time)
379 .bind(end_time)
380 } else if let Some(org) = org_id {
381 sqlx::query_scalar::<_, i64>(
382 r#"
383 SELECT COUNT(*) FROM pillar_usage_events
384 WHERE pillar = 'reality'
385 AND metric_name = 'persona_usage'
386 AND json_extract(metric_value, '$.type') = 'static'
387 AND org_id = $1
388 AND timestamp >= $2 AND timestamp <= $3
389 "#,
390 )
391 .bind(org)
392 .bind(start_time)
393 .bind(end_time)
394 } else {
395 return Err(crate::error::AnalyticsError::InvalidInput(
396 "Either workspace_id or org_id must be provided".to_string(),
397 ));
398 };
399
400 let static_fixtures_count = static_fixtures_query.fetch_one(self.pool()).await.unwrap_or(0);
401
402 let total = smart_personas_count + static_fixtures_count;
403 let smart_personas_percent = if total > 0 {
404 (smart_personas_count as f64 / total as f64) * 100.0
405 } else {
406 0.0
407 };
408 let static_fixtures_percent = if total > 0 {
409 (static_fixtures_count as f64 / total as f64) * 100.0
410 } else {
411 0.0
412 };
413
414 let avg_reality_level = if let Some(ws_id) = workspace_id {
416 sqlx::query_scalar::<_, f64>(
417 r#"
418 SELECT AVG(CAST(json_extract(metric_value, '$.level') AS REAL))
419 FROM pillar_usage_events
420 WHERE pillar = 'reality'
421 AND metric_name = 'reality_level'
422 AND workspace_id = $1
423 AND timestamp >= $2 AND timestamp <= $3
424 "#,
425 )
426 .bind(ws_id)
427 .bind(start_time)
428 .bind(end_time)
429 .fetch_one(self.pool())
430 .await
431 .unwrap_or(0.0)
432 } else if let Some(org) = org_id {
433 sqlx::query_scalar::<_, f64>(
434 r#"
435 SELECT AVG(CAST(json_extract(metric_value, '$.level') AS REAL))
436 FROM pillar_usage_events
437 WHERE pillar = 'reality'
438 AND metric_name = 'reality_level'
439 AND org_id = $1
440 AND timestamp >= $2 AND timestamp <= $3
441 "#,
442 )
443 .bind(org)
444 .bind(start_time)
445 .bind(end_time)
446 .fetch_one(self.pool())
447 .await
448 .unwrap_or(0.0)
449 } else {
450 0.0
451 };
452
453 let chaos_enabled_count = if let Some(ws_id) = workspace_id {
455 sqlx::query_scalar::<_, i64>(
456 r#"
457 SELECT COUNT(DISTINCT json_extract(metadata, '$.scenario_id'))
458 FROM pillar_usage_events
459 WHERE pillar = 'reality'
460 AND metric_name = 'chaos_injection'
461 AND json_extract(metric_value, '$.enabled') = 1
462 AND workspace_id = $1
463 AND timestamp >= $2 AND timestamp <= $3
464 "#,
465 )
466 .bind(ws_id)
467 .bind(start_time)
468 .bind(end_time)
469 .fetch_one(self.pool())
470 .await
471 .unwrap_or(0)
472 } else if let Some(org) = org_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 org_id = $1
481 AND timestamp >= $2 AND timestamp <= $3
482 "#,
483 )
484 .bind(org)
485 .bind(start_time)
486 .bind(end_time)
487 .fetch_one(self.pool())
488 .await
489 .unwrap_or(0)
490 } else {
491 0
492 };
493
494 Ok(RealityPillarMetrics {
495 blended_reality_percent,
496 smart_personas_percent,
497 static_fixtures_percent,
498 avg_reality_level,
499 chaos_enabled_count: chaos_enabled_count as u64,
500 total_scenarios: total as u64,
501 })
502 }
503
504 async fn get_contracts_pillar_metrics(
506 &self,
507 workspace_id: Option<&str>,
508 org_id: Option<&str>,
509 start_time: i64,
510 end_time: i64,
511 ) -> Result<ContractsPillarMetrics> {
512 let validation_disabled_query = if let Some(ws_id) = workspace_id {
514 sqlx::query_scalar::<_, i64>(
515 r#"
516 SELECT COUNT(*) FROM pillar_usage_events
517 WHERE pillar = 'contracts'
518 AND metric_name = 'validation_mode'
519 AND json_extract(metric_value, '$.mode') = 'disabled'
520 AND workspace_id = $1
521 AND timestamp >= $2 AND timestamp <= $3
522 "#,
523 )
524 .bind(ws_id)
525 .bind(start_time)
526 .bind(end_time)
527 } else if let Some(org) = org_id {
528 sqlx::query_scalar::<_, i64>(
529 r#"
530 SELECT COUNT(*) FROM pillar_usage_events
531 WHERE pillar = 'contracts'
532 AND metric_name = 'validation_mode'
533 AND json_extract(metric_value, '$.mode') = 'disabled'
534 AND org_id = $1
535 AND timestamp >= $2 AND timestamp <= $3
536 "#,
537 )
538 .bind(org)
539 .bind(start_time)
540 .bind(end_time)
541 } else {
542 return Err(crate::error::AnalyticsError::InvalidInput(
543 "Either workspace_id or org_id must be provided".to_string(),
544 ));
545 };
546
547 let validation_disabled_count =
548 validation_disabled_query.fetch_one(self.pool()).await.unwrap_or(0);
549
550 let validation_warn_query = if let Some(ws_id) = workspace_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') = 'warn'
557 AND workspace_id = $1
558 AND timestamp >= $2 AND timestamp <= $3
559 "#,
560 )
561 .bind(ws_id)
562 .bind(start_time)
563 .bind(end_time)
564 } else if let Some(org) = org_id {
565 sqlx::query_scalar::<_, i64>(
566 r#"
567 SELECT COUNT(*) FROM pillar_usage_events
568 WHERE pillar = 'contracts'
569 AND metric_name = 'validation_mode'
570 AND json_extract(metric_value, '$.mode') = 'warn'
571 AND org_id = $1
572 AND timestamp >= $2 AND timestamp <= $3
573 "#,
574 )
575 .bind(org)
576 .bind(start_time)
577 .bind(end_time)
578 } else {
579 return Err(crate::error::AnalyticsError::InvalidInput(
580 "Either workspace_id or org_id must be provided".to_string(),
581 ));
582 };
583
584 let validation_warn_count = validation_warn_query.fetch_one(self.pool()).await.unwrap_or(0);
585
586 let validation_enforce_query = if let Some(ws_id) = workspace_id {
587 sqlx::query_scalar::<_, i64>(
588 r#"
589 SELECT COUNT(*) FROM pillar_usage_events
590 WHERE pillar = 'contracts'
591 AND metric_name = 'validation_mode'
592 AND json_extract(metric_value, '$.mode') = 'enforce'
593 AND workspace_id = $1
594 AND timestamp >= $2 AND timestamp <= $3
595 "#,
596 )
597 .bind(ws_id)
598 .bind(start_time)
599 .bind(end_time)
600 } else if let Some(org) = org_id {
601 sqlx::query_scalar::<_, i64>(
602 r#"
603 SELECT COUNT(*) FROM pillar_usage_events
604 WHERE pillar = 'contracts'
605 AND metric_name = 'validation_mode'
606 AND json_extract(metric_value, '$.mode') = 'enforce'
607 AND org_id = $1
608 AND timestamp >= $2 AND timestamp <= $3
609 "#,
610 )
611 .bind(org)
612 .bind(start_time)
613 .bind(end_time)
614 } else {
615 return Err(crate::error::AnalyticsError::InvalidInput(
616 "Either workspace_id or org_id must be provided".to_string(),
617 ));
618 };
619
620 let validation_enforce_count =
621 validation_enforce_query.fetch_one(self.pool()).await.unwrap_or(0);
622
623 let total_validation_events =
624 validation_disabled_count + validation_warn_count + validation_enforce_count;
625 let validation_disabled_percent = if total_validation_events > 0 {
626 (validation_disabled_count as f64 / total_validation_events as f64) * 100.0
627 } else {
628 0.0
629 };
630 let validation_warn_percent = if total_validation_events > 0 {
631 (validation_warn_count as f64 / total_validation_events as f64) * 100.0
632 } else {
633 0.0
634 };
635 let validation_enforce_percent = if total_validation_events > 0 {
636 (validation_enforce_count as f64 / total_validation_events as f64) * 100.0
637 } else {
638 0.0
639 };
640
641 let drift_budget_configured_count = if let Some(ws_id) = workspace_id {
643 sqlx::query_scalar::<_, i64>(
644 r#"
645 SELECT COUNT(DISTINCT json_extract(metadata, '$.endpoint'))
646 FROM pillar_usage_events
647 WHERE pillar = 'contracts'
648 AND metric_name = 'drift_budget_configured'
649 AND workspace_id = $1
650 AND timestamp >= $2 AND timestamp <= $3
651 "#,
652 )
653 .bind(ws_id)
654 .bind(start_time)
655 .bind(end_time)
656 .fetch_one(self.pool())
657 .await
658 .unwrap_or(0)
659 } else if let Some(org) = org_id {
660 sqlx::query_scalar::<_, i64>(
661 r#"
662 SELECT COUNT(DISTINCT json_extract(metadata, '$.endpoint'))
663 FROM pillar_usage_events
664 WHERE pillar = 'contracts'
665 AND metric_name = 'drift_budget_configured'
666 AND org_id = $1
667 AND timestamp >= $2 AND timestamp <= $3
668 "#,
669 )
670 .bind(org)
671 .bind(start_time)
672 .bind(end_time)
673 .fetch_one(self.pool())
674 .await
675 .unwrap_or(0)
676 } else {
677 0
678 };
679
680 let drift_incidents_count = if let Some(ws_id) = workspace_id {
682 sqlx::query_scalar::<_, i64>(
683 r#"
684 SELECT COUNT(*)
685 FROM pillar_usage_events
686 WHERE pillar = 'contracts'
687 AND metric_name = 'drift_detection'
688 AND json_extract(metric_value, '$.incident') = 1
689 AND workspace_id = $1
690 AND timestamp >= $2 AND timestamp <= $3
691 "#,
692 )
693 .bind(ws_id)
694 .bind(start_time)
695 .bind(end_time)
696 .fetch_one(self.pool())
697 .await
698 .unwrap_or(0)
699 } else if let Some(org) = org_id {
700 sqlx::query_scalar::<_, i64>(
701 r#"
702 SELECT COUNT(*)
703 FROM pillar_usage_events
704 WHERE pillar = 'contracts'
705 AND metric_name = 'drift_detection'
706 AND json_extract(metric_value, '$.incident') = 1
707 AND org_id = $1
708 AND timestamp >= $2 AND timestamp <= $3
709 "#,
710 )
711 .bind(org)
712 .bind(start_time)
713 .bind(end_time)
714 .fetch_one(self.pool())
715 .await
716 .unwrap_or(0)
717 } else {
718 0
719 };
720
721 let contract_sync_cycles = if let Some(ws_id) = workspace_id {
723 sqlx::query_scalar::<_, i64>(
724 r#"
725 SELECT COUNT(DISTINCT json_extract(metadata, '$.sync_id'))
726 FROM pillar_usage_events
727 WHERE pillar = 'contracts'
728 AND metric_name = 'contract_sync'
729 AND workspace_id = $1
730 AND timestamp >= $2 AND timestamp <= $3
731 "#,
732 )
733 .bind(ws_id)
734 .bind(start_time)
735 .bind(end_time)
736 .fetch_one(self.pool())
737 .await
738 .unwrap_or(0)
739 } else if let Some(org) = org_id {
740 sqlx::query_scalar::<_, i64>(
741 r#"
742 SELECT COUNT(DISTINCT json_extract(metadata, '$.sync_id'))
743 FROM pillar_usage_events
744 WHERE pillar = 'contracts'
745 AND metric_name = 'contract_sync'
746 AND org_id = $1
747 AND timestamp >= $2 AND timestamp <= $3
748 "#,
749 )
750 .bind(org)
751 .bind(start_time)
752 .bind(end_time)
753 .fetch_one(self.pool())
754 .await
755 .unwrap_or(0)
756 } else {
757 0
758 };
759
760 Ok(ContractsPillarMetrics {
761 validation_disabled_percent,
762 validation_warn_percent,
763 validation_enforce_percent,
764 drift_budget_configured_count: drift_budget_configured_count as u64,
765 drift_incidents_count: drift_incidents_count as u64,
766 contract_sync_cycles: contract_sync_cycles as u64,
767 })
768 }
769
770 async fn get_devx_pillar_metrics(
772 &self,
773 workspace_id: Option<&str>,
774 org_id: Option<&str>,
775 start_time: i64,
776 end_time: i64,
777 ) -> Result<DevXPillarMetrics> {
778 let sdk_installations = if let Some(ws_id) = workspace_id {
780 sqlx::query_scalar::<_, i64>(
781 r#"
782 SELECT COUNT(DISTINCT json_extract(metadata, '$.sdk_type'))
783 FROM pillar_usage_events
784 WHERE pillar = 'devx'
785 AND metric_name = 'sdk_installation'
786 AND workspace_id = $1
787 AND timestamp >= $2 AND timestamp <= $3
788 "#,
789 )
790 .bind(ws_id)
791 .bind(start_time)
792 .bind(end_time)
793 .fetch_one(self.pool())
794 .await
795 .unwrap_or(0)
796 } else if let Some(org) = org_id {
797 sqlx::query_scalar::<_, i64>(
798 r#"
799 SELECT COUNT(DISTINCT json_extract(metadata, '$.sdk_type'))
800 FROM pillar_usage_events
801 WHERE pillar = 'devx'
802 AND metric_name = 'sdk_installation'
803 AND org_id = $1
804 AND timestamp >= $2 AND timestamp <= $3
805 "#,
806 )
807 .bind(org)
808 .bind(start_time)
809 .bind(end_time)
810 .fetch_one(self.pool())
811 .await
812 .unwrap_or(0)
813 } else {
814 0
815 };
816
817 let client_generations = if let Some(ws_id) = workspace_id {
819 sqlx::query_scalar::<_, i64>(
820 r#"
821 SELECT COUNT(*)
822 FROM pillar_usage_events
823 WHERE pillar = 'devx'
824 AND metric_name = 'client_generation'
825 AND workspace_id = $1
826 AND timestamp >= $2 AND timestamp <= $3
827 "#,
828 )
829 .bind(ws_id)
830 .bind(start_time)
831 .bind(end_time)
832 .fetch_one(self.pool())
833 .await
834 .unwrap_or(0)
835 } else if let Some(org) = org_id {
836 sqlx::query_scalar::<_, i64>(
837 r#"
838 SELECT COUNT(*)
839 FROM pillar_usage_events
840 WHERE pillar = 'devx'
841 AND metric_name = 'client_generation'
842 AND org_id = $1
843 AND timestamp >= $2 AND timestamp <= $3
844 "#,
845 )
846 .bind(org)
847 .bind(start_time)
848 .bind(end_time)
849 .fetch_one(self.pool())
850 .await
851 .unwrap_or(0)
852 } else {
853 0
854 };
855
856 let playground_sessions = if let Some(ws_id) = workspace_id {
858 sqlx::query_scalar::<_, i64>(
859 r#"
860 SELECT COUNT(DISTINCT json_extract(metadata, '$.session_id'))
861 FROM pillar_usage_events
862 WHERE pillar = 'devx'
863 AND metric_name = 'playground_session'
864 AND workspace_id = $1
865 AND timestamp >= $2 AND timestamp <= $3
866 "#,
867 )
868 .bind(ws_id)
869 .bind(start_time)
870 .bind(end_time)
871 .fetch_one(self.pool())
872 .await
873 .unwrap_or(0)
874 } else if let Some(org) = org_id {
875 sqlx::query_scalar::<_, i64>(
876 r#"
877 SELECT COUNT(DISTINCT json_extract(metadata, '$.session_id'))
878 FROM pillar_usage_events
879 WHERE pillar = 'devx'
880 AND metric_name = 'playground_session'
881 AND org_id = $1
882 AND timestamp >= $2 AND timestamp <= $3
883 "#,
884 )
885 .bind(org)
886 .bind(start_time)
887 .bind(end_time)
888 .fetch_one(self.pool())
889 .await
890 .unwrap_or(0)
891 } else {
892 0
893 };
894
895 let cli_commands = if let Some(ws_id) = workspace_id {
897 sqlx::query_scalar::<_, i64>(
898 r#"
899 SELECT COUNT(*)
900 FROM pillar_usage_events
901 WHERE pillar = 'devx'
902 AND metric_name = 'cli_command'
903 AND workspace_id = $1
904 AND timestamp >= $2 AND timestamp <= $3
905 "#,
906 )
907 .bind(ws_id)
908 .bind(start_time)
909 .bind(end_time)
910 .fetch_one(self.pool())
911 .await
912 .unwrap_or(0)
913 } else if let Some(org) = org_id {
914 sqlx::query_scalar::<_, i64>(
915 r#"
916 SELECT COUNT(*)
917 FROM pillar_usage_events
918 WHERE pillar = 'devx'
919 AND metric_name = 'cli_command'
920 AND org_id = $1
921 AND timestamp >= $2 AND timestamp <= $3
922 "#,
923 )
924 .bind(org)
925 .bind(start_time)
926 .bind(end_time)
927 .fetch_one(self.pool())
928 .await
929 .unwrap_or(0)
930 } else {
931 0
932 };
933
934 Ok(DevXPillarMetrics {
935 sdk_installations: sdk_installations as u64,
936 client_generations: client_generations as u64,
937 playground_sessions: playground_sessions as u64,
938 cli_commands: cli_commands as u64,
939 })
940 }
941
942 async fn get_cloud_pillar_metrics(
944 &self,
945 workspace_id: Option<&str>,
946 org_id: Option<&str>,
947 start_time: i64,
948 end_time: i64,
949 ) -> Result<CloudPillarMetrics> {
950 let shared_scenarios_count = if let Some(ws_id) = workspace_id {
952 sqlx::query_scalar::<_, i64>(
953 r#"
954 SELECT COUNT(DISTINCT json_extract(metadata, '$.scenario_id'))
955 FROM pillar_usage_events
956 WHERE pillar = 'cloud'
957 AND metric_name = 'scenario_shared'
958 AND workspace_id = $1
959 AND timestamp >= $2 AND timestamp <= $3
960 "#,
961 )
962 .bind(ws_id)
963 .bind(start_time)
964 .bind(end_time)
965 .fetch_one(self.pool())
966 .await
967 .unwrap_or(0)
968 } else if let Some(org) = org_id {
969 sqlx::query_scalar::<_, i64>(
970 r#"
971 SELECT COUNT(DISTINCT json_extract(metadata, '$.scenario_id'))
972 FROM pillar_usage_events
973 WHERE pillar = 'cloud'
974 AND metric_name = 'scenario_shared'
975 AND org_id = $1
976 AND timestamp >= $2 AND timestamp <= $3
977 "#,
978 )
979 .bind(org)
980 .bind(start_time)
981 .bind(end_time)
982 .fetch_one(self.pool())
983 .await
984 .unwrap_or(0)
985 } else {
986 0
987 };
988
989 let marketplace_downloads = if let Some(ws_id) = workspace_id {
991 sqlx::query_scalar::<_, i64>(
992 r#"
993 SELECT COUNT(*)
994 FROM pillar_usage_events
995 WHERE pillar = 'cloud'
996 AND metric_name = 'marketplace_download'
997 AND workspace_id = $1
998 AND timestamp >= $2 AND timestamp <= $3
999 "#,
1000 )
1001 .bind(ws_id)
1002 .bind(start_time)
1003 .bind(end_time)
1004 .fetch_one(self.pool())
1005 .await
1006 .unwrap_or(0)
1007 } else if let Some(org) = org_id {
1008 sqlx::query_scalar::<_, i64>(
1009 r#"
1010 SELECT COUNT(*)
1011 FROM pillar_usage_events
1012 WHERE pillar = 'cloud'
1013 AND metric_name = 'marketplace_download'
1014 AND org_id = $1
1015 AND timestamp >= $2 AND timestamp <= $3
1016 "#,
1017 )
1018 .bind(org)
1019 .bind(start_time)
1020 .bind(end_time)
1021 .fetch_one(self.pool())
1022 .await
1023 .unwrap_or(0)
1024 } else {
1025 0
1026 };
1027
1028 let org_templates_used = if let Some(ws_id) = workspace_id {
1030 sqlx::query_scalar::<_, i64>(
1031 r#"
1032 SELECT COUNT(DISTINCT json_extract(metadata, '$.template_id'))
1033 FROM pillar_usage_events
1034 WHERE pillar = 'cloud'
1035 AND metric_name = 'template_use'
1036 AND workspace_id = $1
1037 AND timestamp >= $2 AND timestamp <= $3
1038 "#,
1039 )
1040 .bind(ws_id)
1041 .bind(start_time)
1042 .bind(end_time)
1043 .fetch_one(self.pool())
1044 .await
1045 .unwrap_or(0)
1046 } else if let Some(org) = org_id {
1047 sqlx::query_scalar::<_, i64>(
1048 r#"
1049 SELECT COUNT(DISTINCT json_extract(metadata, '$.template_id'))
1050 FROM pillar_usage_events
1051 WHERE pillar = 'cloud'
1052 AND metric_name = 'template_use'
1053 AND org_id = $1
1054 AND timestamp >= $2 AND timestamp <= $3
1055 "#,
1056 )
1057 .bind(org)
1058 .bind(start_time)
1059 .bind(end_time)
1060 .fetch_one(self.pool())
1061 .await
1062 .unwrap_or(0)
1063 } else {
1064 0
1065 };
1066
1067 let collaborative_workspaces = if let Some(ws_id) = workspace_id {
1069 sqlx::query_scalar::<_, i64>(
1070 r#"
1071 SELECT COUNT(DISTINCT json_extract(metadata, '$.workspace_id'))
1072 FROM pillar_usage_events
1073 WHERE pillar = 'cloud'
1074 AND metric_name = 'workspace_creation'
1075 AND json_extract(metric_value, '$.collaborative') = 1
1076 AND workspace_id = $1
1077 AND timestamp >= $2 AND timestamp <= $3
1078 "#,
1079 )
1080 .bind(ws_id)
1081 .bind(start_time)
1082 .bind(end_time)
1083 .fetch_one(self.pool())
1084 .await
1085 .unwrap_or(0)
1086 } else if let Some(org) = org_id {
1087 sqlx::query_scalar::<_, i64>(
1088 r#"
1089 SELECT COUNT(DISTINCT json_extract(metadata, '$.workspace_id'))
1090 FROM pillar_usage_events
1091 WHERE pillar = 'cloud'
1092 AND metric_name = 'workspace_creation'
1093 AND json_extract(metric_value, '$.collaborative') = 1
1094 AND org_id = $1
1095 AND timestamp >= $2 AND timestamp <= $3
1096 "#,
1097 )
1098 .bind(org)
1099 .bind(start_time)
1100 .bind(end_time)
1101 .fetch_one(self.pool())
1102 .await
1103 .unwrap_or(0)
1104 } else {
1105 0
1106 };
1107
1108 Ok(CloudPillarMetrics {
1109 shared_scenarios_count: shared_scenarios_count as u64,
1110 marketplace_downloads: marketplace_downloads as u64,
1111 org_templates_used: org_templates_used as u64,
1112 collaborative_workspaces: collaborative_workspaces as u64,
1113 })
1114 }
1115
1116 async fn get_ai_pillar_metrics(
1118 &self,
1119 workspace_id: Option<&str>,
1120 org_id: Option<&str>,
1121 start_time: i64,
1122 end_time: i64,
1123 ) -> Result<AiPillarMetrics> {
1124 let ai_generated_mocks = if let Some(ws_id) = workspace_id {
1126 sqlx::query_scalar::<_, i64>(
1127 r#"
1128 SELECT COUNT(*)
1129 FROM pillar_usage_events
1130 WHERE pillar = 'ai'
1131 AND metric_name = 'ai_generation'
1132 AND json_extract(metric_value, '$.type') = 'mock'
1133 AND workspace_id = $1
1134 AND timestamp >= $2 AND timestamp <= $3
1135 "#,
1136 )
1137 .bind(ws_id)
1138 .bind(start_time)
1139 .bind(end_time)
1140 .fetch_one(self.pool())
1141 .await
1142 .unwrap_or(0)
1143 } else if let Some(org) = org_id {
1144 sqlx::query_scalar::<_, i64>(
1145 r#"
1146 SELECT COUNT(*)
1147 FROM pillar_usage_events
1148 WHERE pillar = 'ai'
1149 AND metric_name = 'ai_generation'
1150 AND json_extract(metric_value, '$.type') = 'mock'
1151 AND org_id = $1
1152 AND timestamp >= $2 AND timestamp <= $3
1153 "#,
1154 )
1155 .bind(org)
1156 .bind(start_time)
1157 .bind(end_time)
1158 .fetch_one(self.pool())
1159 .await
1160 .unwrap_or(0)
1161 } else {
1162 0
1163 };
1164
1165 let ai_contract_diffs = if let Some(ws_id) = workspace_id {
1167 sqlx::query_scalar::<_, i64>(
1168 r#"
1169 SELECT COUNT(*)
1170 FROM pillar_usage_events
1171 WHERE pillar = 'ai'
1172 AND metric_name = 'ai_generation'
1173 AND json_extract(metric_value, '$.type') = 'contract_diff'
1174 AND workspace_id = $1
1175 AND timestamp >= $2 AND timestamp <= $3
1176 "#,
1177 )
1178 .bind(ws_id)
1179 .bind(start_time)
1180 .bind(end_time)
1181 .fetch_one(self.pool())
1182 .await
1183 .unwrap_or(0)
1184 } else if let Some(org) = org_id {
1185 sqlx::query_scalar::<_, i64>(
1186 r#"
1187 SELECT COUNT(*)
1188 FROM pillar_usage_events
1189 WHERE pillar = 'ai'
1190 AND metric_name = 'ai_generation'
1191 AND json_extract(metric_value, '$.type') = 'contract_diff'
1192 AND org_id = $1
1193 AND timestamp >= $2 AND timestamp <= $3
1194 "#,
1195 )
1196 .bind(org)
1197 .bind(start_time)
1198 .bind(end_time)
1199 .fetch_one(self.pool())
1200 .await
1201 .unwrap_or(0)
1202 } else {
1203 0
1204 };
1205
1206 let ai_refinements = if let Some(ws_id) = workspace_id {
1208 sqlx::query_scalar::<_, i64>(
1209 r#"
1210 SELECT COUNT(*)
1211 FROM pillar_usage_events
1212 WHERE pillar = 'ai'
1213 AND metric_name = 'ai_refinement'
1214 AND workspace_id = $1
1215 AND timestamp >= $2 AND timestamp <= $3
1216 "#,
1217 )
1218 .bind(ws_id)
1219 .bind(start_time)
1220 .bind(end_time)
1221 .fetch_one(self.pool())
1222 .await
1223 .unwrap_or(0)
1224 } else if let Some(org) = org_id {
1225 sqlx::query_scalar::<_, i64>(
1226 r#"
1227 SELECT COUNT(*)
1228 FROM pillar_usage_events
1229 WHERE pillar = 'ai'
1230 AND metric_name = 'ai_refinement'
1231 AND org_id = $1
1232 AND timestamp >= $2 AND timestamp <= $3
1233 "#,
1234 )
1235 .bind(org)
1236 .bind(start_time)
1237 .bind(end_time)
1238 .fetch_one(self.pool())
1239 .await
1240 .unwrap_or(0)
1241 } else {
1242 0
1243 };
1244
1245 let voice_commands = if let Some(ws_id) = workspace_id {
1247 sqlx::query_scalar::<_, i64>(
1248 r#"
1249 SELECT COUNT(*)
1250 FROM pillar_usage_events
1251 WHERE pillar = 'ai'
1252 AND metric_name = 'voice_command'
1253 AND workspace_id = $1
1254 AND timestamp >= $2 AND timestamp <= $3
1255 "#,
1256 )
1257 .bind(ws_id)
1258 .bind(start_time)
1259 .bind(end_time)
1260 .fetch_one(self.pool())
1261 .await
1262 .unwrap_or(0)
1263 } else if let Some(org) = org_id {
1264 sqlx::query_scalar::<_, i64>(
1265 r#"
1266 SELECT COUNT(*)
1267 FROM pillar_usage_events
1268 WHERE pillar = 'ai'
1269 AND metric_name = 'voice_command'
1270 AND org_id = $1
1271 AND timestamp >= $2 AND timestamp <= $3
1272 "#,
1273 )
1274 .bind(org)
1275 .bind(start_time)
1276 .bind(end_time)
1277 .fetch_one(self.pool())
1278 .await
1279 .unwrap_or(0)
1280 } else {
1281 0
1282 };
1283
1284 let llm_assisted_operations = ai_generated_mocks + ai_contract_diffs + ai_refinements;
1286
1287 Ok(AiPillarMetrics {
1288 ai_generated_mocks: ai_generated_mocks as u64,
1289 ai_contract_diffs: ai_contract_diffs as u64,
1290 voice_commands: voice_commands as u64,
1291 llm_assisted_operations: llm_assisted_operations as u64,
1292 })
1293 }
1294}