Skip to main content

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