Bug: [FEATURE] Authentication Analytics Enhancement for 3DS Intelligence Engine



### Feature Description

Add new query filters for refined  3DS analytics
Expose exemption data (exemption_requested, exemption_approved) in the response
Support Sankey-chart metrics by returning flow-ready data

### Possible Implementation

Changed auth analytics APIs.

### Have you spent some time checking if this feature request has been raised before?

- [x] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [x] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/crates/analytics/src/auth_events.rs b/crates/analytics/src/auth_events.rs
index 3aa23d0793d..2ce5d52f9b1 100644
--- a/crates/analytics/src/auth_events.rs
+++ b/crates/analytics/src/auth_events.rs
@@ -2,7 +2,8 @@ pub mod accumulator;
 mod core;
 pub mod filters;
 pub mod metrics;
+pub mod sankey;
 pub mod types;
 pub use accumulator::{AuthEventMetricAccumulator, AuthEventMetricsAccumulator};
 
-pub use self::core::{get_filters, get_metrics};
+pub use self::core::{get_filters, get_metrics, get_sankey};
diff --git a/crates/analytics/src/auth_events/accumulator.rs b/crates/analytics/src/auth_events/accumulator.rs
index 13818d2bd43..2ee5d242d63 100644
--- a/crates/analytics/src/auth_events/accumulator.rs
+++ b/crates/analytics/src/auth_events/accumulator.rs
@@ -14,6 +14,8 @@ pub struct AuthEventMetricsAccumulator {
     pub frictionless_flow_count: CountAccumulator,
     pub frictionless_success_count: CountAccumulator,
     pub authentication_funnel: CountAccumulator,
+    pub authentication_exemption_approved_count: CountAccumulator,
+    pub authentication_exemption_requested_count: CountAccumulator,
 }
 
 #[derive(Debug, Default)]
@@ -80,6 +82,12 @@ impl AuthEventMetricsAccumulator {
             frictionless_success_count: self.frictionless_success_count.collect(),
             error_message_count: self.authentication_error_message.collect(),
             authentication_funnel: self.authentication_funnel.collect(),
+            authentication_exemption_approved_count: self
+                .authentication_exemption_approved_count
+                .collect(),
+            authentication_exemption_requested_count: self
+                .authentication_exemption_requested_count
+                .collect(),
         }
     }
 }
diff --git a/crates/analytics/src/auth_events/core.rs b/crates/analytics/src/auth_events/core.rs
index 4d11f19d531..41b7b724954 100644
--- a/crates/analytics/src/auth_events/core.rs
+++ b/crates/analytics/src/auth_events/core.rs
@@ -8,15 +8,18 @@ use api_models::analytics::{
     AuthEventFilterValue, AuthEventFiltersResponse, AuthEventMetricsResponse,
     AuthEventsAnalyticsMetadata, GetAuthEventFilterRequest, GetAuthEventMetricRequest,
 };
+use common_utils::types::TimeRange;
 use error_stack::{report, ResultExt};
 use router_env::{instrument, tracing};
 
 use super::{
     filters::{get_auth_events_filter_for_dimension, AuthEventFilterRow},
+    sankey::{get_sankey_data, SankeyRow},
     AuthEventMetricsAccumulator,
 };
 use crate::{
     auth_events::AuthEventMetricAccumulator,
+    enums::AuthInfo,
     errors::{AnalyticsError, AnalyticsResult},
     AnalyticsProvider,
 };
@@ -92,6 +95,12 @@ pub async fn get_metrics(
                 AuthEventMetrics::AuthenticationFunnel => metrics_builder
                     .authentication_funnel
                     .add_metrics_bucket(&value),
+                AuthEventMetrics::AuthenticationExemptionApprovedCount => metrics_builder
+                    .authentication_exemption_approved_count
+                    .add_metrics_bucket(&value),
+                AuthEventMetrics::AuthenticationExemptionRequestedCount => metrics_builder
+                    .authentication_exemption_requested_count
+                    .add_metrics_bucket(&value),
             }
         }
     }
@@ -170,6 +179,27 @@ pub async fn get_filters(
             AuthEventDimensions::AuthenticationConnector => fil.authentication_connector.map(|i| i.as_ref().to_string()),
             AuthEventDimensions::MessageVersion => fil.message_version,
             AuthEventDimensions::AcsReferenceNumber => fil.acs_reference_number,
+            AuthEventDimensions::Platform => fil.platform,
+            AuthEventDimensions::Mcc => fil.mcc,
+           AuthEventDimensions::Currency => fil.currency.map(|i| i.as_ref().to_string()),
+            AuthEventDimensions::MerchantCountry => fil.merchant_country,
+            AuthEventDimensions::BillingCountry => fil.billing_country,
+            AuthEventDimensions::ShippingCountry => fil.shipping_country,
+            AuthEventDimensions::IssuerCountry => fil.issuer_country,
+            AuthEventDimensions::EarliestSupportedVersion => fil.earliest_supported_version,
+            AuthEventDimensions::LatestSupportedVersion => fil.latest_supported_version,
+            AuthEventDimensions::WhitelistDecision => fil.whitelist_decision.map(|i| i.to_string()),
+            AuthEventDimensions::DeviceManufacturer => fil.device_manufacturer,
+            AuthEventDimensions::DeviceType => fil.device_type,
+            AuthEventDimensions::DeviceBrand => fil.device_brand,
+            AuthEventDimensions::DeviceOs => fil.device_os,
+            AuthEventDimensions::DeviceDisplay => fil.device_display,
+            AuthEventDimensions::BrowserName => fil.browser_name,
+            AuthEventDimensions::BrowserVersion => fil.browser_version,
+            AuthEventDimensions::IssuerId => fil.issuer_id,
+            AuthEventDimensions::SchemeName => fil.scheme_name,
+            AuthEventDimensions::ExemptionRequested => fil.exemption_requested.map(|i| i.to_string()),
+            AuthEventDimensions::ExemptionAccepted => fil.exemption_accepted.map(|i| i.to_string()),
         })
         .collect::<Vec<String>>();
         res.query_data.push(AuthEventFilterValue {
@@ -179,3 +209,24 @@ pub async fn get_filters(
     }
     Ok(res)
 }
+
+#[instrument(skip_all)]
+pub async fn get_sankey(
+    pool: &AnalyticsProvider,
+    auth: &AuthInfo,
+    req: TimeRange,
+) -> AnalyticsResult<Vec<SankeyRow>> {
+    match pool {
+        AnalyticsProvider::Sqlx(_) => Err(AnalyticsError::NotImplemented(
+            "Sankey not implemented for sqlx",
+        ))?,
+        AnalyticsProvider::Clickhouse(ckh_pool)
+        | AnalyticsProvider::CombinedCkh(_, ckh_pool)
+        | AnalyticsProvider::CombinedSqlx(_, ckh_pool) => {
+            let sankey_rows = get_sankey_data(ckh_pool, auth, &req)
+                .await
+                .change_context(AnalyticsError::UnknownError)?;
+            Ok(sankey_rows)
+        }
+    }
+}
diff --git a/crates/analytics/src/auth_events/filters.rs b/crates/analytics/src/auth_events/filters.rs
index 1961116b84c..92496131727 100644
--- a/crates/analytics/src/auth_events/filters.rs
+++ b/crates/analytics/src/auth_events/filters.rs
@@ -1,5 +1,5 @@
 use api_models::analytics::{auth_events::AuthEventDimensions, Granularity, TimeRange};
-use common_enums::DecoupledAuthenticationType;
+use common_enums::{Currency, DecoupledAuthenticationType};
 use common_utils::errors::ReportSwitchExt;
 use diesel_models::enums::{AuthenticationConnectors, AuthenticationStatus, TransactionStatus};
 use error_stack::ResultExt;
@@ -60,4 +60,25 @@ pub struct AuthEventFilterRow {
     pub authentication_connector: Option<DBEnumWrapper<AuthenticationConnectors>>,
     pub message_version: Option<String>,
     pub acs_reference_number: Option<String>,
+    pub platform: Option<String>,
+    pub mcc: Option<String>,
+    pub currency: Option<DBEnumWrapper<Currency>>,
+    pub merchant_country: Option<String>,
+    pub billing_country: Option<String>,
+    pub shipping_country: Option<String>,
+    pub issuer_country: Option<String>,
+    pub earliest_supported_version: Option<String>,
+    pub latest_supported_version: Option<String>,
+    pub whitelist_decision: Option<bool>,
+    pub device_manufacturer: Option<String>,
+    pub device_type: Option<String>,
+    pub device_brand: Option<String>,
+    pub device_os: Option<String>,
+    pub device_display: Option<String>,
+    pub browser_name: Option<String>,
+    pub browser_version: Option<String>,
+    pub issuer_id: Option<String>,
+    pub scheme_name: Option<String>,
+    pub exemption_requested: Option<bool>,
+    pub exemption_accepted: Option<bool>,
 }
diff --git a/crates/analytics/src/auth_events/metrics.rs b/crates/analytics/src/auth_events/metrics.rs
index 6315c1338ee..46c3805662a 100644
--- a/crates/analytics/src/auth_events/metrics.rs
+++ b/crates/analytics/src/auth_events/metrics.rs
@@ -17,6 +17,8 @@ use crate::{
 mod authentication_attempt_count;
 mod authentication_count;
 mod authentication_error_message;
+mod authentication_exemption_approved_count;
+mod authentication_exemption_requested_count;
 mod authentication_funnel;
 mod authentication_success_count;
 mod challenge_attempt_count;
@@ -28,6 +30,8 @@ mod frictionless_success_count;
 use authentication_attempt_count::AuthenticationAttemptCount;
 use authentication_count::AuthenticationCount;
 use authentication_error_message::AuthenticationErrorMessage;
+use authentication_exemption_approved_count::AuthenticationExemptionApprovedCount;
+use authentication_exemption_requested_count::AuthenticationExemptionRequestedCount;
 use authentication_funnel::AuthenticationFunnel;
 use authentication_success_count::AuthenticationSuccessCount;
 use challenge_attempt_count::ChallengeAttemptCount;
@@ -46,6 +50,27 @@ pub struct AuthEventMetricRow {
     pub authentication_connector: Option<DBEnumWrapper<storage_enums::AuthenticationConnectors>>,
     pub message_version: Option<String>,
     pub acs_reference_number: Option<String>,
+    pub platform: Option<String>,
+    pub mcc: Option<String>,
+    pub currency: Option<DBEnumWrapper<storage_enums::Currency>>,
+    pub merchant_country: Option<String>,
+    pub billing_country: Option<String>,
+    pub shipping_country: Option<String>,
+    pub issuer_country: Option<String>,
+    pub earliest_supported_version: Option<String>,
+    pub latest_supported_version: Option<String>,
+    pub whitelist_decision: Option<bool>,
+    pub device_manufacturer: Option<String>,
+    pub device_type: Option<String>,
+    pub device_brand: Option<String>,
+    pub device_os: Option<String>,
+    pub device_display: Option<String>,
+    pub browser_name: Option<String>,
+    pub browser_version: Option<String>,
+    pub issuer_id: Option<String>,
+    pub scheme_name: Option<String>,
+    pub exemption_requested: Option<bool>,
+    pub exemption_accepted: Option<bool>,
     #[serde(with = "common_utils::custom_serde::iso8601::option")]
     pub start_bucket: Option<PrimitiveDateTime>,
     #[serde(with = "common_utils::custom_serde::iso8601::option")]
@@ -210,6 +235,30 @@ where
                     )
                     .await
             }
+            Self::AuthenticationExemptionApprovedCount => {
+                AuthenticationExemptionApprovedCount
+                    .load_metrics(
+                        merchant_id,
+                        dimensions,
+                        filters,
+                        granularity,
+                        time_range,
+                        pool,
+                    )
+                    .await
+            }
+            Self::AuthenticationExemptionRequestedCount => {
+                AuthenticationExemptionRequestedCount
+                    .load_metrics(
+                        merchant_id,
+                        dimensions,
+                        filters,
+                        granularity,
+                        time_range,
+                        pool,
+                    )
+                    .await
+            }
         }
     }
 }
diff --git a/crates/analytics/src/auth_events/metrics/authentication_attempt_count.rs b/crates/analytics/src/auth_events/metrics/authentication_attempt_count.rs
index fbdc4b75b3e..967513af32c 100644
--- a/crates/analytics/src/auth_events/metrics/authentication_attempt_count.rs
+++ b/crates/analytics/src/auth_events/metrics/authentication_attempt_count.rs
@@ -111,6 +111,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/metrics/authentication_count.rs b/crates/analytics/src/auth_events/metrics/authentication_count.rs
index b4c0a5c5957..0334f732f23 100644
--- a/crates/analytics/src/auth_events/metrics/authentication_count.rs
+++ b/crates/analytics/src/auth_events/metrics/authentication_count.rs
@@ -101,6 +101,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/metrics/authentication_error_message.rs b/crates/analytics/src/auth_events/metrics/authentication_error_message.rs
index b064cb7ab30..ac0adc7bbff 100644
--- a/crates/analytics/src/auth_events/metrics/authentication_error_message.rs
+++ b/crates/analytics/src/auth_events/metrics/authentication_error_message.rs
@@ -120,6 +120,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/metrics/authentication_exemption_approved_count.rs b/crates/analytics/src/auth_events/metrics/authentication_exemption_approved_count.rs
new file mode 100644
index 00000000000..89ec3affe6b
--- /dev/null
+++ b/crates/analytics/src/auth_events/metrics/authentication_exemption_approved_count.rs
@@ -0,0 +1,148 @@
+use std::collections::HashSet;
+
+use api_models::analytics::{
+    auth_events::{AuthEventDimensions, AuthEventFilters, AuthEventMetricsBucketIdentifier},
+    Granularity, TimeRange,
+};
+use common_utils::errors::ReportSwitchExt;
+use error_stack::ResultExt;
+use time::PrimitiveDateTime;
+
+use super::AuthEventMetricRow;
+use crate::{
+    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
+    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
+};
+
+#[derive(Default)]
+pub(super) struct AuthenticationExemptionApprovedCount;
+
+#[async_trait::async_trait]
+impl<T> super::AuthEventMetric<T> for AuthenticationExemptionApprovedCount
+where
+    T: AnalyticsDataSource + super::AuthEventMetricAnalytics,
+    PrimitiveDateTime: ToSql<T>,
+    AnalyticsCollection: ToSql<T>,
+    Granularity: GroupByClause<T>,
+    Aggregate<&'static str>: ToSql<T>,
+    Window<&'static str>: ToSql<T>,
+{
+    async fn load_metrics(
+        &self,
+        merchant_id: &common_utils::id_type::MerchantId,
+        dimensions: &[AuthEventDimensions],
+        filters: &AuthEventFilters,
+        granularity: Option<Granularity>,
+        time_range: &TimeRange,
+        pool: &T,
+    ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
+        let mut query_builder: QueryBuilder<T> =
+            QueryBuilder::new(AnalyticsCollection::Authentications);
+        for dim in dimensions.iter() {
+            query_builder.add_select_column(dim).switch()?;
+        }
+        query_builder
+            .add_select_column(Aggregate::Count {
+                field: None,
+                alias: Some("count"),
+            })
+            .switch()?;
+
+        query_builder
+            .add_select_column(Aggregate::Min {
+                field: "created_at",
+                alias: Some("start_bucket"),
+            })
+            .switch()?;
+
+        query_builder
+            .add_select_column(Aggregate::Max {
+                field: "created_at",
+                alias: Some("end_bucket"),
+            })
+            .switch()?;
+
+        query_builder
+            .add_filter_clause("merchant_id", merchant_id)
+            .switch()?;
+
+        query_builder
+            .add_filter_clause(AuthEventDimensions::ExemptionAccepted, true)
+            .switch()?;
+        filters.set_filter_clause(&mut query_builder).switch()?;
+        time_range
+            .set_filter_clause(&mut query_builder)
+            .attach_printable("Error filtering time range")
+            .switch()?;
+
+        for dim in dimensions.iter() {
+            query_builder
+                .add_group_by_clause(dim)
+                .attach_printable("Error grouping by dimensions")
+                .switch()?;
+        }
+
+        if let Some(granularity) = granularity {
+            granularity
+                .set_group_by_clause(&mut query_builder)
+                .attach_printable("Error adding granularity")
+                .switch()?;
+        }
+
+        query_builder
+            .execute_query::<AuthEventMetricRow, _>(pool)
+            .await
+            .change_context(MetricsError::QueryBuildingError)?
+            .change_context(MetricsError::QueryExecutionFailure)?
+            .into_iter()
+            .map(|i| {
+                Ok((
+                    AuthEventMetricsBucketIdentifier::new(
+                        i.authentication_status.as_ref().map(|i| i.0),
+                        i.trans_status.as_ref().map(|i| i.0.clone()),
+                        i.authentication_type.as_ref().map(|i| i.0),
+                        i.error_message.clone(),
+                        i.authentication_connector.as_ref().map(|i| i.0),
+                        i.message_version.clone(),
+                        i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
+                        TimeRange {
+                            start_time: match (granularity, i.start_bucket) {
+                                (Some(g), Some(st)) => g.clip_to_start(st)?,
+                                _ => time_range.start_time,
+                            },
+                            end_time: granularity.as_ref().map_or_else(
+                                || Ok(time_range.end_time),
+                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
+                            )?,
+                        },
+                    ),
+                    i,
+                ))
+            })
+            .collect::<error_stack::Result<
+                HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>,
+                crate::query::PostProcessingError,
+            >>()
+            .change_context(MetricsError::PostProcessingFailure)
+    }
+}
diff --git a/crates/analytics/src/auth_events/metrics/authentication_exemption_requested_count.rs b/crates/analytics/src/auth_events/metrics/authentication_exemption_requested_count.rs
new file mode 100644
index 00000000000..aa4af1a47ff
--- /dev/null
+++ b/crates/analytics/src/auth_events/metrics/authentication_exemption_requested_count.rs
@@ -0,0 +1,149 @@
+use std::collections::HashSet;
+
+use api_models::analytics::{
+    auth_events::{AuthEventDimensions, AuthEventFilters, AuthEventMetricsBucketIdentifier},
+    Granularity, TimeRange,
+};
+use common_utils::errors::ReportSwitchExt;
+use error_stack::ResultExt;
+use time::PrimitiveDateTime;
+
+use super::AuthEventMetricRow;
+use crate::{
+    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
+    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
+};
+
+#[derive(Default)]
+pub(super) struct AuthenticationExemptionRequestedCount;
+
+#[async_trait::async_trait]
+impl<T> super::AuthEventMetric<T> for AuthenticationExemptionRequestedCount
+where
+    T: AnalyticsDataSource + super::AuthEventMetricAnalytics,
+    PrimitiveDateTime: ToSql<T>,
+    AnalyticsCollection: ToSql<T>,
+    Granularity: GroupByClause<T>,
+    Aggregate<&'static str>: ToSql<T>,
+    Window<&'static str>: ToSql<T>,
+{
+    async fn load_metrics(
+        &self,
+        merchant_id: &common_utils::id_type::MerchantId,
+        dimensions: &[AuthEventDimensions],
+        filters: &AuthEventFilters,
+        granularity: Option<Granularity>,
+        time_range: &TimeRange,
+        pool: &T,
+    ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
+        let mut query_builder: QueryBuilder<T> =
+            QueryBuilder::new(AnalyticsCollection::Authentications);
+        for dim in dimensions.iter() {
+            query_builder.add_select_column(dim).switch()?;
+        }
+        query_builder
+            .add_select_column(Aggregate::Count {
+                field: None,
+                alias: Some("count"),
+            })
+            .switch()?;
+
+        query_builder
+            .add_select_column(Aggregate::Min {
+                field: "created_at",
+                alias: Some("start_bucket"),
+            })
+            .switch()?;
+
+        query_builder
+            .add_select_column(Aggregate::Max {
+                field: "created_at",
+                alias: Some("end_bucket"),
+            })
+            .switch()?;
+
+        query_builder
+            .add_filter_clause("merchant_id", merchant_id)
+            .switch()?;
+
+        query_builder
+            .add_filter_clause(AuthEventDimensions::ExemptionRequested, true)
+            .switch()?;
+
+        filters.set_filter_clause(&mut query_builder).switch()?;
+        time_range
+            .set_filter_clause(&mut query_builder)
+            .attach_printable("Error filtering time range")
+            .switch()?;
+
+        for dim in dimensions.iter() {
+            query_builder
+                .add_group_by_clause(dim)
+                .attach_printable("Error grouping by dimensions")
+                .switch()?;
+        }
+
+        if let Some(granularity) = granularity {
+            granularity
+                .set_group_by_clause(&mut query_builder)
+                .attach_printable("Error adding granularity")
+                .switch()?;
+        }
+
+        query_builder
+            .execute_query::<AuthEventMetricRow, _>(pool)
+            .await
+            .change_context(MetricsError::QueryBuildingError)?
+            .change_context(MetricsError::QueryExecutionFailure)?
+            .into_iter()
+            .map(|i| {
+                Ok((
+                    AuthEventMetricsBucketIdentifier::new(
+                        i.authentication_status.as_ref().map(|i| i.0),
+                        i.trans_status.as_ref().map(|i| i.0.clone()),
+                        i.authentication_type.as_ref().map(|i| i.0),
+                        i.error_message.clone(),
+                        i.authentication_connector.as_ref().map(|i| i.0),
+                        i.message_version.clone(),
+                        i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
+                        TimeRange {
+                            start_time: match (granularity, i.start_bucket) {
+                                (Some(g), Some(st)) => g.clip_to_start(st)?,
+                                _ => time_range.start_time,
+                            },
+                            end_time: granularity.as_ref().map_or_else(
+                                || Ok(time_range.end_time),
+                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
+                            )?,
+                        },
+                    ),
+                    i,
+                ))
+            })
+            .collect::<error_stack::Result<
+                HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>,
+                crate::query::PostProcessingError,
+            >>()
+            .change_context(MetricsError::PostProcessingFailure)
+    }
+}
diff --git a/crates/analytics/src/auth_events/metrics/authentication_funnel.rs b/crates/analytics/src/auth_events/metrics/authentication_funnel.rs
index 3a122fd42a9..145a70a542a 100644
--- a/crates/analytics/src/auth_events/metrics/authentication_funnel.rs
+++ b/crates/analytics/src/auth_events/metrics/authentication_funnel.rs
@@ -112,6 +112,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/metrics/authentication_success_count.rs b/crates/analytics/src/auth_events/metrics/authentication_success_count.rs
index 37435123623..9788b6477c1 100644
--- a/crates/analytics/src/auth_events/metrics/authentication_success_count.rs
+++ b/crates/analytics/src/auth_events/metrics/authentication_success_count.rs
@@ -106,6 +106,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs b/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs
index 0265fc7e457..d3d7dcc7471 100644
--- a/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs
+++ b/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs
@@ -116,6 +116,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/metrics/challenge_flow_count.rs b/crates/analytics/src/auth_events/metrics/challenge_flow_count.rs
index 178dcba20bf..5b951e1fa85 100644
--- a/crates/analytics/src/auth_events/metrics/challenge_flow_count.rs
+++ b/crates/analytics/src/auth_events/metrics/challenge_flow_count.rs
@@ -108,6 +108,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/metrics/challenge_success_count.rs b/crates/analytics/src/auth_events/metrics/challenge_success_count.rs
index b78f84326de..2bbd4d81982 100644
--- a/crates/analytics/src/auth_events/metrics/challenge_success_count.rs
+++ b/crates/analytics/src/auth_events/metrics/challenge_success_count.rs
@@ -113,6 +113,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/metrics/frictionless_flow_count.rs b/crates/analytics/src/auth_events/metrics/frictionless_flow_count.rs
index d114e36f140..da8a119d226 100644
--- a/crates/analytics/src/auth_events/metrics/frictionless_flow_count.rs
+++ b/crates/analytics/src/auth_events/metrics/frictionless_flow_count.rs
@@ -109,6 +109,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs b/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs
index a56d5b660aa..b4fb18e48f6 100644
--- a/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs
+++ b/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs
@@ -113,6 +113,26 @@ where
                         i.authentication_connector.as_ref().map(|i| i.0),
                         i.message_version.clone(),
                         i.acs_reference_number.clone(),
+                        i.mcc.clone(),
+                        i.currency.as_ref().map(|i| i.0),
+                        i.merchant_country.clone(),
+                        i.billing_country.clone(),
+                        i.shipping_country.clone(),
+                        i.issuer_country.clone(),
+                        i.earliest_supported_version.clone(),
+                        i.latest_supported_version.clone(),
+                        i.whitelist_decision,
+                        i.device_manufacturer.clone(),
+                        i.device_type.clone(),
+                        i.device_brand.clone(),
+                        i.device_os.clone(),
+                        i.device_display.clone(),
+                        i.browser_name.clone(),
+                        i.browser_version.clone(),
+                        i.issuer_id.clone(),
+                        i.scheme_name.clone(),
+                        i.exemption_requested,
+                        i.exemption_accepted,
                         TimeRange {
                             start_time: match (granularity, i.start_bucket) {
                                 (Some(g), Some(st)) => g.clip_to_start(st)?,
diff --git a/crates/analytics/src/auth_events/sankey.rs b/crates/analytics/src/auth_events/sankey.rs
new file mode 100644
index 00000000000..ede90ad9a81
--- /dev/null
+++ b/crates/analytics/src/auth_events/sankey.rs
@@ -0,0 +1,88 @@
+use common_enums::AuthenticationStatus;
+use common_utils::{
+    errors::ParsingError,
+    types::{authentication::AuthInfo, TimeRange},
+};
+use error_stack::ResultExt;
+use router_env::logger;
+
+use crate::{
+    clickhouse::ClickhouseClient,
+    query::{Aggregate, QueryBuilder, QueryFilter},
+    types::{AnalyticsCollection, MetricsError, MetricsResult},
+};
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct SankeyRow {
+    pub count: i64,
+    pub authentication_status: Option<AuthenticationStatus>,
+    pub exemption_requested: Option<bool>,
+    pub exemption_accepted: Option<bool>,
+}
+
+impl TryInto<SankeyRow> for serde_json::Value {
+    type Error = error_stack::Report<ParsingError>;
+
+    fn try_into(self) -> Result<SankeyRow, Self::Error> {
+        logger::debug!("Parsing SankeyRow from {:?}", self);
+        serde_json::from_value(self).change_context(ParsingError::StructParseFailure(
+            "Failed to parse Sankey in clickhouse results",
+        ))
+    }
+}
+
+pub async fn get_sankey_data(
+    clickhouse_client: &ClickhouseClient,
+    auth: &AuthInfo,
+    time_range: &TimeRange,
+) -> MetricsResult<Vec<SankeyRow>> {
+    let mut query_builder =
+        QueryBuilder::<ClickhouseClient>::new(AnalyticsCollection::Authentications);
+
+    query_builder
+        .add_select_column(Aggregate::<String>::Count {
+            field: None,
+            alias: Some("count"),
+        })
+        .change_context(MetricsError::QueryBuildingError)?;
+
+    query_builder
+        .add_select_column("exemption_requested")
+        .change_context(MetricsError::QueryBuildingError)?;
+
+    query_builder
+        .add_select_column("exemption_accepted")
+        .change_context(MetricsError::QueryBuildingError)?;
+
+    query_builder
+        .add_select_column("authentication_status")
+        .change_context(MetricsError::QueryBuildingError)?;
+
+    auth.set_filter_clause(&mut query_builder)
+        .change_context(MetricsError::QueryBuildingError)?;
+
+    time_range
+        .set_filter_clause(&mut query_builder)
+        .change_context(MetricsError::QueryBuildingError)?;
+
+    query_builder
+        .add_group_by_clause("exemption_requested")
+        .change_context(MetricsError::QueryBuildingError)?;
+
+    query_builder
+        .add_group_by_clause("exemption_accepted")
+        .change_context(MetricsError::QueryBuildingError)?;
+
+    query_builder
+        .add_group_by_clause("authentication_status")
+        .change_context(MetricsError::QueryBuildingError)?;
+
+    query_builder
+        .execute_query::<SankeyRow, _>(clickhouse_client)
+        .await
+        .change_context(MetricsError::QueryBuildingError)?
+        .change_context(MetricsError::QueryExecutionFailure)?
+        .into_iter()
+        .map(Ok)
+        .collect()
+}
diff --git a/crates/analytics/src/auth_events/types.rs b/crates/analytics/src/auth_events/types.rs
index 863e50a0af2..4aaa2622676 100644
--- a/crates/analytics/src/auth_events/types.rs
+++ b/crates/analytics/src/auth_events/types.rs
@@ -54,6 +54,12 @@ where
                 .attach_printable("Error adding message version filter")?;
         }
 
+        if !self.platform.is_empty() {
+            builder
+                .add_filter_in_range_clause(AuthEventDimensions::Platform, &self.platform)
+                .attach_printable("Error adding platform filter")?;
+        }
+
         if !self.acs_reference_number.is_empty() {
             builder
                 .add_filter_in_range_clause(
@@ -62,6 +68,144 @@ where
                 )
                 .attach_printable("Error adding acs reference number filter")?;
         }
+
+        if !self.mcc.is_empty() {
+            builder
+                .add_filter_in_range_clause(AuthEventDimensions::Mcc, &self.mcc)
+                .attach_printable("Failed to add MCC filter")?;
+        }
+        if !self.currency.is_empty() {
+            builder
+                .add_filter_in_range_clause(AuthEventDimensions::Currency, &self.currency)
+                .attach_printable("Failed to add currency filter")?;
+        }
+        if !self.merchant_country.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::MerchantCountry,
+                    &self.merchant_country,
+                )
+                .attach_printable("Failed to add merchant country filter")?;
+        }
+        if !self.billing_country.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::BillingCountry,
+                    &self.billing_country,
+                )
+                .attach_printable("Failed to add billing country filter")?;
+        }
+        if !self.shipping_country.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::ShippingCountry,
+                    &self.shipping_country,
+                )
+                .attach_printable("Failed to add shipping country filter")?;
+        }
+        if !self.issuer_country.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::IssuerCountry,
+                    &self.issuer_country,
+                )
+                .attach_printable("Failed to add issuer country filter")?;
+        }
+        if !self.earliest_supported_version.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::EarliestSupportedVersion,
+                    &self.earliest_supported_version,
+                )
+                .attach_printable("Failed to add earliest supported version filter")?;
+        }
+        if !self.latest_supported_version.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::LatestSupportedVersion,
+                    &self.latest_supported_version,
+                )
+                .attach_printable("Failed to add latest supported version filter")?;
+        }
+        if !self.whitelist_decision.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::WhitelistDecision,
+                    &self.whitelist_decision,
+                )
+                .attach_printable("Failed to add whitelist decision filter")?;
+        }
+        if !self.device_manufacturer.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::DeviceManufacturer,
+                    &self.device_manufacturer,
+                )
+                .attach_printable("Failed to add device manufacturer filter")?;
+        }
+        if !self.device_type.is_empty() {
+            builder
+                .add_filter_in_range_clause(AuthEventDimensions::DeviceType, &self.device_type)
+                .attach_printable("Failed to add device type filter")?;
+        }
+        if !self.device_brand.is_empty() {
+            builder
+                .add_filter_in_range_clause(AuthEventDimensions::DeviceBrand, &self.device_brand)
+                .attach_printable("Failed to add device brand filter")?;
+        }
+        if !self.device_os.is_empty() {
+            builder
+                .add_filter_in_range_clause(AuthEventDimensions::DeviceOs, &self.device_os)
+                .attach_printable("Failed to add device OS filter")?;
+        }
+        if !self.device_display.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::DeviceDisplay,
+                    &self.device_display,
+                )
+                .attach_printable("Failed to add device display filter")?;
+        }
+        if !self.browser_name.is_empty() {
+            builder
+                .add_filter_in_range_clause(AuthEventDimensions::BrowserName, &self.browser_name)
+                .attach_printable("Failed to add browser name filter")?;
+        }
+        if !self.browser_version.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::BrowserVersion,
+                    &self.browser_version,
+                )
+                .attach_printable("Failed to add browser version filter")?;
+        }
+        if !self.issuer_id.is_empty() {
+            builder
+                .add_filter_in_range_clause(AuthEventDimensions::IssuerId, &self.issuer_id)
+                .attach_printable("Failed to add issuer ID filter")?;
+        }
+        if !self.scheme_name.is_empty() {
+            builder
+                .add_filter_in_range_clause(AuthEventDimensions::SchemeName, &self.scheme_name)
+                .attach_printable("Failed to add scheme name filter")?;
+        }
+        if !self.exemption_requested.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::ExemptionRequested,
+                    &self.exemption_requested,
+                )
+                .attach_printable("Failed to add exemption requested filter")?;
+        }
+        if !self.exemption_accepted.is_empty() {
+            builder
+                .add_filter_in_range_clause(
+                    AuthEventDimensions::ExemptionAccepted,
+                    &self.exemption_accepted,
+                )
+                .attach_printable("Failed to add exemption accepted filter")?;
+        }
+
         Ok(())
     }
 }
diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs
index 5a2f097bb0d..7934c911848 100644
--- a/crates/analytics/src/sqlx.rs
+++ b/crates/analytics/src/sqlx.rs
@@ -231,6 +231,11 @@ impl<'a> FromRow<'a, PgRow> for super::auth_events::metrics::AuthEventMetricRow
                 ColumnNotFound(_) => Ok(Default::default()),
                 e => Err(e),
             })?;
+
+        let platform: Option<String> = row.try_get("platform").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
         let acs_reference_number: Option<String> =
             row.try_get("acs_reference_number").or_else(|e| match e {
                 ColumnNotFound(_) => Ok(Default::default()),
@@ -247,6 +252,102 @@ impl<'a> FromRow<'a, PgRow> for super::auth_events::metrics::AuthEventMetricRow
         let end_bucket: Option<PrimitiveDateTime> = row
             .try_get::<Option<PrimitiveDateTime>, _>("end_bucket")?
             .and_then(|dt| dt.replace_millisecond(0).ok());
+        let mcc: Option<String> = row.try_get("mcc").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let currency: Option<DBEnumWrapper<Currency>> =
+            row.try_get("currency").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let merchant_country: Option<String> =
+            row.try_get("merchant_country").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let billing_country: Option<String> =
+            row.try_get("billing_country").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let shipping_country: Option<String> =
+            row.try_get("shipping_country").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let issuer_country: Option<String> =
+            row.try_get("issuer_country").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let earliest_supported_version: Option<String> = row
+            .try_get("earliest_supported_version")
+            .or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let latest_supported_version: Option<String> = row
+            .try_get("latest_supported_version")
+            .or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let whitelist_decision: Option<bool> =
+            row.try_get("whitelist_decision").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let device_manufacturer: Option<String> =
+            row.try_get("device_manufacturer").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let device_type: Option<String> = row.try_get("device_type").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let device_brand: Option<String> = row.try_get("device_brand").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let device_os: Option<String> = row.try_get("device_os").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let device_display: Option<String> =
+            row.try_get("device_display").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let browser_name: Option<String> = row.try_get("browser_name").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let browser_version: Option<String> =
+            row.try_get("browser_version").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let issuer_id: Option<String> = row.try_get("issuer_id").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let scheme_name: Option<String> = row.try_get("scheme_name").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let exemption_requested: Option<bool> =
+            row.try_get("exemption_requested").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let exemption_accepted: Option<bool> =
+            row.try_get("exemption_accepted").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+
         Ok(Self {
             authentication_status,
             trans_status,
@@ -255,9 +356,30 @@ impl<'a> FromRow<'a, PgRow> for super::auth_events::metrics::AuthEventMetricRow
             authentication_connector,
             message_version,
             acs_reference_number,
+            platform,
             count,
             start_bucket,
             end_bucket,
+            mcc,
+            currency,
+            merchant_country,
+            billing_country,
+            shipping_country,
+            issuer_country,
+            earliest_supported_version,
+            latest_supported_version,
+            whitelist_decision,
+            device_manufacturer,
+            device_type,
+            device_brand,
+            device_os,
+            device_display,
+            browser_name,
+            browser_version,
+            issuer_id,
+            scheme_name,
+            exemption_requested,
+            exemption_accepted,
         })
     }
 }
@@ -299,6 +421,106 @@ impl<'a> FromRow<'a, PgRow> for super::auth_events::filters::AuthEventFilterRow
                 ColumnNotFound(_) => Ok(Default::default()),
                 e => Err(e),
             })?;
+        let platform: Option<String> = row.try_get("platform").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let mcc: Option<String> = row.try_get("mcc").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let currency: Option<DBEnumWrapper<Currency>> =
+            row.try_get("currency").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let merchant_country: Option<String> =
+            row.try_get("merchant_country").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let billing_country: Option<String> =
+            row.try_get("billing_country").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let shipping_country: Option<String> =
+            row.try_get("shipping_country").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let issuer_country: Option<String> =
+            row.try_get("issuer_country").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let earliest_supported_version: Option<String> = row
+            .try_get("earliest_supported_version")
+            .or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let latest_supported_version: Option<String> = row
+            .try_get("latest_supported_version")
+            .or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let whitelist_decision: Option<bool> =
+            row.try_get("whitelist_decision").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let device_manufacturer: Option<String> =
+            row.try_get("device_manufacturer").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let device_type: Option<String> = row.try_get("device_type").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let device_brand: Option<String> = row.try_get("device_brand").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let device_os: Option<String> = row.try_get("device_os").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let device_display: Option<String> =
+            row.try_get("device_display").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let browser_name: Option<String> = row.try_get("browser_name").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let browser_version: Option<String> =
+            row.try_get("browser_version").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let issuer_id: Option<String> = row.try_get("issuer_id").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let scheme_name: Option<String> = row.try_get("scheme_name").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
+        let exemption_requested: Option<bool> =
+            row.try_get("exemption_requested").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let exemption_accepted: Option<bool> =
+            row.try_get("exemption_accepted").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+
         Ok(Self {
             authentication_status,
             trans_status,
@@ -306,7 +528,28 @@ impl<'a> FromRow<'a, PgRow> for super::auth_events::filters::AuthEventFilterRow
             error_message,
             authentication_connector,
             message_version,
+            platform,
             acs_reference_number,
+            mcc,
+            currency,
+            merchant_country,
+            billing_country,
+            shipping_country,
+            issuer_country,
+            earliest_supported_version,
+            latest_supported_version,
+            whitelist_decision,
+            device_manufacturer,
+            device_type,
+            device_brand,
+            device_os,
+            device_display,
+            browser_name,
+            browser_version,
+            issuer_id,
+            scheme_name,
+            exemption_requested,
+            exemption_accepted,
         })
     }
 }
diff --git a/crates/analytics/src/utils.rs b/crates/analytics/src/utils.rs
index 0832788e261..06cc998f3da 100644
--- a/crates/analytics/src/utils.rs
+++ b/crates/analytics/src/utils.rs
@@ -52,6 +52,27 @@ pub fn get_auth_event_dimensions() -> Vec<NameDescription> {
         AuthEventDimensions::AuthenticationConnector,
         AuthEventDimensions::MessageVersion,
         AuthEventDimensions::AcsReferenceNumber,
+        AuthEventDimensions::Platform,
+        AuthEventDimensions::Mcc,
+        AuthEventDimensions::Currency,
+        AuthEventDimensions::MerchantCountry,
+        AuthEventDimensions::BillingCountry,
+        AuthEventDimensions::ShippingCountry,
+        AuthEventDimensions::IssuerCountry,
+        AuthEventDimensions::IssuerId,
+        AuthEventDimensions::EarliestSupportedVersion,
+        AuthEventDimensions::LatestSupportedVersion,
+        AuthEventDimensions::WhitelistDecision,
+        AuthEventDimensions::DeviceManufacturer,
+        AuthEventDimensions::DeviceType,
+        AuthEventDimensions::DeviceBrand,
+        AuthEventDimensions::DeviceOs,
+        AuthEventDimensions::DeviceDisplay,
+        AuthEventDimensions::BrowserName,
+        AuthEventDimensions::BrowserVersion,
+        AuthEventDimensions::SchemeName,
+        AuthEventDimensions::ExemptionRequested,
+        AuthEventDimensions::ExemptionAccepted,
     ]
     .into_iter()
     .map(Into::into)
diff --git a/crates/api_models/src/analytics/auth_events.rs b/crates/api_models/src/analytics/auth_events.rs
index 8a1c168aa2f..f1062e0269e 100644
--- a/crates/api_models/src/analytics/auth_events.rs
+++ b/crates/api_models/src/analytics/auth_events.rs
@@ -4,7 +4,8 @@ use std::{
 };
 
 use common_enums::{
-    AuthenticationConnectors, AuthenticationStatus, DecoupledAuthenticationType, TransactionStatus,
+    AuthenticationConnectors, AuthenticationStatus, Currency, DecoupledAuthenticationType,
+    TransactionStatus,
 };
 
 use super::{NameDescription, TimeRange};
@@ -24,7 +25,49 @@ pub struct AuthEventFilters {
     #[serde(default)]
     pub message_version: Vec<String>,
     #[serde(default)]
+    pub platform: Vec<String>,
+    #[serde(default)]
     pub acs_reference_number: Vec<String>,
+    #[serde(default)]
+    pub mcc: Vec<String>,
+    #[serde(default)]
+    pub currency: Vec<Currency>,
+    #[serde(default)]
+    pub merchant_country: Vec<String>,
+    #[serde(default)]
+    pub billing_country: Vec<String>,
+    #[serde(default)]
+    pub shipping_country: Vec<String>,
+    #[serde(default)]
+    pub issuer_country: Vec<String>,
+    #[serde(default)]
+    pub earliest_supported_version: Vec<String>,
+    #[serde(default)]
+    pub latest_supported_version: Vec<String>,
+    #[serde(default)]
+    pub whitelist_decision: Vec<bool>,
+    #[serde(default)]
+    pub device_manufacturer: Vec<String>,
+    #[serde(default)]
+    pub device_type: Vec<String>,
+    #[serde(default)]
+    pub device_brand: Vec<String>,
+    #[serde(default)]
+    pub device_os: Vec<String>,
+    #[serde(default)]
+    pub device_display: Vec<String>,
+    #[serde(default)]
+    pub browser_name: Vec<String>,
+    #[serde(default)]
+    pub browser_version: Vec<String>,
+    #[serde(default)]
+    pub issuer_id: Vec<String>,
+    #[serde(default)]
+    pub scheme_name: Vec<String>,
+    #[serde(default)]
+    pub exemption_requested: Vec<bool>,
+    #[serde(default)]
+    pub exemption_accepted: Vec<bool>,
 }
 
 #[derive(
@@ -53,6 +96,27 @@ pub enum AuthEventDimensions {
     AuthenticationConnector,
     MessageVersion,
     AcsReferenceNumber,
+    Platform,
+    Mcc,
+    Currency,
+    MerchantCountry,
+    BillingCountry,
+    ShippingCountry,
+    IssuerCountry,
+    EarliestSupportedVersion,
+    LatestSupportedVersion,
+    WhitelistDecision,
+    DeviceManufacturer,
+    DeviceType,
+    DeviceBrand,
+    DeviceOs,
+    DeviceDisplay,
+    BrowserName,
+    BrowserVersion,
+    IssuerId,
+    SchemeName,
+    ExemptionRequested,
+    ExemptionAccepted,
 }
 
 #[derive(
@@ -80,6 +144,8 @@ pub enum AuthEventMetrics {
     ChallengeSuccessCount,
     AuthenticationErrorMessage,
     AuthenticationFunnel,
+    AuthenticationExemptionApprovedCount,
+    AuthenticationExemptionRequestedCount,
 }
 
 #[derive(
@@ -139,6 +205,26 @@ pub struct AuthEventMetricsBucketIdentifier {
     pub authentication_connector: Option<AuthenticationConnectors>,
     pub message_version: Option<String>,
     pub acs_reference_number: Option<String>,
+    pub mcc: Option<String>,
+    pub currency: Option<Currency>,
+    pub merchant_country: Option<String>,
+    pub billing_country: Option<String>,
+    pub shipping_country: Option<String>,
+    pub issuer_country: Option<String>,
+    pub earliest_supported_version: Option<String>,
+    pub latest_supported_version: Option<String>,
+    pub whitelist_decision: Option<bool>,
+    pub device_manufacturer: Option<String>,
+    pub device_type: Option<String>,
+    pub device_brand: Option<String>,
+    pub device_os: Option<String>,
+    pub device_display: Option<String>,
+    pub browser_name: Option<String>,
+    pub browser_version: Option<String>,
+    pub issuer_id: Option<String>,
+    pub scheme_name: Option<String>,
+    pub exemption_requested: Option<bool>,
+    pub exemption_accepted: Option<bool>,
     #[serde(rename = "time_range")]
     pub time_bucket: TimeRange,
     #[serde(rename = "time_bucket")]
@@ -156,6 +242,26 @@ impl AuthEventMetricsBucketIdentifier {
         authentication_connector: Option<AuthenticationConnectors>,
         message_version: Option<String>,
         acs_reference_number: Option<String>,
+        mcc: Option<String>,
+        currency: Option<Currency>,
+        merchant_country: Option<String>,
+        billing_country: Option<String>,
+        shipping_country: Option<String>,
+        issuer_country: Option<String>,
+        earliest_supported_version: Option<String>,
+        latest_supported_version: Option<String>,
+        whitelist_decision: Option<bool>,
+        device_manufacturer: Option<String>,
+        device_type: Option<String>,
+        device_brand: Option<String>,
+        device_os: Option<String>,
+        device_display: Option<String>,
+        browser_name: Option<String>,
+        browser_version: Option<String>,
+        issuer_id: Option<String>,
+        scheme_name: Option<String>,
+        exemption_requested: Option<bool>,
+        exemption_accepted: Option<bool>,
         normalized_time_range: TimeRange,
     ) -> Self {
         Self {
@@ -166,6 +272,26 @@ impl AuthEventMetricsBucketIdentifier {
             authentication_connector,
             message_version,
             acs_reference_number,
+            mcc,
+            currency,
+            merchant_country,
+            billing_country,
+            shipping_country,
+            issuer_country,
+            earliest_supported_version,
+            latest_supported_version,
+            whitelist_decision,
+            device_manufacturer,
+            device_type,
+            device_brand,
+            device_os,
+            device_display,
+            browser_name,
+            browser_version,
+            issuer_id,
+            scheme_name,
+            exemption_requested,
+            exemption_accepted,
             time_bucket: normalized_time_range,
             start_time: normalized_time_range.start_time,
         }
@@ -181,6 +307,26 @@ impl Hash for AuthEventMetricsBucketIdentifier {
         self.message_version.hash(state);
         self.acs_reference_number.hash(state);
         self.error_message.hash(state);
+        self.mcc.hash(state);
+        self.currency.hash(state);
+        self.merchant_country.hash(state);
+        self.billing_country.hash(state);
+        self.shipping_country.hash(state);
+        self.issuer_country.hash(state);
+        self.earliest_supported_version.hash(state);
+        self.latest_supported_version.hash(state);
+        self.whitelist_decision.hash(state);
+        self.device_manufacturer.hash(state);
+        self.device_type.hash(state);
+        self.device_brand.hash(state);
+        self.device_os.hash(state);
+        self.device_display.hash(state);
+        self.browser_name.hash(state);
+        self.browser_version.hash(state);
+        self.issuer_id.hash(state);
+        self.scheme_name.hash(state);
+        self.exemption_requested.hash(state);
+        self.exemption_accepted.hash(state);
         self.time_bucket.hash(state);
     }
 }
@@ -207,6 +353,8 @@ pub struct AuthEventMetricsBucketValue {
     pub frictionless_success_count: Option<u64>,
     pub error_message_count: Option<u64>,
     pub authentication_funnel: Option<u64>,
+    pub authentication_exemption_approved_count: Option<u64>,
+    pub authentication_exemption_requested_count: Option<u64>,
 }
 
 #[derive(Debug, serde::Serialize)]
diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs
index 7aaebfc07c1..67722d3e25a 100644
--- a/crates/router/src/analytics.rs
+++ b/crates/router/src/analytics.rs
@@ -110,6 +110,10 @@ pub mod routes {
                             web::resource("metrics/auth_events")
                                 .route(web::post().to(get_auth_event_metrics)),
                         )
+                        .service(
+                            web::resource("metrics/auth_events/sankey")
+                                .route(web::post().to(get_auth_event_sankey)),
+                        )
                         .service(
                             web::resource("filters/auth_events")
                                 .route(web::post().to(get_merchant_auth_events_filters)),
@@ -2727,6 +2731,37 @@ pub mod routes {
         .await
     }
 
+    pub async fn get_auth_event_sankey(
+        state: web::Data<AppState>,
+        req: actix_web::HttpRequest,
+        json_payload: web::Json<TimeRange>,
+    ) -> impl Responder {
+        let flow = AnalyticsFlow::GetSankey;
+        let payload = json_payload.into_inner();
+        Box::pin(api::server_wrap(
+            flow,
+            state,
+            &req,
+            payload,
+            |state, auth: AuthenticationData, req, _| async move {
+                let org_id = auth.merchant_account.get_org_id();
+                let merchant_id = auth.merchant_account.get_id();
+                let auth: AuthInfo = AuthInfo::MerchantLevel {
+                    org_id: org_id.clone(),
+                    merchant_ids: vec![merchant_id.clone()],
+                };
+                analytics::auth_events::get_sankey(&state.pool, &auth, req)
+                    .await
+                    .map(ApplicationResponse::Json)
+            },
+            &auth::JWTAuth {
+                permission: Permission::MerchantAnalyticsRead,
+            },
+            api_locking::LockAction::NotApplicable,
+        ))
+        .await
+    }
+
     #[cfg(feature = "v1")]
     pub async fn get_org_sankey(
         state: web::Data<AppState>,

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [x] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
Added exemption_requested and exemption_approved fields to the auth_events metrics, extended the auth-analytics API with new filters for 3DS Intelligence, and created a dedicated endpoint to serve SCA-exemption Sankey chart data.


### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
Tested it locally by ingesting some test data in authentications tables in CKH. 
CURL for /metrics/auth_events introduced two new fields   authentication_exemption_approved_count and authentication_exemption_requested_count here
```
curl 'http://localhost:8080/analytics/v1/metrics/auth_events' \
  -H 'Accept: */*' \
  -H 'Accept-Language: en-US,en' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: login_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY2IzYjdjNGQtYjQ0Mi00ZDk0LWJkMjQtNjk5NjI4ZjQ1MzBkIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzQ4Mjg1ODc1Iiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTc0ODY5NDkwNywib3JnX2lkIjoib3JnX1VBQnlpWmZ4TW44dFpuSnZUemlvIiwicHJvZmlsZV9pZCI6InByb19Pcjh5cmlGYzBPY1pJZHFBOVFVVyIsInRlbmFudF9pZCI6InB1YmxpYyJ9.Bfo6nVoKTUl0I3Bzn2qg-kFxkiU3cWj-gHInHXpHneU' \
  -H 'Origin: http://localhost:9000/' \
  -H 'Pragma: no-cache' \
  -H 'Referer: http://localhost:9000/dashboard/analytics-authentication' \
  -H 'Sec-Fetch-Dest: empty' \
  -H 'Sec-Fetch-Mode: cors' \
  -H 'Sec-Fetch-Site: same-origin' \
  -H 'Sec-GPC: 1' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' \
  -H 'X-Merchant-Id: merchant_1748285875' \
  -H 'X-Profile-Id: pro_Or8yriFc0OcZIdqA9QUW' \
  -H 'api-key: hyperswitch' \
  -H 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY2IzYjdjNGQtYjQ0Mi00ZDk0LWJkMjQtNjk5NjI4ZjQ1MzBkIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzQ4Mjg1ODc1Iiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTc0ODY5NDkwNywib3JnX2lkIjoib3JnX1VBQnlpWmZ4TW44dFpuSnZUemlvIiwicHJvZmlsZV9pZCI6InByb19Pcjh5cmlGYzBPY1pJZHFBOVFVVyIsInRlbmFudF9pZCI6InB1YmxpYyJ9.Bfo6nVoKTUl0I3Bzn2qg-kFxkiU3cWj-gHInHXpHneU' \
  -H 'sec-ch-ua: "Chromium";v="130", "Brave";v="130", "Not?A_Brand";v="99"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  --data-raw '[{"timeRange":{"startTime":"2025-05-22T18:30:00Z","endTime":"2025-05-30T13:10:30Z"},"mode":"ORDER","source":"BATCH","metrics":["authentication_count","authentication_attempt_count","authentication_success_count","challenge_flow_count","frictionless_flow_count","frictionless_success_count","challenge_attempt_count","challenge_success_count"],"delta":true}]'
```
CURL for /metrics/auth_events/sankey this is required to build an exmemption analytics sankey chart on dashboard
```
curl 'http://localhost:8080/analytics/v1/metrics/auth_events/sankey' \
  -H 'Accept: */*' \
  -H 'Accept-Language: en-US,en' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: login_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY2IzYjdjNGQtYjQ0Mi00ZDk0LWJkMjQtNjk5NjI4ZjQ1MzBkIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzQ4Mjg1ODc1Iiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTc0ODY5NDkwNywib3JnX2lkIjoib3JnX1VBQnlpWmZ4TW44dFpuSnZUemlvIiwicHJvZmlsZV9pZCI6InByb19Pcjh5cmlGYzBPY1pJZHFBOVFVVyIsInRlbmFudF9pZCI6InB1YmxpYyJ9.Bfo6nVoKTUl0I3Bzn2qg-kFxkiU3cWj-gHInHXpHneU' \
  -H 'Origin: http://localhost:9000/' \
  -H 'Pragma: no-cache' \
  -H 'Referer: http://localhost:9000/dashboard/analytics-authentication' \
  -H 'Sec-Fetch-Dest: empty' \
  -H 'Sec-Fetch-Mode: cors' \
  -H 'Sec-Fetch-Site: same-origin' \
  -H 'Sec-GPC: 1' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' \
  -H 'X-Merchant-Id: merchant_1748285875' \
  -H 'X-Profile-Id: pro_Or8yriFc0OcZIdqA9QUW' \
  -H 'api-key: hyperswitch' \
  -H 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY2IzYjdjNGQtYjQ0Mi00ZDk0LWJkMjQtNjk5NjI4ZjQ1MzBkIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzQ4Mjg1ODc1Iiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTc0ODY5NDkwNywib3JnX2lkIjoib3JnX1VBQnlpWmZ4TW44dFpuSnZUemlvIiwicHJvZmlsZV9pZCI6InByb19Pcjh5cmlGYzBPY1pJZHFBOVFVVyIsInRlbmFudF9pZCI6InB1YmxpYyJ9.Bfo6nVoKTUl0I3Bzn2qg-kFxkiU3cWj-gHInHXpHneU' \
  -H 'sec-ch-ua: "Chromium";v="130", "Brave";v="130", "Not?A_Brand";v="99"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  --data-raw '{"startTime": "2025-01-22T18:30:00Z","endTime": "2025-05-29T12:59:33Z"}'
```



## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
