|
28 | 28 | if TYPE_CHECKING:
|
29 | 29 | # prevent circular dependenacy by skipping import at runtime
|
30 | 30 | from .project_config import ProjectConfig
|
31 |
| - from .entities import Experiment, Variation, Group |
| 31 | + from .entities import Experiment, Variation |
32 | 32 | from .helpers.types import TrafficAllocation
|
33 | 33 |
|
34 | 34 |
|
@@ -119,6 +119,34 @@ def bucket(
|
119 | 119 | and array of log messages representing decision making.
|
120 | 120 | */.
|
121 | 121 | """
|
| 122 | + variation_id, decide_reasons = self.bucket_to_entity_id(project_config, experiment, user_id, bucketing_id) |
| 123 | + if variation_id: |
| 124 | + variation = project_config.get_variation_from_id_by_experiment_id(experiment.id, variation_id) |
| 125 | + return variation, decide_reasons |
| 126 | + |
| 127 | + elif not decide_reasons: |
| 128 | + message = 'Bucketed into an empty traffic range. Returning nil.' |
| 129 | + project_config.logger.info(message) |
| 130 | + decide_reasons.append(message) |
| 131 | + |
| 132 | + return None, decide_reasons |
| 133 | + |
| 134 | + def bucket_to_entity_id( |
| 135 | + self, project_config: ProjectConfig, |
| 136 | + experiment: Experiment, user_id: str, bucketing_id: str |
| 137 | + ) -> tuple[Optional[str], list[str]]: |
| 138 | + """ |
| 139 | + For a given experiment and bucketing ID determines variation ID to be shown to user. |
| 140 | +
|
| 141 | + Args: |
| 142 | + project_config: Instance of ProjectConfig. |
| 143 | + experiment: The experiment object (used for group/groupPolicy logic if needed). |
| 144 | + user_id: The user ID string. |
| 145 | + bucketing_id: The bucketing ID string for the user. |
| 146 | +
|
| 147 | + Returns: |
| 148 | + Tuple of (entity_id or None, list of decide reasons). |
| 149 | + """ |
122 | 150 | decide_reasons: list[str] = []
|
123 | 151 | if not experiment:
|
124 | 152 | return None, decide_reasons
|
@@ -154,77 +182,5 @@ def bucket(
|
154 | 182 | # Bucket user if not in white-list and in group (if any)
|
155 | 183 | variation_id = self.find_bucket(project_config, bucketing_id,
|
156 | 184 | experiment.id, experiment.trafficAllocation)
|
157 |
| - if variation_id: |
158 |
| - variation = project_config.get_variation_from_id_by_experiment_id(experiment.id, variation_id) |
159 |
| - return variation, decide_reasons |
160 |
| - |
161 |
| - else: |
162 |
| - message = 'Bucketed into an empty traffic range. Returning nil.' |
163 |
| - project_config.logger.info(message) |
164 |
| - decide_reasons.append(message) |
165 |
| - |
166 |
| - return None, decide_reasons |
167 |
| - |
168 |
| - def bucket_to_entity_id( |
169 |
| - self, |
170 |
| - bucketing_id: str, |
171 |
| - experiment: Experiment, |
172 |
| - traffic_allocations: list[TrafficAllocation], |
173 |
| - group: Optional[Group] = None |
174 |
| - ) -> tuple[Optional[str], list[str]]: |
175 |
| - """ |
176 |
| - Buckets the user and returns the entity ID (for CMAB experiments). |
177 |
| - Args: |
178 |
| - bucketing_id: The bucketing ID string for the user. |
179 |
| - experiment: The experiment object (for group/groupPolicy logic if needed). |
180 |
| - traffic_allocations: List of traffic allocation dicts (should have 'entity_id' and 'end_of_range' keys). |
181 |
| - group: (optional) Group object for mutex group support. |
182 | 185 |
|
183 |
| - Returns: |
184 |
| - Tuple of (entity_id or None, list of decide reasons). |
185 |
| - """ |
186 |
| - decide_reasons = [] |
187 |
| - |
188 |
| - group_id = getattr(experiment, 'groupId', None) |
189 |
| - if group_id and group and getattr(group, 'policy', None) == 'random': |
190 |
| - bucket_key = bucketing_id + group_id |
191 |
| - bucket_val = self._generate_bucket_value(bucket_key) |
192 |
| - decide_reasons.append(f'Generated group bucket value {bucket_val} for key "{bucket_key}".') |
193 |
| - |
194 |
| - matched = False |
195 |
| - for allocation in group.trafficAllocation: |
196 |
| - end_of_range = allocation['endOfRange'] |
197 |
| - entity_id = allocation['entityId'] |
198 |
| - if bucket_val < end_of_range: |
199 |
| - matched = True |
200 |
| - if entity_id != experiment.id: |
201 |
| - decide_reasons.append( |
202 |
| - f'User not bucketed into experiment "{experiment.id}" (got "{entity_id}").' |
203 |
| - ) |
204 |
| - return None, decide_reasons |
205 |
| - decide_reasons.append( |
206 |
| - f'User is bucketed into experiment "{experiment.id}" within group "{group_id}".' |
207 |
| - ) |
208 |
| - break |
209 |
| - if not matched: |
210 |
| - decide_reasons.append( |
211 |
| - f'User not bucketed into any experiment in group "{group_id}".' |
212 |
| - ) |
213 |
| - return None, decide_reasons |
214 |
| - |
215 |
| - # Main experiment bucketing |
216 |
| - bucket_key = bucketing_id + experiment.id |
217 |
| - bucket_val = self._generate_bucket_value(bucket_key) |
218 |
| - decide_reasons.append(f'Generated experiment bucket value {bucket_val} for key "{bucket_key}".') |
219 |
| - |
220 |
| - for allocation in traffic_allocations: |
221 |
| - end_of_range = allocation['endOfRange'] |
222 |
| - entity_id = allocation['entityId'] |
223 |
| - if bucket_val < end_of_range: |
224 |
| - decide_reasons.append( |
225 |
| - f'User bucketed into entity id "{entity_id}".' |
226 |
| - ) |
227 |
| - return entity_id, decide_reasons |
228 |
| - |
229 |
| - decide_reasons.append('User not bucketed into any entity id.') |
230 |
| - return None, decide_reasons |
| 186 | + return variation_id, decide_reasons |
0 commit comments