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