@@ -44,7 +44,7 @@ class Decision(NamedTuple):
44
44
experiment : Optional [entities .Experiment ]
45
45
variation : Optional [entities .Variation ]
46
46
source : Optional [str ]
47
- # cmab_uuid: Optional[str]
47
+ cmab_uuid : Optional [str ]
48
48
49
49
50
50
class DecisionService :
@@ -58,6 +58,7 @@ def __init__(self,
58
58
self .logger = logger
59
59
self .user_profile_service = user_profile_service
60
60
self .cmab_service = cmab_service
61
+ self .cmab_uuid = None
61
62
62
63
# Map of user IDs to another map of experiments to variations.
63
64
# This contains all the forced variations set by the user
@@ -305,7 +306,7 @@ def get_variation(
305
306
user_profile_tracker : Optional [UserProfileTracker ],
306
307
reasons : list [str ] = [],
307
308
options : Optional [Sequence [str ]] = None
308
- ) -> tuple [Optional [entities .Variation ], list [str ]]:
309
+ ) -> tuple [Optional [entities .Variation ], list [str ], Optional [ str ] ]:
309
310
""" Top-level function to help determine variation user should be put in.
310
311
311
312
First, check if experiment is running.
@@ -323,11 +324,12 @@ def get_variation(
323
324
options: Decide options.
324
325
325
326
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
328
330
"""
329
331
user_id = user_context .user_id
330
-
332
+ cmab_uuid = None
331
333
if options :
332
334
ignore_user_profile = OptimizelyDecideOption .IGNORE_USER_PROFILE_SERVICE in options
333
335
else :
@@ -341,20 +343,20 @@ def get_variation(
341
343
message = f'Experiment "{ experiment .key } " is not running.'
342
344
self .logger .info (message )
343
345
decide_reasons .append (message )
344
- return None , decide_reasons
346
+ return None , decide_reasons , cmab_uuid
345
347
346
348
# Check if the user is forced into a variation
347
349
variation : Optional [entities .Variation ]
348
350
variation , reasons_received = self .get_forced_variation (project_config , experiment .key , user_id )
349
351
decide_reasons += reasons_received
350
352
if variation :
351
- return variation , decide_reasons
353
+ return variation , decide_reasons , cmab_uuid
352
354
353
355
# Check to see if user is white-listed for a certain variation
354
356
variation , reasons_received = self .get_whitelisted_variation (project_config , experiment , user_id )
355
357
decide_reasons += reasons_received
356
358
if variation :
357
- return variation , decide_reasons
359
+ return variation , decide_reasons , cmab_uuid
358
360
359
361
# Check to see if user has a decision available for the given experiment
360
362
if user_profile_tracker is not None and not ignore_user_profile :
@@ -364,7 +366,7 @@ def get_variation(
364
366
f'"{ experiment } " for user "{ user_id } " from user profile.'
365
367
self .logger .info (message )
366
368
decide_reasons .append (message )
367
- return variation , decide_reasons
369
+ return variation , decide_reasons , cmab_uuid
368
370
else :
369
371
self .logger .warning ('User profile has invalid format.' )
370
372
@@ -380,7 +382,7 @@ def get_variation(
380
382
message = f'User "{ user_id } " does not meet conditions to be in experiment "{ experiment .key } ".'
381
383
self .logger .info (message )
382
384
decide_reasons .append (message )
383
- return None , decide_reasons
385
+ return None , decide_reasons , cmab_uuid
384
386
385
387
# Determine bucketing ID to be used
386
388
bucketing_id , bucketing_id_reasons = self ._get_bucketing_id (user_id , user_context .get_user_attributes ())
@@ -403,7 +405,7 @@ def get_variation(
403
405
message = f'User "{ user_id } " not in CMAB experiment "{ experiment .key } " due to traffic allocation.'
404
406
self .logger .info (message )
405
407
decide_reasons .append (message )
406
- return None , decide_reasons
408
+ return None , decide_reasons , cmab_uuid
407
409
408
410
# User is in CMAB allocation, proceed to CMAB decision
409
411
decision_variation_value = self ._get_decision_for_cmab_experiment (project_config ,
@@ -413,8 +415,9 @@ def get_variation(
413
415
decide_reasons += decision_variation_value .get ('reasons' , [])
414
416
cmab_decision = decision_variation_value .get ('result' )
415
417
if not cmab_decision :
416
- return None , decide_reasons
418
+ return None , decide_reasons , cmab_uuid
417
419
variation_id = cmab_decision ['variation_id' ]
420
+ cmab_uuid = cmab_decision ['cmab_uuid' ]
418
421
variation = project_config .get_variation_from_id (experiment_key = experiment .key , variation_id = variation_id )
419
422
else :
420
423
# Bucket the user
@@ -431,11 +434,11 @@ def get_variation(
431
434
user_profile_tracker .update_user_profile (experiment , variation )
432
435
except :
433
436
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
435
438
message = f'User "{ user_id } " is in no variation.'
436
439
self .logger .info (message )
437
440
decide_reasons .append (message )
438
- return None , decide_reasons
441
+ return None , decide_reasons , cmab_uuid
439
442
440
443
def get_variation_for_rollout (
441
444
self , project_config : ProjectConfig , feature : entities .FeatureFlag , user_context : OptimizelyUserContext
@@ -459,23 +462,23 @@ def get_variation_for_rollout(
459
462
attributes = user_context .get_user_attributes ()
460
463
461
464
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
463
466
464
467
rollout = project_config .get_rollout_from_id (feature .rolloutId )
465
468
466
469
if not rollout :
467
470
message = f'There is no rollout of feature { feature .key } .'
468
471
self .logger .debug (message )
469
472
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
471
474
472
475
rollout_rules = project_config .get_rollout_experiments (rollout )
473
476
474
477
if not rollout_rules :
475
478
message = f'Rollout { rollout .id } has no experiments.'
476
479
self .logger .debug (message )
477
480
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
479
482
480
483
index = 0
481
484
while index < len (rollout_rules ):
@@ -490,7 +493,7 @@ def get_variation_for_rollout(
490
493
491
494
if forced_decision_variation :
492
495
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
494
497
495
498
bucketing_id , bucket_reasons = self ._get_bucketing_id (user_id , attributes )
496
499
decide_reasons += bucket_reasons
@@ -524,7 +527,7 @@ def get_variation_for_rollout(
524
527
self .logger .debug (message )
525
528
decide_reasons .append (message )
526
529
return Decision (experiment = rule , variation = bucketed_variation ,
527
- source = enums .DecisionSources .ROLLOUT ), decide_reasons
530
+ source = enums .DecisionSources .ROLLOUT , cmab_uuid = None ), decide_reasons
528
531
529
532
elif not everyone_else :
530
533
# skip this logging for EveryoneElse since this has a message not for everyone_else
@@ -544,7 +547,7 @@ def get_variation_for_rollout(
544
547
# the last rule is special for "Everyone Else"
545
548
index = len (rollout_rules ) - 1 if skip_to_everyone_else else index + 1
546
549
547
- return Decision (None , None , enums .DecisionSources .ROLLOUT ), decide_reasons
550
+ return Decision (None , None , enums .DecisionSources .ROLLOUT , None ), decide_reasons
548
551
549
552
def get_variation_for_feature (
550
553
self ,
@@ -680,8 +683,9 @@ def get_variations_for_feature_list(
680
683
681
684
if forced_decision_variation :
682
685
decision_variation = forced_decision_variation
686
+ cmab_uuid = None
683
687
else :
684
- decision_variation , variation_reasons = self .get_variation (
688
+ decision_variation , variation_reasons , cmab_uuid = self .get_variation (
685
689
project_config , experiment , user_context , user_profile_tracker , feature_reasons , options
686
690
)
687
691
feature_reasons .extend (variation_reasons )
@@ -691,7 +695,8 @@ def get_variations_for_feature_list(
691
695
f'User "{ user_context .user_id } " '
692
696
f'bucketed into experiment "{ experiment .key } " of feature "{ feature .key } ".'
693
697
)
694
- decision = Decision (experiment , decision_variation , enums .DecisionSources .FEATURE_TEST )
698
+ decision = Decision (experiment , decision_variation ,
699
+ enums .DecisionSources .FEATURE_TEST , cmab_uuid )
695
700
decisions .append ((decision , feature_reasons ))
696
701
experiment_decision_found = True # Mark that a decision was found
697
702
break # Stop after the first successful experiment decision
0 commit comments