wingify-fme-ruby-sdk 1.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/resources/debug_messages.json +21 -0
- data/lib/resources/error_messages.json +63 -0
- data/lib/resources/info_messages.json +43 -0
- data/lib/resources/warn_messages.json +1 -0
- data/lib/vwo.rb +40 -0
- data/lib/wingify/api/get_flag.rb +244 -0
- data/lib/wingify/api/set_attribute.rb +57 -0
- data/lib/wingify/api/track_event.rb +80 -0
- data/lib/wingify/constants/constants.rb +106 -0
- data/lib/wingify/decorators/storage_decorator.rb +82 -0
- data/lib/wingify/enums/api_enum.rb +23 -0
- data/lib/wingify/enums/campaign_type_enum.rb +19 -0
- data/lib/wingify/enums/debug_category_enum.rb +20 -0
- data/lib/wingify/enums/decision_types_enum.rb +18 -0
- data/lib/wingify/enums/event_enum.rb +22 -0
- data/lib/wingify/enums/headers_enum.rb +20 -0
- data/lib/wingify/enums/hooks_enum.rb +17 -0
- data/lib/wingify/enums/http_method_enum.rb +18 -0
- data/lib/wingify/enums/log_level_enum.rb +21 -0
- data/lib/wingify/enums/log_level_to_number.rb +27 -0
- data/lib/wingify/enums/status_enum.rb +19 -0
- data/lib/wingify/enums/storage_enum.rb +22 -0
- data/lib/wingify/enums/url_enum.rb +22 -0
- data/lib/wingify/models/campaign/campaign_model.rb +192 -0
- data/lib/wingify/models/campaign/feature_model.rb +111 -0
- data/lib/wingify/models/campaign/impact_campaign_model.rb +38 -0
- data/lib/wingify/models/campaign/metric_model.rb +44 -0
- data/lib/wingify/models/campaign/rule_model.rb +56 -0
- data/lib/wingify/models/campaign/variable_model.rb +51 -0
- data/lib/wingify/models/campaign/variation_model.rb +137 -0
- data/lib/wingify/models/gateway_service_model.rb +39 -0
- data/lib/wingify/models/schemas/settings_schema_validation.rb +104 -0
- data/lib/wingify/models/settings/settings_model.rb +101 -0
- data/lib/wingify/models/storage/storage_data_model.rb +44 -0
- data/lib/wingify/models/user/context_model.rb +154 -0
- data/lib/wingify/models/user/context_vwo_model.rb +38 -0
- data/lib/wingify/models/user/get_flag_response.rb +50 -0
- data/lib/wingify/models/vwo_options_model.rb +129 -0
- data/lib/wingify/packages/decision_maker/decision_maker.rb +60 -0
- data/lib/wingify/packages/logger/core/log_manager.rb +92 -0
- data/lib/wingify/packages/logger/core/transport_manager.rb +76 -0
- data/lib/wingify/packages/logger/log_message_builder.rb +70 -0
- data/lib/wingify/packages/logger/logger.rb +38 -0
- data/lib/wingify/packages/logger/transports/console_transport.rb +49 -0
- data/lib/wingify/packages/network_layer/client/network_client.rb +276 -0
- data/lib/wingify/packages/network_layer/handlers/request_handler.rb +37 -0
- data/lib/wingify/packages/network_layer/manager/network_manager.rb +145 -0
- data/lib/wingify/packages/network_layer/models/global_request_model.rb +105 -0
- data/lib/wingify/packages/network_layer/models/request_model.rb +179 -0
- data/lib/wingify/packages/network_layer/models/response_model.rb +62 -0
- data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +77 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +33 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +218 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +415 -0
- data/lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
- data/lib/wingify/packages/storage/connector.rb +26 -0
- data/lib/wingify/packages/storage/storage.rb +47 -0
- data/lib/wingify/services/batch_event_queue.rb +179 -0
- data/lib/wingify/services/campaign_decision_service.rb +161 -0
- data/lib/wingify/services/hooks_service.rb +51 -0
- data/lib/wingify/services/logger_service.rb +114 -0
- data/lib/wingify/services/settings_service.rb +178 -0
- data/lib/wingify/services/storage_service.rb +66 -0
- data/lib/wingify/utils/batch_event_dispatcher_util.rb +178 -0
- data/lib/wingify/utils/brand_context.rb +33 -0
- data/lib/wingify/utils/brand_util.rb +53 -0
- data/lib/wingify/utils/campaign_util.rb +284 -0
- data/lib/wingify/utils/data_type_util.rb +105 -0
- data/lib/wingify/utils/debugger_service_util.rb +40 -0
- data/lib/wingify/utils/decision_util.rb +259 -0
- data/lib/wingify/utils/event_util.rb +55 -0
- data/lib/wingify/utils/function_util.rb +141 -0
- data/lib/wingify/utils/gateway_service_util.rb +101 -0
- data/lib/wingify/utils/impression_util.rb +66 -0
- data/lib/wingify/utils/log_message_util.rb +42 -0
- data/lib/wingify/utils/meg_util.rb +357 -0
- data/lib/wingify/utils/network_util.rb +503 -0
- data/lib/wingify/utils/rule_evaluation_util.rb +57 -0
- data/lib/wingify/utils/settings_util.rb +38 -0
- data/lib/wingify/utils/usage_stats_util.rb +119 -0
- data/lib/wingify/utils/uuid_util.rb +96 -0
- data/lib/wingify/wingify_builder.rb +261 -0
- data/lib/wingify/wingify_client.rb +227 -0
- data/lib/wingify.rb +117 -0
- metadata +327 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
require_relative '../constants/constants'
|
|
16
|
+
require_relative '../decorators/storage_decorator'
|
|
17
|
+
require_relative '../enums/campaign_type_enum'
|
|
18
|
+
require_relative '../models/campaign/campaign_model'
|
|
19
|
+
require_relative '../models/campaign/feature_model'
|
|
20
|
+
require_relative '../models/campaign/variation_model'
|
|
21
|
+
require_relative '../models/settings/settings_model'
|
|
22
|
+
require_relative '../models/user/context_model'
|
|
23
|
+
require_relative '../packages/decision_maker/decision_maker'
|
|
24
|
+
require_relative '../services/campaign_decision_service'
|
|
25
|
+
require_relative '../services/storage_service'
|
|
26
|
+
require_relative './rule_evaluation_util'
|
|
27
|
+
require_relative './campaign_util'
|
|
28
|
+
require_relative './data_type_util'
|
|
29
|
+
require_relative './decision_util'
|
|
30
|
+
require_relative './function_util'
|
|
31
|
+
require_relative '../services/logger_service'
|
|
32
|
+
require_relative '../enums/log_level_enum'
|
|
33
|
+
require_relative '../enums/api_enum'
|
|
34
|
+
|
|
35
|
+
# Evaluates groups for a given feature and group ID.
|
|
36
|
+
# @param settings [SettingsModel] The settings for the VWO instance
|
|
37
|
+
# @param feature [FeatureModel] The feature to evaluate
|
|
38
|
+
# @param group_id [String] The group ID to evaluate
|
|
39
|
+
# @param evaluated_feature_map [Hash] The map of evaluated features
|
|
40
|
+
# @param context [ContextModel] The context for the evaluation
|
|
41
|
+
# @param storage_service [StorageService] The storage service for the evaluation
|
|
42
|
+
def evaluate_groups(settings, feature, group_id, evaluated_feature_map, context, storage_service)
|
|
43
|
+
feature_to_skip = []
|
|
44
|
+
campaign_map = {}
|
|
45
|
+
|
|
46
|
+
# Get all feature keys and campaignIds from the groupId
|
|
47
|
+
feature_keys, group_campaign_ids = get_feature_keys_from_group(settings, group_id)
|
|
48
|
+
|
|
49
|
+
feature_keys.each do |feature_key|
|
|
50
|
+
temp_feature = get_feature_from_key(settings, feature_key)
|
|
51
|
+
|
|
52
|
+
next if feature_to_skip.include?(feature_key)
|
|
53
|
+
|
|
54
|
+
# Evaluate the feature rollout rules
|
|
55
|
+
is_rollout_rule_passed = is_rollout_rule_for_feature_passed(settings, temp_feature, evaluated_feature_map, feature_to_skip, storage_service, context)
|
|
56
|
+
if is_rollout_rule_passed
|
|
57
|
+
settings.get_features.each do |current_feature|
|
|
58
|
+
if current_feature.key == feature_key
|
|
59
|
+
current_feature.get_rules_linked_campaign.each do |rule|
|
|
60
|
+
if group_campaign_ids.include?(rule.id.to_s) || group_campaign_ids.include?("#{rule.id}_#{rule.variations[0].id}")
|
|
61
|
+
campaign_map[feature_key] ||= []
|
|
62
|
+
# Check if the campaign is already present in the campaignMap for the feature
|
|
63
|
+
if campaign_map[feature_key].find_index { |item| item.rule_key == rule.rule_key }.nil?
|
|
64
|
+
campaign_map[feature_key] << rule
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
eligible_campaigns, eligible_campaigns_with_storage = get_eligible_campaigns(settings, campaign_map, context, storage_service)
|
|
74
|
+
|
|
75
|
+
find_winner_campaign_among_eligible_campaigns(settings, feature.key, eligible_campaigns, eligible_campaigns_with_storage, group_id, context, storage_service)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Get the feature keys from the group
|
|
79
|
+
# @param settings [SettingsModel] The settings for the VWO instance
|
|
80
|
+
# @param group_id [String] The group ID to get the feature keys from
|
|
81
|
+
# @return [Array] The feature keys and the group campaign IDs
|
|
82
|
+
def get_feature_keys_from_group(settings, group_id)
|
|
83
|
+
group_campaign_ids = CampaignUtil.get_campaigns_by_group_id(settings, group_id)
|
|
84
|
+
feature_keys = CampaignUtil.get_feature_keys_from_campaign_ids(settings, group_campaign_ids)
|
|
85
|
+
|
|
86
|
+
return feature_keys, group_campaign_ids
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Check if the rollout rule for the feature is passed
|
|
90
|
+
# @param settings [SettingsModel] The settings for the VWO instance
|
|
91
|
+
# @param feature [FeatureModel] The feature to check the rollout rule for
|
|
92
|
+
# @param evaluated_feature_map [Hash] The map of evaluated features
|
|
93
|
+
# @param feature_to_skip [Array] The list of features to skip
|
|
94
|
+
# @param storage_service [StorageService] The storage service for the evaluation
|
|
95
|
+
# @param context [ContextModel] The context for the evaluation
|
|
96
|
+
def is_rollout_rule_for_feature_passed(settings, feature, evaluated_feature_map, feature_to_skip, storage_service, context)
|
|
97
|
+
return true if evaluated_feature_map.key?(feature.key) && evaluated_feature_map[feature.key].key?(:rollout_id)
|
|
98
|
+
|
|
99
|
+
rollout_rules = get_specific_rules_based_on_type(feature, CampaignTypeEnum::ROLLOUT)
|
|
100
|
+
|
|
101
|
+
if rollout_rules.any?
|
|
102
|
+
rule_to_test_for_traffic = nil
|
|
103
|
+
rollout_rules.each do |rule|
|
|
104
|
+
result = evaluate_rule(settings, feature, rule, context, evaluated_feature_map, nil, storage_service, {})
|
|
105
|
+
if result[:pre_segmentation_result]
|
|
106
|
+
rule_to_test_for_traffic = rule
|
|
107
|
+
break
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if rule_to_test_for_traffic
|
|
112
|
+
campaign = CampaignModel.new.model_from_dictionary(rule_to_test_for_traffic)
|
|
113
|
+
variation = evaluate_traffic_and_get_variation(settings, campaign, context)
|
|
114
|
+
if variation.is_a?(VariationModel) && !variation.nil? && variation.id.is_a?(Integer)
|
|
115
|
+
evaluated_feature_map[feature.key] = {
|
|
116
|
+
rollout_id: rule_to_test_for_traffic.id,
|
|
117
|
+
rollout_key: rule_to_test_for_traffic.key,
|
|
118
|
+
rollout_variation_id: rule_to_test_for_traffic.variations[0].id
|
|
119
|
+
}
|
|
120
|
+
return true
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# No rollout rule passed
|
|
125
|
+
feature_to_skip.push(feature.key)
|
|
126
|
+
return false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# No rollout rule, evaluate experiments
|
|
130
|
+
LoggerService.log(LogLevelEnum::INFO, "MEG_SKIP_ROLLOUT_EVALUATE_EXPERIMENTS", { featureKey: feature.key })
|
|
131
|
+
return true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Get the eligible campaigns
|
|
135
|
+
# @param settings [SettingsModel] The settings for the VWO instance
|
|
136
|
+
# @param campaign_map [Hash] The map of campaigns
|
|
137
|
+
# @param context [ContextModel] The context for the evaluation
|
|
138
|
+
# @param storage_service [StorageService] The storage service for the evaluation
|
|
139
|
+
def get_eligible_campaigns(settings, campaign_map, context, storage_service)
|
|
140
|
+
eligible_campaigns = []
|
|
141
|
+
eligible_campaigns_with_storage = []
|
|
142
|
+
ineligible_campaigns = []
|
|
143
|
+
|
|
144
|
+
campaign_map.each do |feature_key, campaigns|
|
|
145
|
+
campaigns.each do |campaign|
|
|
146
|
+
stored_data = StorageDecorator.new.get_feature_from_storage(feature_key, context, storage_service)
|
|
147
|
+
|
|
148
|
+
if stored_data && stored_data[:experiment_variation_id]
|
|
149
|
+
if stored_data[:experiment_key] == campaign.key
|
|
150
|
+
variation = CampaignUtil.get_variation_from_campaign_key(settings, stored_data[:experiment_key], stored_data[:experiment_variation_id])
|
|
151
|
+
if variation
|
|
152
|
+
LoggerService.log(LogLevelEnum::INFO, "MEG_CAMPAIGN_FOUND_IN_STORAGE", { campaignKey: stored_data[:experiment_key], userId: context.id })
|
|
153
|
+
|
|
154
|
+
unless eligible_campaigns_with_storage.any? { |item| item.key == campaign.key }
|
|
155
|
+
eligible_campaigns_with_storage.push(campaign)
|
|
156
|
+
end
|
|
157
|
+
next
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Check if user is eligible for the campaign
|
|
163
|
+
if CampaignDecisionService.new.get_pre_segmentation_decision(campaign, context) && CampaignDecisionService.new.is_user_part_of_campaign(context, campaign)
|
|
164
|
+
LoggerService.log(LogLevelEnum::INFO, "MEG_CAMPAIGN_ELIGIBLE", { campaignKey: campaign.key, userId: context.id })
|
|
165
|
+
|
|
166
|
+
eligible_campaigns.push(campaign)
|
|
167
|
+
next
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
ineligible_campaigns.push(campaign)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
return eligible_campaigns, eligible_campaigns_with_storage
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Find the winner campaign among the eligible campaigns
|
|
178
|
+
# @param settings [SettingsModel] The settings for the VWO instance
|
|
179
|
+
# @param feature_key [String] The key of the feature
|
|
180
|
+
# @param eligible_campaigns [Array] The list of eligible campaigns
|
|
181
|
+
# @param eligible_campaigns_with_storage [Array] The list of eligible campaigns with storage
|
|
182
|
+
# @param group_id [String] The ID of the group
|
|
183
|
+
def find_winner_campaign_among_eligible_campaigns(settings, feature_key, eligible_campaigns, eligible_campaigns_with_storage, group_id, context, storage_service)
|
|
184
|
+
winner_campaign = nil
|
|
185
|
+
campaign_ids = CampaignUtil.get_campaign_ids_from_feature_key(settings, feature_key)
|
|
186
|
+
meg_algo_number = settings.get_groups[group_id.to_s][:et.to_s] || Constants::RANDOM_ALGO
|
|
187
|
+
|
|
188
|
+
# Check eligible_campaigns_with_storage first
|
|
189
|
+
if eligible_campaigns_with_storage.length == 1
|
|
190
|
+
winner_campaign = eligible_campaigns_with_storage[0]
|
|
191
|
+
LoggerService.log(LogLevelEnum::INFO, "MEG_WINNER_CAMPAIGN", { campaignKey: winner_campaign.key, groupId: group_id, userId: context.id, algo: 'using random algorithm' })
|
|
192
|
+
elsif eligible_campaigns_with_storage.length > 1 && meg_algo_number == Constants::RANDOM_ALGO
|
|
193
|
+
winner_campaign = normalize_weights_and_find_winning_campaign(eligible_campaigns_with_storage, context, campaign_ids, group_id, storage_service)
|
|
194
|
+
elsif eligible_campaigns_with_storage.length > 1
|
|
195
|
+
winner_campaign = get_campaign_using_advanced_algo(settings, eligible_campaigns_with_storage, context, campaign_ids, group_id, storage_service)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Fallback to eligible_campaigns if no winner found in storage
|
|
199
|
+
if eligible_campaigns_with_storage.empty?
|
|
200
|
+
if eligible_campaigns.length == 1
|
|
201
|
+
winner_campaign = eligible_campaigns[0]
|
|
202
|
+
LoggerService.log(LogLevelEnum::INFO, "MEG_WINNER_CAMPAIGN", { campaignKey: winner_campaign.key, groupId: group_id, userId: context.id, algo: 'using random algorithm' })
|
|
203
|
+
elsif eligible_campaigns.length > 1 && meg_algo_number == Constants::RANDOM_ALGO
|
|
204
|
+
winner_campaign = normalize_weights_and_find_winning_campaign(eligible_campaigns, context, campaign_ids, group_id, storage_service)
|
|
205
|
+
elsif eligible_campaigns.length > 1
|
|
206
|
+
winner_campaign = get_campaign_using_advanced_algo(settings, eligible_campaigns, context, campaign_ids, group_id, storage_service)
|
|
207
|
+
else
|
|
208
|
+
LoggerService.log(LogLevelEnum::INFO, "No winner campaign found for MEG group: #{group_id}", nil)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
winner_campaign
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Helper for random allocation winner selection
|
|
216
|
+
# @param shortlisted_campaigns [Array] The list of shortlisted campaigns
|
|
217
|
+
# @param context [ContextModel] The context for the evaluation
|
|
218
|
+
# @param called_campaign_ids [Array] The list of called campaign IDs
|
|
219
|
+
# @param group_id [String] The ID of the group
|
|
220
|
+
# @param storage_service [StorageService] The storage service for the evaluation
|
|
221
|
+
def normalize_weights_and_find_winning_campaign(shortlisted_campaigns, context, called_campaign_ids, group_id, storage_service)
|
|
222
|
+
# Convert to VariationModel first and then normalize weights
|
|
223
|
+
shortlisted_variations = shortlisted_campaigns.map do |campaign|
|
|
224
|
+
variation = VariationModel.new.model_from_dictionary(campaign)
|
|
225
|
+
variation.weight = (100.0 / shortlisted_campaigns.length).round(4)
|
|
226
|
+
variation
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Set campaign allocation
|
|
230
|
+
CampaignUtil.set_campaign_allocation(shortlisted_variations)
|
|
231
|
+
|
|
232
|
+
winner_campaign = CampaignDecisionService.new.get_variation(
|
|
233
|
+
shortlisted_variations,
|
|
234
|
+
DecisionMaker.new.calculate_bucket_value(CampaignUtil.get_bucketing_seed(context.get_bucketing_seed || context.get_id, nil, group_id))
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if winner_campaign
|
|
238
|
+
campaign_key = winner_campaign.type == CampaignTypeEnum::AB ?
|
|
239
|
+
winner_campaign.key :
|
|
240
|
+
"#{winner_campaign.key}_#{winner_campaign.rule_key}"
|
|
241
|
+
|
|
242
|
+
LoggerService.log(
|
|
243
|
+
LogLevelEnum::INFO,
|
|
244
|
+
"MEG_WINNER_CAMPAIGN",
|
|
245
|
+
{
|
|
246
|
+
campaignKey: campaign_key,
|
|
247
|
+
groupId: group_id,
|
|
248
|
+
userId: context.id,
|
|
249
|
+
algo: 'using random algorithm'
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
begin
|
|
254
|
+
StorageDecorator.new.set_data_in_storage(
|
|
255
|
+
{
|
|
256
|
+
feature_key: "#{Constants::VWO_META_MEG_KEY}#{group_id}",
|
|
257
|
+
context: context,
|
|
258
|
+
experiment_id: winner_campaign.id,
|
|
259
|
+
experiment_key: winner_campaign.key,
|
|
260
|
+
experiment_variation_id: winner_campaign.type == CampaignTypeEnum::PERSONALIZE ? winner_campaign.variations[0].id : -1
|
|
261
|
+
},
|
|
262
|
+
storage_service
|
|
263
|
+
)
|
|
264
|
+
rescue StandardError => e
|
|
265
|
+
LoggerService.log(LogLevelEnum::ERROR, "ERROR_STORING_DATA_IN_STORAGE", { err: e.message, an: ApiEnum::GET_FLAG, sId: context.get_session_id, uuid: context.get_uuid})
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
return winner_campaign if called_campaign_ids.include?(winner_campaign.id)
|
|
269
|
+
else
|
|
270
|
+
LoggerService.log(LogLevelEnum::INFO, "No winner campaign found for MEG group: #{group_id}, using random algorithm", nil)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
nil
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Advanced algorithm for campaign selection
|
|
277
|
+
# @param settings [SettingsModel] The settings for the VWO instance
|
|
278
|
+
# @param shortlisted_campaigns [Array] The list of shortlisted campaigns
|
|
279
|
+
# @param context [ContextModel] The context for the evaluation
|
|
280
|
+
# @param called_campaign_ids [Array] The list of called campaign IDs
|
|
281
|
+
# @param group_id [String] The ID of the group
|
|
282
|
+
# @param storage_service [StorageService] The storage service for the evaluation
|
|
283
|
+
def get_campaign_using_advanced_algo(settings, shortlisted_campaigns, context, called_campaign_ids, group_id, storage_service)
|
|
284
|
+
winner_campaign = nil
|
|
285
|
+
found = false
|
|
286
|
+
priority_order = settings.get_groups[group_id.to_s][:p.to_s] || []
|
|
287
|
+
weights = settings.get_groups[group_id.to_s][:wt.to_s] || {}
|
|
288
|
+
|
|
289
|
+
# Check priority order first
|
|
290
|
+
priority_order.each do |priority|
|
|
291
|
+
shortlisted_campaigns.each do |campaign|
|
|
292
|
+
if campaign.id.to_s == priority.to_s || "#{campaign.id}_#{campaign.variations[0].id}" == priority
|
|
293
|
+
winner_campaign = campaign.clone
|
|
294
|
+
found = true
|
|
295
|
+
break
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
break if found
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# If no winner found through priority, try weighted distribution
|
|
302
|
+
if winner_campaign.nil?
|
|
303
|
+
participating_campaign_list = shortlisted_campaigns.map do |campaign|
|
|
304
|
+
campaign_id = campaign.id.to_s
|
|
305
|
+
weight = weights[campaign_id] || weights["#{campaign_id}_#{campaign.variations[0].id}"]
|
|
306
|
+
next nil unless weight
|
|
307
|
+
|
|
308
|
+
cloned_campaign = campaign.clone
|
|
309
|
+
variation = VariationModel.new.model_from_dictionary(cloned_campaign)
|
|
310
|
+
variation.weight = weight
|
|
311
|
+
variation
|
|
312
|
+
end.compact # Remove nil values
|
|
313
|
+
|
|
314
|
+
# Convert to VariationModel and set allocations
|
|
315
|
+
CampaignUtil.set_campaign_allocation(participating_campaign_list)
|
|
316
|
+
winner_campaign = CampaignDecisionService.new.get_variation(
|
|
317
|
+
participating_campaign_list,
|
|
318
|
+
DecisionMaker.new.calculate_bucket_value(CampaignUtil.get_bucketing_seed(context.get_bucketing_seed || context.get_id, nil, group_id))
|
|
319
|
+
)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
if winner_campaign
|
|
323
|
+
campaign_key = winner_campaign.type == CampaignTypeEnum::AB ?
|
|
324
|
+
winner_campaign.key :
|
|
325
|
+
"#{winner_campaign.key}_#{winner_campaign.rule_key}"
|
|
326
|
+
|
|
327
|
+
LoggerService.log(
|
|
328
|
+
LogLevelEnum::INFO,
|
|
329
|
+
"MEG_WINNER_CAMPAIGN",
|
|
330
|
+
{
|
|
331
|
+
campaignKey: campaign_key,
|
|
332
|
+
groupId: group_id,
|
|
333
|
+
userId: context.id,
|
|
334
|
+
algo: 'using advanced algorithm'
|
|
335
|
+
}
|
|
336
|
+
)
|
|
337
|
+
begin
|
|
338
|
+
StorageDecorator.new.set_data_in_storage(
|
|
339
|
+
{
|
|
340
|
+
feature_key: "#{Constants::VWO_META_MEG_KEY}#{group_id}",
|
|
341
|
+
context: context,
|
|
342
|
+
experiment_id: winner_campaign.id,
|
|
343
|
+
experiment_key: winner_campaign.key,
|
|
344
|
+
experiment_variation_id: winner_campaign.type == CampaignTypeEnum::PERSONALIZE ? winner_campaign.variations[0].id : -1
|
|
345
|
+
},
|
|
346
|
+
storage_service
|
|
347
|
+
)
|
|
348
|
+
rescue StandardError => e
|
|
349
|
+
LoggerService.log(LogLevelEnum::ERROR, "ERROR_STORING_DATA_IN_STORAGE", { err: e.message, an: ApiEnum::GET_FLAG, sId: context.get_session_id, uuid: context.get_uuid})
|
|
350
|
+
end
|
|
351
|
+
return winner_campaign if called_campaign_ids.include?(winner_campaign.id)
|
|
352
|
+
else
|
|
353
|
+
LoggerService.log(LogLevelEnum::INFO, "No winner campaign found for MEG group: #{group_id}, using advanced algorithm", nil)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
nil
|
|
357
|
+
end
|