|
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