models.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. from typing import List, Dict, Optional, Set
  2. import json
  3. from attr import dataclass
  4. import hashlib
  5. class DiversionBucket:
  6. def match(self, experiment_context):
  7. raise NotImplementedError("Subclasses must implement this method")
  8. class UidDiversionBucket(DiversionBucket):
  9. def __init__(self, total_buckets: int, buckets: str):
  10. self.total_buckets = total_buckets
  11. self.buckets = set(map(int, buckets.split(",")))
  12. def match(self, experiment_context):
  13. uid_hash = int(hashlib.md5(experiment_context.uid.encode()).hexdigest(), 16)
  14. bucket = uid_hash % self.total_buckets
  15. return bucket in self.buckets
  16. class FilterDiversionBucket(DiversionBucket):
  17. def __init__(self, filter_condition: str):
  18. self.filter_condition = filter_condition
  19. def match(self, experiment_context):
  20. raise NotImplementedError("not implemented")
  21. class Feature:
  22. def __init__(self, params=None):
  23. self.params = params
  24. def init(self):
  25. # Initialize feature-specific logic
  26. pass
  27. class ExperimentContext:
  28. def __init__(self, uid=None, filter_params=None):
  29. self.uid = uid
  30. self.filter_params = filter_params or {}
  31. def __str__(self):
  32. return f"ExperimentContext(uid={self.uid}, filter_params={self.filter_params})"
  33. class Domain:
  34. def __init__(self, domain_id, name, flow: int, buckets: str, bucket_type: str, debug_crowd_ids=None, is_default_domain=False, exp_layer_id=None,
  35. debug_users=""):
  36. self.id = domain_id
  37. self.name = name
  38. self.debug_crowd_ids = debug_crowd_ids
  39. self.is_default_domain = is_default_domain
  40. self.exp_layer_id = exp_layer_id
  41. self.features = []
  42. self.layers = []
  43. self.debug_users = debug_users
  44. self.flow = flow
  45. self.buckets = buckets
  46. self.diversion_bucket = None
  47. self.bucket_type = bucket_type
  48. self.debug_user_set = set()
  49. def add_debug_users(self, users: List[str]):
  50. self.debug_user_set.update(users)
  51. def match_debug_users(self, experiment_context):
  52. return experiment_context.uid in self.debug_user_set
  53. def add_feature(self, feature: Feature):
  54. self.features.append(feature)
  55. def add_layer(self, layer):
  56. self.layers.append(layer)
  57. def init(self):
  58. self.debug_user_set.update(self.debug_users.split(","))
  59. self.diversion_bucket = UidDiversionBucket(100, self.buckets)
  60. def match(self, experiment_context):
  61. if self.flow == 0:
  62. return False
  63. elif self.flow == 100:
  64. return True
  65. if self.diversion_bucket:
  66. return self.diversion_bucket.match(experiment_context)
  67. return False
  68. class Layer:
  69. id: int
  70. name: str
  71. experiments: List['Experiment']
  72. domains: List[Domain]
  73. def __init__(self, layer_id, name):
  74. self.id = layer_id
  75. self.name = name
  76. self.experiments = []
  77. self.domains = []
  78. def add_experiment(self, experiment):
  79. self.experiments.append(experiment)
  80. def add_domain(self, domain):
  81. self.domains.append(domain)
  82. @dataclass
  83. class Experiment:
  84. id: int
  85. flow: int
  86. crowd_ids: List[str]
  87. debug_users: str
  88. buckets: str
  89. filter_condition: str
  90. bucket_type: str = "Random"
  91. debug_user_set: Set[str] = set()
  92. diversion_bucket: Optional[DiversionBucket] = None
  93. experiment_versions: List['ExperimentVersion'] = []
  94. def add_debug_users(self, users: List[str]):
  95. self.debug_user_set.update(users)
  96. def match_debug_users(self, experiment_context):
  97. return experiment_context.uid in self.debug_user_set
  98. def add_experiment_version(self, version):
  99. self.experiment_versions.append(version)
  100. def match(self, experiment_context: ExperimentContext) -> bool:
  101. if self.bucket_type == "Random":
  102. if self.flow == 0:
  103. return False
  104. elif self.flow == 100:
  105. return True
  106. if self.diversion_bucket:
  107. return self.diversion_bucket.match(experiment_context)
  108. return False
  109. def init(self):
  110. # 初始化 debug_user_map
  111. if self.debug_users:
  112. self.debug_user_set.update(self.debug_users.split(","))
  113. # 初始化 diversion_bucket
  114. if self.bucket_type == "Random": # ExpBucketTypeRand
  115. self.diversion_bucket = UidDiversionBucket(100, self.buckets)
  116. elif self.bucket_type == "Condition" and self.filter_condition: # ExpBucketTypeCond
  117. self.diversion_bucket = FilterDiversionBucket(self.filter_condition)
  118. class ExperimentVersion:
  119. def __init__(self, exp_version_id, flow, buckets: str, exp_version_name=None, debug_users: str = '',
  120. config=None, debug_crowd_ids=None):
  121. self.id = exp_version_id
  122. self.exp_version_name = exp_version_name
  123. self.config = config
  124. self.debug_crowd_ids = debug_crowd_ids
  125. self.debug_users = debug_users
  126. self.params = {}
  127. self.flow = flow
  128. self.buckets = buckets
  129. self.debug_user_set = set()
  130. self.diversion_bucket = None
  131. def add_debug_users(self, users: List[str]):
  132. self.debug_user_set.update(users)
  133. def match_debug_users(self, experiment_context):
  134. return experiment_context.uid in self.debug_user_set
  135. def match(self, experiment_context: ExperimentContext):
  136. if self.flow == 0:
  137. return False
  138. elif self.flow == 100:
  139. return True
  140. if self.diversion_bucket:
  141. return self.diversion_bucket.match(experiment_context)
  142. return False
  143. def init(self):
  144. self.debug_user_set.update(self.debug_users.split(","))
  145. self.diversion_bucket = UidDiversionBucket(100, self.buckets)
  146. params = json.loads(self.config)
  147. for kv in params:
  148. self.params[kv['key']] = kv['value']
  149. class Project:
  150. def __init__(self, project_name=None, project_id=None):
  151. self.project_name = project_name
  152. self.id = project_id
  153. self.domains = []
  154. self.layers = []
  155. self.default_domain : Optional[Domain] = None
  156. self.layer_map = {}
  157. self.domain_map = {}
  158. def add_domain(self, domain):
  159. self.domains.append(domain)
  160. self.domain_map[domain.id] = domain
  161. def add_layer(self, layer):
  162. self.layers.append(layer)
  163. self.layer_map[layer.id] = layer
  164. def set_default_domain(self, domain: Domain):
  165. self.default_domain = domain
  166. class ExperimentResult:
  167. def __init__(self, project=None, experiment_context=None, project_name=None):
  168. self.project = project
  169. self.experiment_context = experiment_context
  170. self.project_name = project_name
  171. self.params = {}
  172. self.experiment_versions = []
  173. def add_params(self, params: Dict[str, str]):
  174. self.params.update(params)
  175. def add_experiment_version(self, version):
  176. self.experiment_versions.append(version)
  177. def init(self):
  178. # Initialize result-specific logic
  179. pass
  180. def __str__(self):
  181. return f"ExperimentResult(project={self.project_name}, params={self.params}, experiment_context={self.experiment_context}, experiment_versions={self.experiment_versions})"