@@ -44,7 +44,7 @@ class Decision(NamedTuple):
4444 experiment : Optional [entities .Experiment ]
4545 variation : Optional [entities .Variation ]
4646 source : Optional [str ]
47- # cmab_uuid: Optional[str]
47+ cmab_uuid : Optional [str ]
4848
4949
5050class DecisionService :
@@ -58,6 +58,7 @@ def __init__(self,
5858 self .logger = logger
5959 self .user_profile_service = user_profile_service
6060 self .cmab_service = cmab_service
61+ self .cmab_uuid = None
6162
6263 # Map of user IDs to another map of experiments to variations.
6364 # This contains all the forced variations set by the user
@@ -305,7 +306,7 @@ def get_variation(
305306 user_profile_tracker : Optional [UserProfileTracker ],
306307 reasons : list [str ] = [],
307308 options : Optional [Sequence [str ]] = None
308- ) -> tuple [Optional [entities .Variation ], list [str ]]:
309+ ) -> tuple [Optional [entities .Variation ], list [str ], Optional [ str ] ]:
309310 """ Top-level function to help determine variation user should be put in.
310311
311312 First, check if experiment is running.
@@ -323,11 +324,12 @@ def get_variation(
323324 options: Decide options.
324325
325326 Returns:
326- Variation user should see. None if user is not in experiment or experiment is not running
327- And an array of log messages representing decision making.
327+ Variation user should see. None if user is not in experiment or experiment is not running,
328+ an array of log messages representing decision making
329+ and a cmab_uuid if experiment is cmab-experiment
328330 """
329331 user_id = user_context .user_id
330-
332+ cmab_uuid = None
331333 if options :
332334 ignore_user_profile = OptimizelyDecideOption .IGNORE_USER_PROFILE_SERVICE in options
333335 else :
@@ -341,20 +343,20 @@ def get_variation(
341343 message = f'Experiment "{ experiment .key } " is not running.'
342344 self .logger .info (message )
343345 decide_reasons .append (message )
344- return None , decide_reasons
346+ return None , decide_reasons , cmab_uuid
345347
346348 # Check if the user is forced into a variation
347349 variation : Optional [entities .Variation ]
348350 variation , reasons_received = self .get_forced_variation (project_config , experiment .key , user_id )
349351 decide_reasons += reasons_received
350352 if variation :
351- return variation , decide_reasons
353+ return variation , decide_reasons , cmab_uuid
352354
353355 # Check to see if user is white-listed for a certain variation
354356 variation , reasons_received = self .get_whitelisted_variation (project_config , experiment , user_id )
355357 decide_reasons += reasons_received
356358 if variation :
357- return variation , decide_reasons
359+ return variation , decide_reasons , cmab_uuid
358360
359361 # Check to see if user has a decision available for the given experiment
360362 if user_profile_tracker is not None and not ignore_user_profile :
@@ -364,7 +366,7 @@ def get_variation(
364366 f'"{ experiment } " for user "{ user_id } " from user profile.'
365367 self .logger .info (message )
366368 decide_reasons .append (message )
367- return variation , decide_reasons
369+ return variation , decide_reasons , cmab_uuid
368370 else :
369371 self .logger .warning ('User profile has invalid format.' )
370372
@@ -380,7 +382,7 @@ def get_variation(
380382 message = f'User "{ user_id } " does not meet conditions to be in experiment "{ experiment .key } ".'
381383 self .logger .info (message )
382384 decide_reasons .append (message )
383- return None , decide_reasons
385+ return None , decide_reasons , cmab_uuid
384386
385387 # Determine bucketing ID to be used
386388 bucketing_id , bucketing_id_reasons = self ._get_bucketing_id (user_id , user_context .get_user_attributes ())
@@ -403,7 +405,7 @@ def get_variation(
403405 message = f'User "{ user_id } " not in CMAB experiment "{ experiment .key } " due to traffic allocation.'
404406 self .logger .info (message )
405407 decide_reasons .append (message )
406- return None , decide_reasons
408+ return None , decide_reasons , cmab_uuid
407409
408410 # User is in CMAB allocation, proceed to CMAB decision
409411 decision_variation_value = self ._get_decision_for_cmab_experiment (project_config ,
@@ -413,8 +415,9 @@ def get_variation(
413415 decide_reasons += decision_variation_value .get ('reasons' , [])
414416 cmab_decision = decision_variation_value .get ('result' )
415417 if not cmab_decision :
416- return None , decide_reasons
418+ return None , decide_reasons , cmab_uuid
417419 variation_id = cmab_decision ['variation_id' ]
420+ cmab_uuid = cmab_decision ['cmab_uuid' ]
418421 variation = project_config .get_variation_from_id (experiment_key = experiment .key , variation_id = variation_id )
419422 else :
420423 # Bucket the user
@@ -431,11 +434,11 @@ def get_variation(
431434 user_profile_tracker .update_user_profile (experiment , variation )
432435 except :
433436 self .logger .exception (f'Unable to save user profile for user "{ user_id } ".' )
434- return variation , decide_reasons
437+ return variation , decide_reasons , cmab_uuid
435438 message = f'User "{ user_id } " is in no variation.'
436439 self .logger .info (message )
437440 decide_reasons .append (message )
438- return None , decide_reasons
441+ return None , decide_reasons , cmab_uuid
439442
440443 def get_variation_for_rollout (
441444 self , project_config : ProjectConfig , feature : entities .FeatureFlag , user_context : OptimizelyUserContext
@@ -459,23 +462,23 @@ def get_variation_for_rollout(
459462 attributes = user_context .get_user_attributes ()
460463
461464 if not feature or not feature .rolloutId :
462- return Decision (None , None , enums .DecisionSources .ROLLOUT ), decide_reasons
465+ return Decision (None , None , enums .DecisionSources .ROLLOUT , None ), decide_reasons
463466
464467 rollout = project_config .get_rollout_from_id (feature .rolloutId )
465468
466469 if not rollout :
467470 message = f'There is no rollout of feature { feature .key } .'
468471 self .logger .debug (message )
469472 decide_reasons .append (message )
470- return Decision (None , None , enums .DecisionSources .ROLLOUT ), decide_reasons
473+ return Decision (None , None , enums .DecisionSources .ROLLOUT , None ), decide_reasons
471474
472475 rollout_rules = project_config .get_rollout_experiments (rollout )
473476
474477 if not rollout_rules :
475478 message = f'Rollout { rollout .id } has no experiments.'
476479 self .logger .debug (message )
477480 decide_reasons .append (message )
478- return Decision (None , None , enums .DecisionSources .ROLLOUT ), decide_reasons
481+ return Decision (None , None , enums .DecisionSources .ROLLOUT , None ), decide_reasons
479482
480483 index = 0
481484 while index < len (rollout_rules ):
@@ -490,7 +493,7 @@ def get_variation_for_rollout(
490493
491494 if forced_decision_variation :
492495 return Decision (experiment = rule , variation = forced_decision_variation ,
493- source = enums .DecisionSources .ROLLOUT ), decide_reasons
496+ source = enums .DecisionSources .ROLLOUT , cmab_uuid = None ), decide_reasons
494497
495498 bucketing_id , bucket_reasons = self ._get_bucketing_id (user_id , attributes )
496499 decide_reasons += bucket_reasons
@@ -524,7 +527,7 @@ def get_variation_for_rollout(
524527 self .logger .debug (message )
525528 decide_reasons .append (message )
526529 return Decision (experiment = rule , variation = bucketed_variation ,
527- source = enums .DecisionSources .ROLLOUT ), decide_reasons
530+ source = enums .DecisionSources .ROLLOUT , cmab_uuid = None ), decide_reasons
528531
529532 elif not everyone_else :
530533 # skip this logging for EveryoneElse since this has a message not for everyone_else
@@ -544,7 +547,7 @@ def get_variation_for_rollout(
544547 # the last rule is special for "Everyone Else"
545548 index = len (rollout_rules ) - 1 if skip_to_everyone_else else index + 1
546549
547- return Decision (None , None , enums .DecisionSources .ROLLOUT ), decide_reasons
550+ return Decision (None , None , enums .DecisionSources .ROLLOUT , None ), decide_reasons
548551
549552 def get_variation_for_feature (
550553 self ,
@@ -680,8 +683,9 @@ def get_variations_for_feature_list(
680683
681684 if forced_decision_variation :
682685 decision_variation = forced_decision_variation
686+ cmab_uuid = None
683687 else :
684- decision_variation , variation_reasons = self .get_variation (
688+ decision_variation , variation_reasons , cmab_uuid = self .get_variation (
685689 project_config , experiment , user_context , user_profile_tracker , feature_reasons , options
686690 )
687691 feature_reasons .extend (variation_reasons )
@@ -691,7 +695,8 @@ def get_variations_for_feature_list(
691695 f'User "{ user_context .user_id } " '
692696 f'bucketed into experiment "{ experiment .key } " of feature "{ feature .key } ".'
693697 )
694- decision = Decision (experiment , decision_variation , enums .DecisionSources .FEATURE_TEST )
698+ decision = Decision (experiment , decision_variation ,
699+ enums .DecisionSources .FEATURE_TEST , cmab_uuid )
695700 decisions .append ((decision , feature_reasons ))
696701 experiment_decision_found = True # Mark that a decision was found
697702 break # Stop after the first successful experiment decision
0 commit comments