mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat(dynamic_routing): Decision engine config API integration (#8044)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -9,6 +9,7 @@ pub use euclid::{
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::{
|
||||
enums::{Currency, PaymentMethod},
|
||||
@ -172,3 +173,148 @@ pub enum TxnStatus {
|
||||
Failure,
|
||||
Declined,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DecisionEngineConfigSetupRequest {
|
||||
pub merchant_id: String,
|
||||
pub config: DecisionEngineConfigVariant,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum DecisionEngineConfigVariant {
|
||||
SuccessRate(DecisionEngineSuccessRateData),
|
||||
Elimination(DecisionEngineEliminationData),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DecisionEngineSuccessRateData {
|
||||
pub default_latency_threshold: Option<f64>,
|
||||
pub default_bucket_size: Option<i32>,
|
||||
pub default_hedging_percent: Option<f64>,
|
||||
pub default_lower_reset_factor: Option<f64>,
|
||||
pub default_upper_reset_factor: Option<f64>,
|
||||
pub default_gateway_extra_score: Option<Vec<DecisionEngineGatewayWiseExtraScore>>,
|
||||
pub sub_level_input_config: Option<Vec<DecisionEngineSRSubLevelInputConfig>>,
|
||||
}
|
||||
|
||||
impl DecisionEngineSuccessRateData {
|
||||
pub fn update(&mut self, new_config: Self) {
|
||||
if let Some(threshold) = new_config.default_latency_threshold {
|
||||
self.default_latency_threshold = Some(threshold);
|
||||
}
|
||||
if let Some(bucket_size) = new_config.default_bucket_size {
|
||||
self.default_bucket_size = Some(bucket_size);
|
||||
}
|
||||
if let Some(hedging_percent) = new_config.default_hedging_percent {
|
||||
self.default_hedging_percent = Some(hedging_percent);
|
||||
}
|
||||
if let Some(lower_reset_factor) = new_config.default_lower_reset_factor {
|
||||
self.default_lower_reset_factor = Some(lower_reset_factor);
|
||||
}
|
||||
if let Some(upper_reset_factor) = new_config.default_upper_reset_factor {
|
||||
self.default_upper_reset_factor = Some(upper_reset_factor);
|
||||
}
|
||||
if let Some(gateway_extra_score) = new_config.default_gateway_extra_score {
|
||||
self.default_gateway_extra_score
|
||||
.as_mut()
|
||||
.map(|score| score.extend(gateway_extra_score));
|
||||
}
|
||||
if let Some(sub_level_input_config) = new_config.sub_level_input_config {
|
||||
self.sub_level_input_config.as_mut().map(|config| {
|
||||
config.extend(sub_level_input_config);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DecisionEngineSRSubLevelInputConfig {
|
||||
pub payment_method_type: Option<String>,
|
||||
pub payment_method: Option<String>,
|
||||
pub latency_threshold: Option<f64>,
|
||||
pub bucket_size: Option<i32>,
|
||||
pub hedging_percent: Option<f64>,
|
||||
pub lower_reset_factor: Option<f64>,
|
||||
pub upper_reset_factor: Option<f64>,
|
||||
pub gateway_extra_score: Option<Vec<DecisionEngineGatewayWiseExtraScore>>,
|
||||
}
|
||||
|
||||
impl DecisionEngineSRSubLevelInputConfig {
|
||||
pub fn update(&mut self, new_config: Self) {
|
||||
if let Some(payment_method_type) = new_config.payment_method_type {
|
||||
self.payment_method_type = Some(payment_method_type);
|
||||
}
|
||||
if let Some(payment_method) = new_config.payment_method {
|
||||
self.payment_method = Some(payment_method);
|
||||
}
|
||||
if let Some(latency_threshold) = new_config.latency_threshold {
|
||||
self.latency_threshold = Some(latency_threshold);
|
||||
}
|
||||
if let Some(bucket_size) = new_config.bucket_size {
|
||||
self.bucket_size = Some(bucket_size);
|
||||
}
|
||||
if let Some(hedging_percent) = new_config.hedging_percent {
|
||||
self.hedging_percent = Some(hedging_percent);
|
||||
}
|
||||
if let Some(lower_reset_factor) = new_config.lower_reset_factor {
|
||||
self.lower_reset_factor = Some(lower_reset_factor);
|
||||
}
|
||||
if let Some(upper_reset_factor) = new_config.upper_reset_factor {
|
||||
self.upper_reset_factor = Some(upper_reset_factor);
|
||||
}
|
||||
if let Some(gateway_extra_score) = new_config.gateway_extra_score {
|
||||
self.gateway_extra_score
|
||||
.as_mut()
|
||||
.map(|score| score.extend(gateway_extra_score));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DecisionEngineGatewayWiseExtraScore {
|
||||
pub gateway_name: String,
|
||||
pub gateway_sigma_factor: f64,
|
||||
}
|
||||
|
||||
impl DecisionEngineGatewayWiseExtraScore {
|
||||
pub fn update(&mut self, new_config: Self) {
|
||||
self.gateway_name = new_config.gateway_name;
|
||||
self.gateway_sigma_factor = new_config.gateway_sigma_factor;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DecisionEngineEliminationData {
|
||||
pub threshold: f64,
|
||||
}
|
||||
|
||||
impl DecisionEngineEliminationData {
|
||||
pub fn update(&mut self, new_config: Self) {
|
||||
self.threshold = new_config.threshold;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MerchantAccount {
|
||||
pub merchant_id: String,
|
||||
pub gateway_success_rate_based_decider_input: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct FetchRoutingConfig {
|
||||
pub merchant_id: String,
|
||||
pub algorithm: AlgorithmType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum AlgorithmType {
|
||||
SuccessRate,
|
||||
Elimination,
|
||||
DebitRouting,
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use common_utils::{errors::ParsingError, ext_traits::ValueExt, pii};
|
||||
use common_utils::{
|
||||
errors::{ParsingError, ValidationError},
|
||||
ext_traits::ValueExt,
|
||||
pii,
|
||||
};
|
||||
pub use euclid::{
|
||||
dssa::types::EuclidAnalysable,
|
||||
frontend::{
|
||||
@ -11,7 +15,17 @@ pub use euclid::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::enums::{RoutableConnectors, TransactionType};
|
||||
use crate::{
|
||||
enums::{RoutableConnectors, TransactionType},
|
||||
open_router,
|
||||
};
|
||||
|
||||
// Define constants for default values
|
||||
const DEFAULT_LATENCY_THRESHOLD: f64 = 90.0;
|
||||
const DEFAULT_BUCKET_SIZE: i32 = 200;
|
||||
const DEFAULT_HEDGING_PERCENT: f64 = 5.0;
|
||||
const DEFAULT_ELIMINATION_THRESHOLD: f64 = 0.35;
|
||||
const DEFAULT_PAYMENT_METHOD: &str = "CARD";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
|
||||
@ -834,6 +848,8 @@ pub struct ToggleDynamicRoutingPath {
|
||||
pub struct EliminationRoutingConfig {
|
||||
pub params: Option<Vec<DynamicRoutingConfigParams>>,
|
||||
pub elimination_analyser_config: Option<EliminationAnalyserConfig>,
|
||||
#[schema(value_type = DecisionEngineEliminationData)]
|
||||
pub decision_engine_configs: Option<open_router::DecisionEngineEliminationData>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, ToSchema)]
|
||||
@ -861,6 +877,7 @@ impl Default for EliminationRoutingConfig {
|
||||
bucket_size: Some(5),
|
||||
bucket_leak_interval_in_secs: Some(60),
|
||||
}),
|
||||
decision_engine_configs: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -875,6 +892,34 @@ impl EliminationRoutingConfig {
|
||||
.as_mut()
|
||||
.map(|config| config.update(new_config));
|
||||
}
|
||||
if let Some(new_config) = new.decision_engine_configs {
|
||||
self.decision_engine_configs
|
||||
.as_mut()
|
||||
.map(|config| config.update(new_config));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_router_config_default() -> Self {
|
||||
Self {
|
||||
elimination_analyser_config: None,
|
||||
params: None,
|
||||
decision_engine_configs: Some(open_router::DecisionEngineEliminationData {
|
||||
threshold: DEFAULT_ELIMINATION_THRESHOLD,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_decision_engine_configs(
|
||||
&self,
|
||||
) -> Result<open_router::DecisionEngineEliminationData, error_stack::Report<ValidationError>>
|
||||
{
|
||||
self.decision_engine_configs
|
||||
.clone()
|
||||
.ok_or(error_stack::Report::new(
|
||||
ValidationError::MissingRequiredField {
|
||||
field_name: "decision_engine_configs".to_string(),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -882,6 +927,8 @@ impl EliminationRoutingConfig {
|
||||
pub struct SuccessBasedRoutingConfig {
|
||||
pub params: Option<Vec<DynamicRoutingConfigParams>>,
|
||||
pub config: Option<SuccessBasedRoutingConfigBody>,
|
||||
#[schema(value_type = DecisionEngineSuccessRateData)]
|
||||
pub decision_engine_configs: Option<open_router::DecisionEngineSuccessRateData>,
|
||||
}
|
||||
|
||||
impl Default for SuccessBasedRoutingConfig {
|
||||
@ -898,6 +945,7 @@ impl Default for SuccessBasedRoutingConfig {
|
||||
}),
|
||||
specificity_level: SuccessRateSpecificityLevel::default(),
|
||||
}),
|
||||
decision_engine_configs: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -982,6 +1030,51 @@ impl SuccessBasedRoutingConfig {
|
||||
if let Some(new_config) = new.config {
|
||||
self.config.as_mut().map(|config| config.update(new_config));
|
||||
}
|
||||
if let Some(new_config) = new.decision_engine_configs {
|
||||
self.decision_engine_configs
|
||||
.as_mut()
|
||||
.map(|config| config.update(new_config));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_router_config_default() -> Self {
|
||||
Self {
|
||||
params: None,
|
||||
config: None,
|
||||
decision_engine_configs: Some(open_router::DecisionEngineSuccessRateData {
|
||||
default_latency_threshold: Some(DEFAULT_LATENCY_THRESHOLD),
|
||||
default_bucket_size: Some(DEFAULT_BUCKET_SIZE),
|
||||
default_hedging_percent: Some(DEFAULT_HEDGING_PERCENT),
|
||||
default_lower_reset_factor: None,
|
||||
default_upper_reset_factor: None,
|
||||
default_gateway_extra_score: None,
|
||||
sub_level_input_config: Some(vec![
|
||||
open_router::DecisionEngineSRSubLevelInputConfig {
|
||||
payment_method_type: Some(DEFAULT_PAYMENT_METHOD.to_string()),
|
||||
payment_method: None,
|
||||
latency_threshold: None,
|
||||
bucket_size: Some(DEFAULT_BUCKET_SIZE),
|
||||
hedging_percent: Some(DEFAULT_HEDGING_PERCENT),
|
||||
lower_reset_factor: None,
|
||||
upper_reset_factor: None,
|
||||
gateway_extra_score: None,
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_decision_engine_configs(
|
||||
&self,
|
||||
) -> Result<open_router::DecisionEngineSuccessRateData, error_stack::Report<ValidationError>>
|
||||
{
|
||||
self.decision_engine_configs
|
||||
.clone()
|
||||
.ok_or(error_stack::Report::new(
|
||||
ValidationError::MissingRequiredField {
|
||||
field_name: "decision_engine_configs".to_string(),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user