mockforge_analytics/
pillar_usage.rs

1//! Pillar usage tracking
2//!
3//! Tracks usage of `MockForge` pillars (Reality, Contracts, `DevX`, Cloud, AI)
4//! to help users understand platform adoption and identify under-utilized features.
5
6use crate::database::AnalyticsDatabase;
7use crate::error::Result;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12/// Pillar name
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
14#[serde(rename_all = "lowercase")]
15pub enum Pillar {
16    /// Reality pillar - everything that makes mocks feel like a real, evolving backend
17    Reality,
18    /// Contracts pillar - schema, drift, validation, and safety nets
19    Contracts,
20    /// `DevX` pillar - SDKs, generators, playgrounds, ergonomics
21    DevX,
22    /// Cloud pillar - registry, orgs, governance, monetization, marketplace
23    Cloud,
24    /// AI pillar - LLM/voice flows, AI diff/assist, generative behaviors
25    Ai,
26}
27
28impl Pillar {
29    /// Convert to string
30    #[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    /// Parse from string
42    #[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/// Pillar usage event
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct PillarUsageEvent {
64    /// Workspace ID (optional)
65    pub workspace_id: Option<String>,
66    /// Organization ID (optional)
67    pub org_id: Option<String>,
68    /// Pillar name
69    pub pillar: Pillar,
70    /// Metric name (e.g., "`blended_reality_ratio`", "`smart_personas_usage`", "`validation_mode`")
71    pub metric_name: String,
72    /// Metric value (JSON)
73    pub metric_value: Value,
74    /// Timestamp
75    pub timestamp: DateTime<Utc>,
76}
77
78/// Pillar usage metrics
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct PillarUsageMetrics {
81    /// Workspace ID
82    pub workspace_id: Option<String>,
83    /// Organization ID
84    pub org_id: Option<String>,
85    /// Time range
86    pub time_range: String,
87    /// Reality pillar metrics
88    pub reality: Option<RealityPillarMetrics>,
89    /// Contracts pillar metrics
90    pub contracts: Option<ContractsPillarMetrics>,
91    /// `DevX` pillar metrics
92    pub devx: Option<DevXPillarMetrics>,
93    /// Cloud pillar metrics
94    pub cloud: Option<CloudPillarMetrics>,
95    /// AI pillar metrics
96    pub ai: Option<AiPillarMetrics>,
97}
98
99/// Reality pillar metrics
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct RealityPillarMetrics {
102    /// Percentage of requests using blended reality (reality continuum)
103    pub blended_reality_percent: f64,
104    /// Percentage of scenarios using Smart Personas
105    pub smart_personas_percent: f64,
106    /// Percentage of scenarios using static fixtures
107    pub static_fixtures_percent: f64,
108    /// Average reality level (1-5)
109    pub avg_reality_level: f64,
110    /// Number of scenarios with chaos enabled
111    pub chaos_enabled_count: u64,
112    /// Total number of scenarios
113    pub total_scenarios: u64,
114}
115
116/// Contracts pillar metrics
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct ContractsPillarMetrics {
119    /// Percentage of requests with validation disabled
120    pub validation_disabled_percent: f64,
121    /// Percentage of requests with validation in warn mode
122    pub validation_warn_percent: f64,
123    /// Percentage of requests with validation in enforce mode
124    pub validation_enforce_percent: f64,
125    /// Number of endpoints with drift budgets configured
126    pub drift_budget_configured_count: u64,
127    /// Number of drift incidents
128    pub drift_incidents_count: u64,
129    /// Number of contract sync cycles
130    pub contract_sync_cycles: u64,
131}
132
133/// `DevX` pillar metrics
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct DevXPillarMetrics {
136    /// Number of SDK installations
137    pub sdk_installations: u64,
138    /// Number of client code generations
139    pub client_generations: u64,
140    /// Number of playground sessions
141    pub playground_sessions: u64,
142    /// Number of CLI commands executed
143    pub cli_commands: u64,
144}
145
146/// Cloud pillar metrics
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct CloudPillarMetrics {
149    /// Number of shared scenarios
150    pub shared_scenarios_count: u64,
151    /// Number of marketplace downloads
152    pub marketplace_downloads: u64,
153    /// Number of org templates used
154    pub org_templates_used: u64,
155    /// Number of collaborative workspaces
156    pub collaborative_workspaces: u64,
157}
158
159/// AI pillar metrics
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct AiPillarMetrics {
162    /// Number of AI-generated mocks
163    pub ai_generated_mocks: u64,
164    /// Number of AI contract diffs
165    pub ai_contract_diffs: u64,
166    /// Number of voice commands
167    pub voice_commands: u64,
168    /// Number of LLM-assisted operations
169    pub llm_assisted_operations: u64,
170}
171
172impl AnalyticsDatabase {
173    /// Record a pillar usage event
174    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    /// Get pillar usage metrics for a workspace
199    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        // Get reality pillar metrics
208        let reality = self
209            .get_reality_pillar_metrics(Some(workspace_id), None, start_time, end_time)
210            .await?;
211
212        // Get contracts pillar metrics
213        let contracts = self
214            .get_contracts_pillar_metrics(Some(workspace_id), None, start_time, end_time)
215            .await?;
216
217        // Get DevX pillar metrics
218        let devx = self
219            .get_devx_pillar_metrics(Some(workspace_id), None, start_time, end_time)
220            .await?;
221
222        // Get Cloud pillar metrics
223        let cloud = self
224            .get_cloud_pillar_metrics(Some(workspace_id), None, start_time, end_time)
225            .await?;
226
227        // Get AI pillar metrics
228        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    /// Get pillar usage metrics for an organization
245    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        // Get reality pillar metrics
254        let reality = self
255            .get_reality_pillar_metrics(None, Some(org_id), start_time, end_time)
256            .await?;
257
258        // Get contracts pillar metrics
259        let contracts = self
260            .get_contracts_pillar_metrics(None, Some(org_id), start_time, end_time)
261            .await?;
262
263        // Get DevX pillar metrics
264        let devx = self.get_devx_pillar_metrics(None, Some(org_id), start_time, end_time).await?;
265
266        // Get Cloud pillar metrics
267        let cloud = self.get_cloud_pillar_metrics(None, Some(org_id), start_time, end_time).await?;
268
269        // Get AI pillar metrics
270        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    /// Get reality pillar metrics
285    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        // Query blended reality usage
293        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        // Query Smart Personas vs static fixtures
331        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        // Query average reality level
416        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        // Query chaos enabled count
455        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    /// Get contracts pillar metrics
506    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        // Query validation mode usage
514        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        // Query drift budget configured count
643        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        // Query drift incidents count
682        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        // Query contract sync cycles
723        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    /// Get `DevX` pillar metrics
772    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        // Query SDK installations
780        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        // Query client generations
819        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        // Query playground sessions
858        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        // Query CLI commands
897        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    /// Get Cloud pillar metrics
944    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        // Query shared scenarios count
952        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        // Query marketplace downloads
991        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        // Query org templates used
1030        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        // Query collaborative workspaces
1069        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    /// Get AI pillar metrics
1118    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        // Query AI-generated mocks
1126        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        // Query AI contract diffs
1167        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        // Query AI refinements
1208        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        // Query voice commands
1247        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        // LLM-assisted operations includes all AI generations, diffs, and refinements
1286        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}
1296
1297#[cfg(test)]
1298mod tests {
1299    use super::*;
1300
1301    #[test]
1302    fn test_pillar_as_str() {
1303        assert_eq!(Pillar::Reality.as_str(), "reality");
1304        assert_eq!(Pillar::Contracts.as_str(), "contracts");
1305        assert_eq!(Pillar::DevX.as_str(), "devx");
1306        assert_eq!(Pillar::Cloud.as_str(), "cloud");
1307        assert_eq!(Pillar::Ai.as_str(), "ai");
1308    }
1309
1310    #[test]
1311    fn test_pillar_from_str() {
1312        assert_eq!(Pillar::from_str("reality"), Some(Pillar::Reality));
1313        assert_eq!(Pillar::from_str("contracts"), Some(Pillar::Contracts));
1314        assert_eq!(Pillar::from_str("devx"), Some(Pillar::DevX));
1315        assert_eq!(Pillar::from_str("cloud"), Some(Pillar::Cloud));
1316        assert_eq!(Pillar::from_str("ai"), Some(Pillar::Ai));
1317        assert_eq!(Pillar::from_str("unknown"), None);
1318    }
1319
1320    #[test]
1321    fn test_pillar_from_str_case_insensitive() {
1322        assert_eq!(Pillar::from_str("REALITY"), Some(Pillar::Reality));
1323        assert_eq!(Pillar::from_str("Reality"), Some(Pillar::Reality));
1324        assert_eq!(Pillar::from_str("DEVX"), Some(Pillar::DevX));
1325        assert_eq!(Pillar::from_str("AI"), Some(Pillar::Ai));
1326    }
1327
1328    #[test]
1329    fn test_pillar_display() {
1330        assert_eq!(format!("{}", Pillar::Reality), "reality");
1331        assert_eq!(format!("{}", Pillar::Contracts), "contracts");
1332        assert_eq!(format!("{}", Pillar::DevX), "devx");
1333        assert_eq!(format!("{}", Pillar::Cloud), "cloud");
1334        assert_eq!(format!("{}", Pillar::Ai), "ai");
1335    }
1336
1337    #[test]
1338    fn test_pillar_serialize() {
1339        let pillar = Pillar::Reality;
1340        let json = serde_json::to_string(&pillar).unwrap();
1341        assert_eq!(json, "\"reality\"");
1342    }
1343
1344    #[test]
1345    fn test_pillar_deserialize() {
1346        let pillar: Pillar = serde_json::from_str("\"contracts\"").unwrap();
1347        assert_eq!(pillar, Pillar::Contracts);
1348    }
1349
1350    #[test]
1351    fn test_pillar_clone() {
1352        let pillar = Pillar::DevX;
1353        let cloned = pillar;
1354        assert_eq!(pillar, cloned);
1355    }
1356
1357    #[test]
1358    fn test_pillar_hash() {
1359        use std::collections::HashSet;
1360        let mut set = HashSet::new();
1361        set.insert(Pillar::Reality);
1362        set.insert(Pillar::Contracts);
1363        set.insert(Pillar::Reality); // duplicate
1364        assert_eq!(set.len(), 2);
1365    }
1366
1367    #[test]
1368    fn test_pillar_usage_event_serialize() {
1369        let event = PillarUsageEvent {
1370            workspace_id: Some("ws-123".to_string()),
1371            org_id: None,
1372            pillar: Pillar::Reality,
1373            metric_name: "blended_reality_ratio".to_string(),
1374            metric_value: serde_json::json!({"ratio": 0.75}),
1375            timestamp: Utc::now(),
1376        };
1377        let json = serde_json::to_string(&event).unwrap();
1378        assert!(json.contains("ws-123"));
1379        assert!(json.contains("reality"));
1380        assert!(json.contains("blended_reality_ratio"));
1381    }
1382
1383    #[test]
1384    fn test_pillar_usage_event_deserialize() {
1385        let json = r#"{
1386            "workspace_id": "ws-456",
1387            "org_id": null,
1388            "pillar": "contracts",
1389            "metric_name": "validation_mode",
1390            "metric_value": {"mode": "enforce"},
1391            "timestamp": "2024-01-15T12:00:00Z"
1392        }"#;
1393        let event: PillarUsageEvent = serde_json::from_str(json).unwrap();
1394        assert_eq!(event.workspace_id, Some("ws-456".to_string()));
1395        assert_eq!(event.pillar, Pillar::Contracts);
1396        assert_eq!(event.metric_name, "validation_mode");
1397    }
1398
1399    #[test]
1400    fn test_reality_pillar_metrics_serialize() {
1401        let metrics = RealityPillarMetrics {
1402            blended_reality_percent: 75.0,
1403            smart_personas_percent: 60.0,
1404            static_fixtures_percent: 40.0,
1405            avg_reality_level: 3.5,
1406            chaos_enabled_count: 5,
1407            total_scenarios: 100,
1408        };
1409        let json = serde_json::to_string(&metrics).unwrap();
1410        assert!(json.contains("blended_reality_percent"));
1411        assert!(json.contains("75.0"));
1412    }
1413
1414    #[test]
1415    fn test_contracts_pillar_metrics_serialize() {
1416        let metrics = ContractsPillarMetrics {
1417            validation_disabled_percent: 10.0,
1418            validation_warn_percent: 30.0,
1419            validation_enforce_percent: 60.0,
1420            drift_budget_configured_count: 15,
1421            drift_incidents_count: 3,
1422            contract_sync_cycles: 42,
1423        };
1424        let json = serde_json::to_string(&metrics).unwrap();
1425        assert!(json.contains("validation_enforce_percent"));
1426        assert!(json.contains("60.0"));
1427    }
1428
1429    #[test]
1430    fn test_devx_pillar_metrics_serialize() {
1431        let metrics = DevXPillarMetrics {
1432            sdk_installations: 100,
1433            client_generations: 50,
1434            playground_sessions: 200,
1435            cli_commands: 1000,
1436        };
1437        let json = serde_json::to_string(&metrics).unwrap();
1438        assert!(json.contains("sdk_installations"));
1439        assert!(json.contains("100"));
1440    }
1441
1442    #[test]
1443    fn test_cloud_pillar_metrics_serialize() {
1444        let metrics = CloudPillarMetrics {
1445            shared_scenarios_count: 25,
1446            marketplace_downloads: 500,
1447            org_templates_used: 10,
1448            collaborative_workspaces: 5,
1449        };
1450        let json = serde_json::to_string(&metrics).unwrap();
1451        assert!(json.contains("marketplace_downloads"));
1452        assert!(json.contains("500"));
1453    }
1454
1455    #[test]
1456    fn test_ai_pillar_metrics_serialize() {
1457        let metrics = AiPillarMetrics {
1458            ai_generated_mocks: 100,
1459            ai_contract_diffs: 50,
1460            voice_commands: 25,
1461            llm_assisted_operations: 175,
1462        };
1463        let json = serde_json::to_string(&metrics).unwrap();
1464        assert!(json.contains("ai_generated_mocks"));
1465        assert!(json.contains("100"));
1466    }
1467
1468    #[test]
1469    fn test_pillar_usage_metrics_serialize() {
1470        let metrics = PillarUsageMetrics {
1471            workspace_id: Some("ws-123".to_string()),
1472            org_id: None,
1473            time_range: "3600s".to_string(),
1474            reality: Some(RealityPillarMetrics {
1475                blended_reality_percent: 50.0,
1476                smart_personas_percent: 75.0,
1477                static_fixtures_percent: 25.0,
1478                avg_reality_level: 4.0,
1479                chaos_enabled_count: 2,
1480                total_scenarios: 50,
1481            }),
1482            contracts: None,
1483            devx: None,
1484            cloud: None,
1485            ai: None,
1486        };
1487        let json = serde_json::to_string(&metrics).unwrap();
1488        assert!(json.contains("ws-123"));
1489        assert!(json.contains("3600s"));
1490        assert!(json.contains("blended_reality_percent"));
1491    }
1492
1493    #[test]
1494    fn test_pillar_usage_metrics_all_pillars() {
1495        let metrics = PillarUsageMetrics {
1496            workspace_id: Some("ws-all".to_string()),
1497            org_id: Some("org-1".to_string()),
1498            time_range: "86400s".to_string(),
1499            reality: Some(RealityPillarMetrics {
1500                blended_reality_percent: 80.0,
1501                smart_personas_percent: 90.0,
1502                static_fixtures_percent: 10.0,
1503                avg_reality_level: 4.5,
1504                chaos_enabled_count: 10,
1505                total_scenarios: 200,
1506            }),
1507            contracts: Some(ContractsPillarMetrics {
1508                validation_disabled_percent: 5.0,
1509                validation_warn_percent: 15.0,
1510                validation_enforce_percent: 80.0,
1511                drift_budget_configured_count: 30,
1512                drift_incidents_count: 2,
1513                contract_sync_cycles: 100,
1514            }),
1515            devx: Some(DevXPillarMetrics {
1516                sdk_installations: 500,
1517                client_generations: 200,
1518                playground_sessions: 1000,
1519                cli_commands: 5000,
1520            }),
1521            cloud: Some(CloudPillarMetrics {
1522                shared_scenarios_count: 50,
1523                marketplace_downloads: 1000,
1524                org_templates_used: 20,
1525                collaborative_workspaces: 15,
1526            }),
1527            ai: Some(AiPillarMetrics {
1528                ai_generated_mocks: 300,
1529                ai_contract_diffs: 100,
1530                voice_commands: 50,
1531                llm_assisted_operations: 450,
1532            }),
1533        };
1534        let json = serde_json::to_string(&metrics).unwrap();
1535        assert!(json.contains("org-1"));
1536        // Check all pillar sections are present
1537        assert!(json.contains("reality"));
1538        assert!(json.contains("contracts"));
1539        assert!(json.contains("devx"));
1540        assert!(json.contains("cloud"));
1541        assert!(json.contains("ai"));
1542    }
1543
1544    #[test]
1545    fn test_pillar_debug() {
1546        let pillar = Pillar::Reality;
1547        let debug = format!("{:?}", pillar);
1548        assert!(debug.contains("Reality"));
1549    }
1550
1551    #[test]
1552    fn test_reality_pillar_metrics_clone() {
1553        let metrics = RealityPillarMetrics {
1554            blended_reality_percent: 50.0,
1555            smart_personas_percent: 60.0,
1556            static_fixtures_percent: 40.0,
1557            avg_reality_level: 3.0,
1558            chaos_enabled_count: 1,
1559            total_scenarios: 10,
1560        };
1561        let cloned = metrics.clone();
1562        assert_eq!(metrics.blended_reality_percent, cloned.blended_reality_percent);
1563        assert_eq!(metrics.total_scenarios, cloned.total_scenarios);
1564    }
1565
1566    #[test]
1567    fn test_pillar_usage_event_clone() {
1568        let event = PillarUsageEvent {
1569            workspace_id: Some("ws-test".to_string()),
1570            org_id: None,
1571            pillar: Pillar::Ai,
1572            metric_name: "ai_generation".to_string(),
1573            metric_value: serde_json::json!({"type": "mock"}),
1574            timestamp: Utc::now(),
1575        };
1576        let cloned = event.clone();
1577        assert_eq!(event.workspace_id, cloned.workspace_id);
1578        assert_eq!(event.pillar, cloned.pillar);
1579        assert_eq!(event.metric_name, cloned.metric_name);
1580    }
1581}