Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(routing): Integrate global success rates #6950

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
10 changes: 10 additions & 0 deletions api-reference/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -25162,9 +25162,19 @@
}
],
"nullable": true
},
"specificity_level": {
"$ref": "#/components/schemas/SuccessRateSpecificityLevel"
}
}
},
"SuccessRateSpecificityLevel": {
"type": "string",
"enum": [
"merchant",
"global"
]
},
"SupportedPaymentMethod": {
"type": "object",
"required": [
Expand Down
12 changes: 12 additions & 0 deletions crates/api_models/src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ impl Default for SuccessBasedRoutingConfig {
duration_in_mins: Some(5),
max_total_count: Some(2),
}),
specificity_level: SuccessRateSpecificityLevel::default(),
}),
}
}
Expand All @@ -801,6 +802,8 @@ pub struct SuccessBasedRoutingConfigBody {
pub default_success_rate: Option<f64>,
pub max_aggregates_size: Option<u32>,
pub current_block_threshold: Option<CurrentBlockThreshold>,
#[serde(default)]
pub specificity_level: SuccessRateSpecificityLevel,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
Expand All @@ -809,6 +812,14 @@ pub struct CurrentBlockThreshold {
pub max_total_count: Option<u64>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum SuccessRateSpecificityLevel {
Sarthak1799 marked this conversation as resolved.
Show resolved Hide resolved
#[default]
Merchant,
Global,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SuccessBasedRoutingPayloadWrapper {
pub updated_config: SuccessBasedRoutingConfig,
Expand Down Expand Up @@ -849,6 +860,7 @@ impl SuccessBasedRoutingConfigBody {
.as_mut()
.map(|threshold| threshold.update(current_block_threshold));
}
self.specificity_level = new.specificity_level
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
use api_models::routing::{
CurrentBlockThreshold, RoutableConnectorChoice, RoutableConnectorChoiceWithStatus,
SuccessBasedRoutingConfig, SuccessBasedRoutingConfigBody,
SuccessBasedRoutingConfig, SuccessBasedRoutingConfigBody, SuccessRateSpecificityLevel,
};
use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom};
use error_stack::ResultExt;
pub use success_rate::{
success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig,
success_rate_calculator_client::SuccessRateCalculatorClient, CalGlobalSuccessRateConfig,
CalGlobalSuccessRateRequest, CalGlobalSuccessRateResponse, CalSuccessRateConfig,
CalSuccessRateRequest, CalSuccessRateResponse,
CurrentBlockThreshold as DynamicCurrentThreshold, InvalidateWindowsRequest,
InvalidateWindowsResponse, LabelWithStatus, UpdateSuccessRateWindowConfig,
InvalidateWindowsResponse, LabelWithStatus,
SuccessRateSpecificityLevel as ProtoSpecificityLevel, UpdateSuccessRateWindowConfig,
UpdateSuccessRateWindowRequest, UpdateSuccessRateWindowResponse,
};
#[allow(
missing_docs,
unused_qualifications,
clippy::unwrap_used,
clippy::as_conversions
clippy::as_conversions,
clippy::use_self
)]
pub mod success_rate {
tonic::include_proto!("success_rate");
Expand Down Expand Up @@ -49,6 +52,15 @@ pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync {
id: String,
headers: GrpcHeaders,
) -> DynamicRoutingResult<InvalidateWindowsResponse>;
/// To calculate both global and merchant specific success rate for the list of chosen connectors
async fn calculate_entity_and_global_success_rate(
&self,
id: String,
success_rate_based_config: SuccessBasedRoutingConfig,
params: String,
label_input: Vec<RoutableConnectorChoice>,
headers: GrpcHeaders,
) -> DynamicRoutingResult<CalGlobalSuccessRateResponse>;
}

#[async_trait::async_trait]
Expand Down Expand Up @@ -106,18 +118,29 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
.transpose()?;

let labels_with_status = label_input
.clone()
.into_iter()
.map(|conn_choice| LabelWithStatus {
label: conn_choice.routable_connector_choice.to_string(),
status: conn_choice.status,
})
.collect();

let global_labels_with_status = label_input
.into_iter()
.map(|conn_choice| LabelWithStatus {
label: conn_choice.routable_connector_choice.connector.to_string(),
status: conn_choice.status,
})
.collect();

let mut request = tonic::Request::new(UpdateSuccessRateWindowRequest {
id,
params,
params: params.clone(),
labels_with_status,
config,
global_params: params,
global_labels_with_status,
});

request.add_headers_to_grpc_request(headers);
Expand Down Expand Up @@ -152,6 +175,53 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
.into_inner();
Ok(response)
}

async fn calculate_entity_and_global_success_rate(
&self,
id: String,
success_rate_based_config: SuccessBasedRoutingConfig,
params: String,
label_input: Vec<RoutableConnectorChoice>,
headers: GrpcHeaders,
) -> DynamicRoutingResult<CalGlobalSuccessRateResponse> {
let labels = label_input
.clone()
.into_iter()
.map(|conn_choice| conn_choice.to_string())
.collect::<Vec<_>>();

let global_labels = label_input
.into_iter()
.map(|conn_choice| conn_choice.connector.to_string())
.collect::<Vec<_>>();

let config = success_rate_based_config
.config
.map(ForeignTryFrom::foreign_try_from)
.transpose()?;

let mut request = tonic::Request::new(CalGlobalSuccessRateRequest {
entity_id: id,
entity_params: params.clone(),
entity_labels: labels,
global_params: params,
global_labels,
config,
});

request.add_headers_to_grpc_request(headers);

let response = self
.clone()
.fetch_entity_and_global_success_rate(request)
.await
.change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure(
"Failed to fetch the entity and global success rate".to_string(),
))?
.into_inner();

Ok(response)
}
}

impl ForeignTryFrom<CurrentBlockThreshold> for DynamicCurrentThreshold {
Expand Down Expand Up @@ -203,6 +273,30 @@ impl ForeignTryFrom<SuccessBasedRoutingConfigBody> for CalSuccessRateConfig {
.change_context(DynamicRoutingError::MissingRequiredField {
field: "default_success_rate".to_string(),
})?,
specificity_level: match config.specificity_level {
SuccessRateSpecificityLevel::Merchant => Some(ProtoSpecificityLevel::Entity.into()),
SuccessRateSpecificityLevel::Global => Some(ProtoSpecificityLevel::Global.into()),
},
})
}
}

impl ForeignTryFrom<SuccessBasedRoutingConfigBody> for CalGlobalSuccessRateConfig {
type Error = error_stack::Report<DynamicRoutingError>;
fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result<Self, Self::Error> {
Ok(Self {
entity_min_aggregates_size: config
.min_aggregates_size
.get_required_value("min_aggregate_size")
.change_context(DynamicRoutingError::MissingRequiredField {
field: "min_aggregates_size".to_string(),
})?,
entity_default_success_rate: config
.default_success_rate
.get_required_value("default_success_rate")
.change_context(DynamicRoutingError::MissingRequiredField {
field: "default_success_rate".to_string(),
})?,
})
}
}
1 change: 1 addition & 0 deletions crates/openapi/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::routing::StraightThroughAlgorithm,
api_models::routing::ConnectorVolumeSplit,
api_models::routing::ConnectorSelection,
api_models::routing::SuccessRateSpecificityLevel,
api_models::routing::ToggleDynamicRoutingQuery,
api_models::routing::ToggleDynamicRoutingPath,
api_models::routing::ast::RoutableChoiceKind,
Expand Down
34 changes: 25 additions & 9 deletions crates/router/src/core/routing/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
);

let success_based_connectors = client
.calculate_success_rate(
.calculate_entity_and_global_success_rate(
business_profile.get_id().get_string_repr().into(),
success_based_routing_configs.clone(),
success_based_routing_config_params.clone(),
Expand All @@ -725,8 +725,8 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
let payment_status_attribute =
get_desired_payment_status_for_success_routing_metrics(payment_attempt.status);

let first_success_based_connector_label = &success_based_connectors
.labels_with_score
let first_merchant_success_based_connector_label = &success_based_connectors
.entity_scores_with_labels
.first()
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
Expand All @@ -735,18 +735,26 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
.label
.to_string();

let (first_success_based_connector, _) = first_success_based_connector_label
let (first_merchant_success_based_connector, _) = first_merchant_success_based_connector_label
.split_once(':')
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable(format!(
"unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service",
first_success_based_connector_label
first_merchant_success_based_connector_label
))?;

let first_global_success_based_connector = &success_based_connectors
.global_scores_with_labels
.first()
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"unable to fetch the first global connector from list of connectors obtained from dynamic routing service",
)?;

let outcome = get_success_based_metrics_outcome_for_payment(
payment_status_attribute,
payment_connector.to_string(),
first_success_based_connector.to_string(),
first_merchant_success_based_connector.to_string(),
);

let dynamic_routing_stats = DynamicRoutingStatsNew {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we create a new column here which stores the globally_top_performing_connector at the time this particular payment was made?
cc: @prajjwalkumar17

Expand All @@ -755,7 +763,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
merchant_id: payment_attempt.merchant_id.to_owned(),
profile_id: payment_attempt.profile_id.to_owned(),
amount: payment_attempt.get_total_amount(),
success_based_routing_connector: first_success_based_connector.to_string(),
success_based_routing_connector: first_merchant_success_based_connector.to_string(),
payment_connector: payment_connector.to_string(),
payment_method_type: payment_attempt.payment_method_type,
currency: payment_attempt.currency,
Expand Down Expand Up @@ -783,8 +791,16 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
),
),
(
"success_based_routing_connector",
first_success_based_connector.to_string(),
"merchant_specific_success_based_routing_connector",
first_merchant_success_based_connector.to_string(),
),
(
"global_success_based_routing_connector",
first_global_success_based_connector.label.to_string(),
),
(
"global_success_based_routing_connector_score",
first_global_success_based_connector.score.to_string(),
),
("payment_connector", payment_connector.to_string()),
(
Expand Down
32 changes: 31 additions & 1 deletion proto/success_rate.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ service SuccessRateCalculator {
rpc UpdateSuccessRateWindow (UpdateSuccessRateWindowRequest) returns (UpdateSuccessRateWindowResponse);

rpc InvalidateWindows (InvalidateWindowsRequest) returns (InvalidateWindowsResponse);

rpc FetchEntityAndGlobalSuccessRate (CalGlobalSuccessRateRequest) returns (CalGlobalSuccessRateResponse);
}

// API-1 types
Expand All @@ -20,6 +22,12 @@ message CalSuccessRateRequest {
message CalSuccessRateConfig {
uint32 min_aggregates_size = 1;
double default_success_rate = 2;
optional SuccessRateSpecificityLevel specificity_level = 3;
}

enum SuccessRateSpecificityLevel {
ENTITY = 0;
GLOBAL = 1;
}

message CalSuccessRateResponse {
Expand All @@ -31,12 +39,14 @@ message LabelWithScore {
string label = 2;
}

// API-2 types
// API-2 types
message UpdateSuccessRateWindowRequest {
string id = 1;
string params = 2;
repeated LabelWithStatus labels_with_status = 3;
UpdateSuccessRateWindowConfig config = 4;
string global_params = 5;
repeated LabelWithStatus global_labels_with_status = 6;
}

message LabelWithStatus {
Expand Down Expand Up @@ -65,4 +75,24 @@ message InvalidateWindowsRequest {

message InvalidateWindowsResponse {
string message = 1;
}

// API-4 types
message CalGlobalSuccessRateRequest {
string entity_id = 1;
string entity_params = 2;
repeated string entity_labels = 3;
string global_params = 4;
repeated string global_labels = 5;
CalGlobalSuccessRateConfig config = 6;
}

message CalGlobalSuccessRateConfig {
uint32 entity_min_aggregates_size = 1;
double entity_default_success_rate = 2;
}

message CalGlobalSuccessRateResponse {
repeated LabelWithScore entity_scores_with_labels = 1;
repeated LabelWithScore global_scores_with_labels = 2;
}
Loading