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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/lib/resources/debug_messages.json +21 -0
  3. data/lib/resources/error_messages.json +63 -0
  4. data/lib/resources/info_messages.json +43 -0
  5. data/lib/resources/warn_messages.json +1 -0
  6. data/lib/vwo.rb +40 -0
  7. data/lib/wingify/api/get_flag.rb +244 -0
  8. data/lib/wingify/api/set_attribute.rb +57 -0
  9. data/lib/wingify/api/track_event.rb +80 -0
  10. data/lib/wingify/constants/constants.rb +106 -0
  11. data/lib/wingify/decorators/storage_decorator.rb +82 -0
  12. data/lib/wingify/enums/api_enum.rb +23 -0
  13. data/lib/wingify/enums/campaign_type_enum.rb +19 -0
  14. data/lib/wingify/enums/debug_category_enum.rb +20 -0
  15. data/lib/wingify/enums/decision_types_enum.rb +18 -0
  16. data/lib/wingify/enums/event_enum.rb +22 -0
  17. data/lib/wingify/enums/headers_enum.rb +20 -0
  18. data/lib/wingify/enums/hooks_enum.rb +17 -0
  19. data/lib/wingify/enums/http_method_enum.rb +18 -0
  20. data/lib/wingify/enums/log_level_enum.rb +21 -0
  21. data/lib/wingify/enums/log_level_to_number.rb +27 -0
  22. data/lib/wingify/enums/status_enum.rb +19 -0
  23. data/lib/wingify/enums/storage_enum.rb +22 -0
  24. data/lib/wingify/enums/url_enum.rb +22 -0
  25. data/lib/wingify/models/campaign/campaign_model.rb +192 -0
  26. data/lib/wingify/models/campaign/feature_model.rb +111 -0
  27. data/lib/wingify/models/campaign/impact_campaign_model.rb +38 -0
  28. data/lib/wingify/models/campaign/metric_model.rb +44 -0
  29. data/lib/wingify/models/campaign/rule_model.rb +56 -0
  30. data/lib/wingify/models/campaign/variable_model.rb +51 -0
  31. data/lib/wingify/models/campaign/variation_model.rb +137 -0
  32. data/lib/wingify/models/gateway_service_model.rb +39 -0
  33. data/lib/wingify/models/schemas/settings_schema_validation.rb +104 -0
  34. data/lib/wingify/models/settings/settings_model.rb +101 -0
  35. data/lib/wingify/models/storage/storage_data_model.rb +44 -0
  36. data/lib/wingify/models/user/context_model.rb +154 -0
  37. data/lib/wingify/models/user/context_vwo_model.rb +38 -0
  38. data/lib/wingify/models/user/get_flag_response.rb +50 -0
  39. data/lib/wingify/models/vwo_options_model.rb +129 -0
  40. data/lib/wingify/packages/decision_maker/decision_maker.rb +60 -0
  41. data/lib/wingify/packages/logger/core/log_manager.rb +92 -0
  42. data/lib/wingify/packages/logger/core/transport_manager.rb +76 -0
  43. data/lib/wingify/packages/logger/log_message_builder.rb +70 -0
  44. data/lib/wingify/packages/logger/logger.rb +38 -0
  45. data/lib/wingify/packages/logger/transports/console_transport.rb +49 -0
  46. data/lib/wingify/packages/network_layer/client/network_client.rb +276 -0
  47. data/lib/wingify/packages/network_layer/handlers/request_handler.rb +37 -0
  48. data/lib/wingify/packages/network_layer/manager/network_manager.rb +145 -0
  49. data/lib/wingify/packages/network_layer/models/global_request_model.rb +105 -0
  50. data/lib/wingify/packages/network_layer/models/request_model.rb +179 -0
  51. data/lib/wingify/packages/network_layer/models/response_model.rb +62 -0
  52. data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +77 -0
  53. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
  54. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
  55. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +33 -0
  56. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +218 -0
  57. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +415 -0
  58. data/lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
  59. data/lib/wingify/packages/storage/connector.rb +26 -0
  60. data/lib/wingify/packages/storage/storage.rb +47 -0
  61. data/lib/wingify/services/batch_event_queue.rb +179 -0
  62. data/lib/wingify/services/campaign_decision_service.rb +161 -0
  63. data/lib/wingify/services/hooks_service.rb +51 -0
  64. data/lib/wingify/services/logger_service.rb +114 -0
  65. data/lib/wingify/services/settings_service.rb +178 -0
  66. data/lib/wingify/services/storage_service.rb +66 -0
  67. data/lib/wingify/utils/batch_event_dispatcher_util.rb +178 -0
  68. data/lib/wingify/utils/brand_context.rb +33 -0
  69. data/lib/wingify/utils/brand_util.rb +53 -0
  70. data/lib/wingify/utils/campaign_util.rb +284 -0
  71. data/lib/wingify/utils/data_type_util.rb +105 -0
  72. data/lib/wingify/utils/debugger_service_util.rb +40 -0
  73. data/lib/wingify/utils/decision_util.rb +259 -0
  74. data/lib/wingify/utils/event_util.rb +55 -0
  75. data/lib/wingify/utils/function_util.rb +141 -0
  76. data/lib/wingify/utils/gateway_service_util.rb +101 -0
  77. data/lib/wingify/utils/impression_util.rb +66 -0
  78. data/lib/wingify/utils/log_message_util.rb +42 -0
  79. data/lib/wingify/utils/meg_util.rb +357 -0
  80. data/lib/wingify/utils/network_util.rb +503 -0
  81. data/lib/wingify/utils/rule_evaluation_util.rb +57 -0
  82. data/lib/wingify/utils/settings_util.rb +38 -0
  83. data/lib/wingify/utils/usage_stats_util.rb +119 -0
  84. data/lib/wingify/utils/uuid_util.rb +96 -0
  85. data/lib/wingify/wingify_builder.rb +261 -0
  86. data/lib/wingify/wingify_client.rb +227 -0
  87. data/lib/wingify.rb +117 -0
  88. 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