bead 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. bead/__init__.py +11 -0
  2. bead/__main__.py +11 -0
  3. bead/active_learning/__init__.py +15 -0
  4. bead/active_learning/config.py +231 -0
  5. bead/active_learning/loop.py +566 -0
  6. bead/active_learning/models/__init__.py +24 -0
  7. bead/active_learning/models/base.py +852 -0
  8. bead/active_learning/models/binary.py +910 -0
  9. bead/active_learning/models/categorical.py +943 -0
  10. bead/active_learning/models/cloze.py +862 -0
  11. bead/active_learning/models/forced_choice.py +956 -0
  12. bead/active_learning/models/free_text.py +773 -0
  13. bead/active_learning/models/lora.py +365 -0
  14. bead/active_learning/models/magnitude.py +835 -0
  15. bead/active_learning/models/multi_select.py +795 -0
  16. bead/active_learning/models/ordinal_scale.py +811 -0
  17. bead/active_learning/models/peft_adapter.py +155 -0
  18. bead/active_learning/models/random_effects.py +639 -0
  19. bead/active_learning/selection.py +354 -0
  20. bead/active_learning/strategies.py +391 -0
  21. bead/active_learning/trainers/__init__.py +26 -0
  22. bead/active_learning/trainers/base.py +210 -0
  23. bead/active_learning/trainers/data_collator.py +172 -0
  24. bead/active_learning/trainers/dataset_utils.py +261 -0
  25. bead/active_learning/trainers/huggingface.py +304 -0
  26. bead/active_learning/trainers/lightning.py +324 -0
  27. bead/active_learning/trainers/metrics.py +424 -0
  28. bead/active_learning/trainers/mixed_effects.py +551 -0
  29. bead/active_learning/trainers/model_wrapper.py +509 -0
  30. bead/active_learning/trainers/registry.py +104 -0
  31. bead/adapters/__init__.py +11 -0
  32. bead/adapters/huggingface.py +61 -0
  33. bead/behavioral/__init__.py +116 -0
  34. bead/behavioral/analytics.py +646 -0
  35. bead/behavioral/extraction.py +343 -0
  36. bead/behavioral/merging.py +343 -0
  37. bead/cli/__init__.py +11 -0
  38. bead/cli/active_learning.py +513 -0
  39. bead/cli/active_learning_commands.py +779 -0
  40. bead/cli/completion.py +359 -0
  41. bead/cli/config.py +624 -0
  42. bead/cli/constraint_builders.py +286 -0
  43. bead/cli/deployment.py +859 -0
  44. bead/cli/deployment_trials.py +493 -0
  45. bead/cli/deployment_ui.py +332 -0
  46. bead/cli/display.py +378 -0
  47. bead/cli/items.py +960 -0
  48. bead/cli/items_factories.py +776 -0
  49. bead/cli/list_constraints.py +714 -0
  50. bead/cli/lists.py +490 -0
  51. bead/cli/main.py +430 -0
  52. bead/cli/models.py +877 -0
  53. bead/cli/resource_loaders.py +621 -0
  54. bead/cli/resources.py +1036 -0
  55. bead/cli/shell.py +356 -0
  56. bead/cli/simulate.py +840 -0
  57. bead/cli/templates.py +1158 -0
  58. bead/cli/training.py +1080 -0
  59. bead/cli/utils.py +614 -0
  60. bead/cli/workflow.py +1273 -0
  61. bead/config/__init__.py +68 -0
  62. bead/config/active_learning.py +1009 -0
  63. bead/config/config.py +192 -0
  64. bead/config/defaults.py +118 -0
  65. bead/config/deployment.py +217 -0
  66. bead/config/env.py +147 -0
  67. bead/config/item.py +45 -0
  68. bead/config/list.py +193 -0
  69. bead/config/loader.py +149 -0
  70. bead/config/logging.py +42 -0
  71. bead/config/model.py +49 -0
  72. bead/config/paths.py +46 -0
  73. bead/config/profiles.py +320 -0
  74. bead/config/resources.py +47 -0
  75. bead/config/serialization.py +210 -0
  76. bead/config/simulation.py +206 -0
  77. bead/config/template.py +238 -0
  78. bead/config/validation.py +267 -0
  79. bead/data/__init__.py +65 -0
  80. bead/data/base.py +87 -0
  81. bead/data/identifiers.py +97 -0
  82. bead/data/language_codes.py +61 -0
  83. bead/data/metadata.py +270 -0
  84. bead/data/range.py +123 -0
  85. bead/data/repository.py +358 -0
  86. bead/data/serialization.py +249 -0
  87. bead/data/timestamps.py +89 -0
  88. bead/data/validation.py +349 -0
  89. bead/data_collection/__init__.py +11 -0
  90. bead/data_collection/jatos.py +223 -0
  91. bead/data_collection/merger.py +154 -0
  92. bead/data_collection/prolific.py +198 -0
  93. bead/deployment/__init__.py +5 -0
  94. bead/deployment/distribution.py +402 -0
  95. bead/deployment/jatos/__init__.py +1 -0
  96. bead/deployment/jatos/api.py +200 -0
  97. bead/deployment/jatos/exporter.py +210 -0
  98. bead/deployment/jspsych/__init__.py +9 -0
  99. bead/deployment/jspsych/biome.json +44 -0
  100. bead/deployment/jspsych/config.py +411 -0
  101. bead/deployment/jspsych/generator.py +598 -0
  102. bead/deployment/jspsych/package.json +51 -0
  103. bead/deployment/jspsych/pnpm-lock.yaml +2141 -0
  104. bead/deployment/jspsych/randomizer.py +299 -0
  105. bead/deployment/jspsych/src/lib/list-distributor.test.ts +327 -0
  106. bead/deployment/jspsych/src/lib/list-distributor.ts +1282 -0
  107. bead/deployment/jspsych/src/lib/randomizer.test.ts +232 -0
  108. bead/deployment/jspsych/src/lib/randomizer.ts +367 -0
  109. bead/deployment/jspsych/src/plugins/cloze-dropdown.ts +252 -0
  110. bead/deployment/jspsych/src/plugins/forced-choice.ts +265 -0
  111. bead/deployment/jspsych/src/plugins/plugins.test.ts +141 -0
  112. bead/deployment/jspsych/src/plugins/rating.ts +248 -0
  113. bead/deployment/jspsych/src/slopit/index.ts +9 -0
  114. bead/deployment/jspsych/src/types/jatos.d.ts +256 -0
  115. bead/deployment/jspsych/src/types/jspsych.d.ts +228 -0
  116. bead/deployment/jspsych/templates/experiment.css +1 -0
  117. bead/deployment/jspsych/templates/experiment.js.template +289 -0
  118. bead/deployment/jspsych/templates/index.html +51 -0
  119. bead/deployment/jspsych/templates/randomizer.js +241 -0
  120. bead/deployment/jspsych/templates/randomizer.js.template +313 -0
  121. bead/deployment/jspsych/trials.py +723 -0
  122. bead/deployment/jspsych/tsconfig.json +23 -0
  123. bead/deployment/jspsych/tsup.config.ts +30 -0
  124. bead/deployment/jspsych/ui/__init__.py +1 -0
  125. bead/deployment/jspsych/ui/components.py +383 -0
  126. bead/deployment/jspsych/ui/styles.py +411 -0
  127. bead/dsl/__init__.py +80 -0
  128. bead/dsl/ast.py +168 -0
  129. bead/dsl/context.py +178 -0
  130. bead/dsl/errors.py +71 -0
  131. bead/dsl/evaluator.py +570 -0
  132. bead/dsl/grammar.lark +81 -0
  133. bead/dsl/parser.py +231 -0
  134. bead/dsl/stdlib.py +929 -0
  135. bead/evaluation/__init__.py +13 -0
  136. bead/evaluation/convergence.py +485 -0
  137. bead/evaluation/interannotator.py +398 -0
  138. bead/items/__init__.py +40 -0
  139. bead/items/adapters/__init__.py +70 -0
  140. bead/items/adapters/anthropic.py +224 -0
  141. bead/items/adapters/api_utils.py +167 -0
  142. bead/items/adapters/base.py +216 -0
  143. bead/items/adapters/google.py +259 -0
  144. bead/items/adapters/huggingface.py +1074 -0
  145. bead/items/adapters/openai.py +323 -0
  146. bead/items/adapters/registry.py +202 -0
  147. bead/items/adapters/sentence_transformers.py +224 -0
  148. bead/items/adapters/togetherai.py +309 -0
  149. bead/items/binary.py +515 -0
  150. bead/items/cache.py +558 -0
  151. bead/items/categorical.py +593 -0
  152. bead/items/cloze.py +757 -0
  153. bead/items/constructor.py +784 -0
  154. bead/items/forced_choice.py +413 -0
  155. bead/items/free_text.py +681 -0
  156. bead/items/generation.py +432 -0
  157. bead/items/item.py +396 -0
  158. bead/items/item_template.py +787 -0
  159. bead/items/magnitude.py +573 -0
  160. bead/items/multi_select.py +621 -0
  161. bead/items/ordinal_scale.py +569 -0
  162. bead/items/scoring.py +448 -0
  163. bead/items/validation.py +723 -0
  164. bead/lists/__init__.py +30 -0
  165. bead/lists/balancer.py +263 -0
  166. bead/lists/constraints.py +1067 -0
  167. bead/lists/experiment_list.py +286 -0
  168. bead/lists/list_collection.py +378 -0
  169. bead/lists/partitioner.py +1141 -0
  170. bead/lists/stratification.py +254 -0
  171. bead/participants/__init__.py +73 -0
  172. bead/participants/collection.py +699 -0
  173. bead/participants/merging.py +312 -0
  174. bead/participants/metadata_spec.py +491 -0
  175. bead/participants/models.py +276 -0
  176. bead/resources/__init__.py +29 -0
  177. bead/resources/adapters/__init__.py +19 -0
  178. bead/resources/adapters/base.py +104 -0
  179. bead/resources/adapters/cache.py +128 -0
  180. bead/resources/adapters/glazing.py +508 -0
  181. bead/resources/adapters/registry.py +117 -0
  182. bead/resources/adapters/unimorph.py +796 -0
  183. bead/resources/classification.py +856 -0
  184. bead/resources/constraint_builders.py +329 -0
  185. bead/resources/constraints.py +165 -0
  186. bead/resources/lexical_item.py +223 -0
  187. bead/resources/lexicon.py +744 -0
  188. bead/resources/loaders.py +209 -0
  189. bead/resources/template.py +441 -0
  190. bead/resources/template_collection.py +707 -0
  191. bead/resources/template_generation.py +349 -0
  192. bead/simulation/__init__.py +29 -0
  193. bead/simulation/annotators/__init__.py +15 -0
  194. bead/simulation/annotators/base.py +175 -0
  195. bead/simulation/annotators/distance_based.py +135 -0
  196. bead/simulation/annotators/lm_based.py +114 -0
  197. bead/simulation/annotators/oracle.py +182 -0
  198. bead/simulation/annotators/random.py +181 -0
  199. bead/simulation/dsl_extension/__init__.py +3 -0
  200. bead/simulation/noise_models/__init__.py +13 -0
  201. bead/simulation/noise_models/base.py +42 -0
  202. bead/simulation/noise_models/random_noise.py +82 -0
  203. bead/simulation/noise_models/systematic.py +132 -0
  204. bead/simulation/noise_models/temperature.py +86 -0
  205. bead/simulation/runner.py +144 -0
  206. bead/simulation/strategies/__init__.py +23 -0
  207. bead/simulation/strategies/base.py +123 -0
  208. bead/simulation/strategies/binary.py +103 -0
  209. bead/simulation/strategies/categorical.py +123 -0
  210. bead/simulation/strategies/cloze.py +224 -0
  211. bead/simulation/strategies/forced_choice.py +127 -0
  212. bead/simulation/strategies/free_text.py +105 -0
  213. bead/simulation/strategies/magnitude.py +116 -0
  214. bead/simulation/strategies/multi_select.py +129 -0
  215. bead/simulation/strategies/ordinal_scale.py +131 -0
  216. bead/templates/__init__.py +27 -0
  217. bead/templates/adapters/__init__.py +17 -0
  218. bead/templates/adapters/base.py +128 -0
  219. bead/templates/adapters/cache.py +178 -0
  220. bead/templates/adapters/huggingface.py +312 -0
  221. bead/templates/combinatorics.py +103 -0
  222. bead/templates/filler.py +605 -0
  223. bead/templates/renderers.py +177 -0
  224. bead/templates/resolver.py +178 -0
  225. bead/templates/strategies.py +1806 -0
  226. bead/templates/streaming.py +195 -0
  227. bead-0.1.0.dist-info/METADATA +212 -0
  228. bead-0.1.0.dist-info/RECORD +231 -0
  229. bead-0.1.0.dist-info/WHEEL +4 -0
  230. bead-0.1.0.dist-info/entry_points.txt +2 -0
  231. bead-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,714 @@
1
+ """List constraint builder commands for bead CLI.
2
+
3
+ This module provides CLI commands for creating list and batch constraints.
4
+ List constraints apply to individual lists, while batch constraints apply
5
+ across all lists in a batch.
6
+
7
+ List constraints (8 types):
8
+ - uniqueness: No duplicate property values
9
+ - balance: Balanced distribution
10
+ - quantile: Uniform across quantiles
11
+ - grouped-quantile: Quantile distribution within groups
12
+ - conditional-uniqueness: Conditional uniqueness via DSL
13
+ - diversity: Minimum unique values
14
+ - size: List size requirements
15
+ - ordering: Presentation order (runtime)
16
+
17
+ Batch constraints (4 types):
18
+ - coverage: All values appear somewhere
19
+ - balance: Balanced distribution across batch
20
+ - diversity: Prevent values in too many lists
21
+ - min-occurrence: Minimum occurrences per value
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from pathlib import Path
27
+
28
+ import click
29
+
30
+ from bead.cli.display import print_error, print_success
31
+ from bead.cli.utils import parse_key_value_pairs, parse_list_option
32
+ from bead.lists.constraints import (
33
+ BalanceConstraint,
34
+ BatchBalanceConstraint,
35
+ BatchCoverageConstraint,
36
+ BatchDiversityConstraint,
37
+ BatchMinOccurrenceConstraint,
38
+ DiversityConstraint,
39
+ GroupedQuantileConstraint,
40
+ QuantileConstraint,
41
+ SizeConstraint,
42
+ UniquenessConstraint,
43
+ )
44
+
45
+ # ==================== List Constraint Commands ====================
46
+
47
+
48
+ @click.command()
49
+ @click.option(
50
+ "--property-expression",
51
+ type=str,
52
+ required=True,
53
+ help="Property expression (e.g., 'item.metadata.verb')",
54
+ )
55
+ @click.option(
56
+ "--output",
57
+ "-o",
58
+ type=click.Path(path_type=Path),
59
+ required=True,
60
+ help="Output JSONL file",
61
+ )
62
+ @click.option(
63
+ "--priority",
64
+ type=int,
65
+ default=5,
66
+ help="Constraint priority (default: 5)",
67
+ )
68
+ def create_uniqueness(
69
+ property_expression: str,
70
+ output: Path,
71
+ priority: int,
72
+ ) -> None:
73
+ r"""Create uniqueness constraint.
74
+
75
+ Ensures no duplicate values for a property within each list.
76
+
77
+ Examples
78
+ --------
79
+ $ bead lists create-uniqueness \\
80
+ --property-expression "item.metadata.verb" \\
81
+ -o constraints/unique_verbs.jsonl
82
+ """
83
+ try:
84
+ constraint = UniquenessConstraint(
85
+ property_expression=property_expression,
86
+ priority=priority,
87
+ )
88
+
89
+ output.parent.mkdir(parents=True, exist_ok=True)
90
+ with open(output, "w") as f:
91
+ f.write(constraint.model_dump_json() + "\n")
92
+
93
+ print_success(f"Created uniqueness constraint: {output}")
94
+
95
+ except Exception as e:
96
+ print_error(f"Failed to create uniqueness constraint: {e}")
97
+
98
+
99
+ @click.command()
100
+ @click.option(
101
+ "--property-expression",
102
+ type=str,
103
+ required=True,
104
+ help="Property expression",
105
+ )
106
+ @click.option(
107
+ "--target-counts",
108
+ type=str,
109
+ help=(
110
+ "Target counts (key=value pairs, comma-separated, e.g., 'a=20,b=10'). "
111
+ "Omit for equal distribution."
112
+ ),
113
+ )
114
+ @click.option(
115
+ "--output",
116
+ "-o",
117
+ type=click.Path(path_type=Path),
118
+ required=True,
119
+ help="Output JSONL file",
120
+ )
121
+ @click.option(
122
+ "--tolerance",
123
+ type=float,
124
+ default=0.1,
125
+ help="Tolerance (default: 0.1)",
126
+ )
127
+ @click.option(
128
+ "--priority",
129
+ type=int,
130
+ default=5,
131
+ help="Constraint priority (default: 5)",
132
+ )
133
+ def create_balance(
134
+ property_expression: str,
135
+ target_counts: str | None,
136
+ output: Path,
137
+ tolerance: float,
138
+ priority: int,
139
+ ) -> None:
140
+ r"""Create balance constraint.
141
+
142
+ Ensures balanced distribution of property values.
143
+
144
+ Examples
145
+ --------
146
+ $ bead lists create-balance \\
147
+ --property-expression "item.metadata.condition" \\
148
+ --target-counts "control=20,experimental=10" \\
149
+ -o constraints/balance.jsonl
150
+ """
151
+ try:
152
+ # Parse target counts (None means equal distribution)
153
+ counts_dict = None
154
+ if target_counts:
155
+ parsed = parse_key_value_pairs(target_counts)
156
+ counts_dict = {k: int(v) for k, v in parsed.items()}
157
+
158
+ constraint = BalanceConstraint(
159
+ property_expression=property_expression,
160
+ target_counts=counts_dict,
161
+ tolerance=tolerance,
162
+ priority=priority,
163
+ )
164
+
165
+ output.parent.mkdir(parents=True, exist_ok=True)
166
+ with open(output, "w") as f:
167
+ f.write(constraint.model_dump_json() + "\n")
168
+
169
+ print_success(f"Created balance constraint: {output}")
170
+
171
+ except Exception as e:
172
+ print_error(f"Failed to create balance constraint: {e}")
173
+
174
+
175
+ @click.command()
176
+ @click.option(
177
+ "--property-expression",
178
+ type=str,
179
+ required=True,
180
+ help="Property expression",
181
+ )
182
+ @click.option(
183
+ "--n-quantiles",
184
+ type=int,
185
+ required=True,
186
+ help="Number of quantiles",
187
+ )
188
+ @click.option(
189
+ "--output",
190
+ "-o",
191
+ type=click.Path(path_type=Path),
192
+ required=True,
193
+ help="Output JSONL file",
194
+ )
195
+ @click.option(
196
+ "--priority",
197
+ type=int,
198
+ default=5,
199
+ help="Constraint priority (default: 5)",
200
+ )
201
+ def create_quantile(
202
+ property_expression: str,
203
+ n_quantiles: int,
204
+ output: Path,
205
+ priority: int,
206
+ ) -> None:
207
+ r"""Create quantile constraint.
208
+
209
+ Ensures uniform distribution across quantiles.
210
+
211
+ Examples
212
+ --------
213
+ $ bead lists create-quantile \\
214
+ --property-expression "item.metadata.word_length" \\
215
+ --n-quantiles 4 \\
216
+ -o constraints/quantile.jsonl
217
+ """
218
+ try:
219
+ constraint = QuantileConstraint(
220
+ property_expression=property_expression,
221
+ n_quantiles=n_quantiles,
222
+ priority=priority,
223
+ )
224
+
225
+ output.parent.mkdir(parents=True, exist_ok=True)
226
+ with open(output, "w") as f:
227
+ f.write(constraint.model_dump_json() + "\n")
228
+
229
+ print_success(f"Created quantile constraint: {output}")
230
+
231
+ except Exception as e:
232
+ print_error(f"Failed to create quantile constraint: {e}")
233
+
234
+
235
+ @click.command()
236
+ @click.option(
237
+ "--property-expression",
238
+ type=str,
239
+ required=True,
240
+ help="Property expression",
241
+ )
242
+ @click.option(
243
+ "--group-by-expression",
244
+ type=str,
245
+ required=True,
246
+ help="Group-by expression",
247
+ )
248
+ @click.option(
249
+ "--n-quantiles",
250
+ type=int,
251
+ required=True,
252
+ help="Number of quantiles",
253
+ )
254
+ @click.option(
255
+ "--output",
256
+ "-o",
257
+ type=click.Path(path_type=Path),
258
+ required=True,
259
+ help="Output JSONL file",
260
+ )
261
+ @click.option(
262
+ "--priority",
263
+ type=int,
264
+ default=5,
265
+ help="Constraint priority (default: 5)",
266
+ )
267
+ def create_grouped_quantile(
268
+ property_expression: str,
269
+ group_by_expression: str,
270
+ n_quantiles: int,
271
+ output: Path,
272
+ priority: int,
273
+ ) -> None:
274
+ r"""Create grouped quantile constraint.
275
+
276
+ Ensures quantile distribution within groups.
277
+
278
+ Examples
279
+ --------
280
+ $ bead lists create-grouped-quantile \\
281
+ --property-expression "item.metadata.frequency" \\
282
+ --group-by-expression "item.metadata.condition" \\
283
+ --n-quantiles 3 \\
284
+ -o constraints/grouped_quantile.jsonl
285
+ """
286
+ try:
287
+ constraint = GroupedQuantileConstraint(
288
+ property_expression=property_expression,
289
+ group_by_expression=group_by_expression,
290
+ n_quantiles=n_quantiles,
291
+ priority=priority,
292
+ )
293
+
294
+ output.parent.mkdir(parents=True, exist_ok=True)
295
+ with open(output, "w") as f:
296
+ f.write(constraint.model_dump_json() + "\n")
297
+
298
+ print_success(f"Created grouped quantile constraint: {output}")
299
+
300
+ except Exception as e:
301
+ print_error(f"Failed to create grouped quantile constraint: {e}")
302
+
303
+
304
+ @click.command()
305
+ @click.option(
306
+ "--property-expression",
307
+ type=str,
308
+ required=True,
309
+ help="Property expression",
310
+ )
311
+ @click.option(
312
+ "--min-unique",
313
+ type=int,
314
+ required=True,
315
+ help="Minimum unique values",
316
+ )
317
+ @click.option(
318
+ "--output",
319
+ "-o",
320
+ type=click.Path(path_type=Path),
321
+ required=True,
322
+ help="Output JSONL file",
323
+ )
324
+ @click.option(
325
+ "--priority",
326
+ type=int,
327
+ default=5,
328
+ help="Constraint priority (default: 5)",
329
+ )
330
+ def create_diversity(
331
+ property_expression: str,
332
+ min_unique: int,
333
+ output: Path,
334
+ priority: int,
335
+ ) -> None:
336
+ r"""Create diversity constraint.
337
+
338
+ Ensures minimum unique values for property.
339
+
340
+ Examples
341
+ --------
342
+ $ bead lists create-diversity \\
343
+ --property-expression "item.metadata.verb_class" \\
344
+ --min-unique 10 \\
345
+ -o constraints/diversity.jsonl
346
+ """
347
+ try:
348
+ constraint = DiversityConstraint(
349
+ property_expression=property_expression,
350
+ min_unique_values=min_unique,
351
+ priority=priority,
352
+ )
353
+
354
+ output.parent.mkdir(parents=True, exist_ok=True)
355
+ with open(output, "w") as f:
356
+ f.write(constraint.model_dump_json() + "\n")
357
+
358
+ print_success(f"Created diversity constraint: {output}")
359
+
360
+ except Exception as e:
361
+ print_error(f"Failed to create diversity constraint: {e}")
362
+
363
+
364
+ @click.command()
365
+ @click.option(
366
+ "--exact-size",
367
+ type=int,
368
+ help="Exact number of items per list (mutually exclusive with min/max)",
369
+ )
370
+ @click.option(
371
+ "--min-size",
372
+ type=int,
373
+ help="Minimum items per list",
374
+ )
375
+ @click.option(
376
+ "--max-size",
377
+ type=int,
378
+ help="Maximum items per list",
379
+ )
380
+ @click.option(
381
+ "--output",
382
+ "-o",
383
+ type=click.Path(path_type=Path),
384
+ required=True,
385
+ help="Output JSONL file",
386
+ )
387
+ @click.option(
388
+ "--priority",
389
+ type=int,
390
+ default=5,
391
+ help="Constraint priority (default: 5)",
392
+ )
393
+ def create_size(
394
+ exact_size: int | None,
395
+ min_size: int | None,
396
+ max_size: int | None,
397
+ output: Path,
398
+ priority: int,
399
+ ) -> None:
400
+ r"""Create size constraint.
401
+
402
+ Ensures list size requirements.
403
+
404
+ Examples
405
+ --------
406
+ $ bead lists create-size --exact-size 40 -o constraints/size.jsonl
407
+ $ bead lists create-size --min-size 40 --max-size 60 \\
408
+ -o constraints/size.jsonl
409
+ """
410
+ try:
411
+ if exact_size is not None and (min_size is not None or max_size is not None):
412
+ raise ValueError(
413
+ "Cannot specify --exact-size with --min-size or --max-size"
414
+ )
415
+
416
+ if exact_size is None and min_size is None and max_size is None:
417
+ raise ValueError(
418
+ "Must specify --exact-size or at least one of --min-size/--max-size"
419
+ )
420
+
421
+ constraint = SizeConstraint(
422
+ exact_size=exact_size,
423
+ min_size=min_size,
424
+ max_size=max_size,
425
+ priority=priority,
426
+ )
427
+
428
+ output.parent.mkdir(parents=True, exist_ok=True)
429
+ with open(output, "w") as f:
430
+ f.write(constraint.model_dump_json() + "\n")
431
+
432
+ print_success(f"Created size constraint: {output}")
433
+
434
+ except Exception as e:
435
+ print_error(f"Failed to create size constraint: {e}")
436
+
437
+
438
+ # ==================== Batch Constraint Commands ====================
439
+
440
+
441
+ @click.command()
442
+ @click.option(
443
+ "--property-expression",
444
+ type=str,
445
+ required=True,
446
+ help="Property expression",
447
+ )
448
+ @click.option(
449
+ "--target-values",
450
+ type=str,
451
+ required=True,
452
+ help="Target values (comma-separated)",
453
+ )
454
+ @click.option(
455
+ "--min-coverage",
456
+ type=float,
457
+ default=1.0,
458
+ help="Minimum coverage (default: 1.0 = 100%%)",
459
+ )
460
+ @click.option(
461
+ "--output",
462
+ "-o",
463
+ type=click.Path(path_type=Path),
464
+ required=True,
465
+ help="Output JSONL file",
466
+ )
467
+ @click.option(
468
+ "--priority",
469
+ type=int,
470
+ default=5,
471
+ help="Constraint priority (default: 5)",
472
+ )
473
+ def create_batch_coverage(
474
+ property_expression: str,
475
+ target_values: str,
476
+ min_coverage: float,
477
+ output: Path,
478
+ priority: int,
479
+ ) -> None:
480
+ r"""Create batch coverage constraint.
481
+
482
+ Ensures all target values appear somewhere across all lists.
483
+
484
+ Examples
485
+ --------
486
+ $ bead lists create-batch-coverage \\
487
+ --property-expression "item.metadata.template_id" \\
488
+ --target-values "0,1,2,3,4,5" \\
489
+ -o constraints/coverage.jsonl
490
+ """
491
+ try:
492
+ values_list = parse_list_option(target_values)
493
+
494
+ constraint = BatchCoverageConstraint(
495
+ property_expression=property_expression,
496
+ target_values=values_list, # type: ignore[arg-type]
497
+ min_coverage=min_coverage,
498
+ priority=priority,
499
+ )
500
+
501
+ output.parent.mkdir(parents=True, exist_ok=True)
502
+ with open(output, "w") as f:
503
+ f.write(constraint.model_dump_json() + "\n")
504
+
505
+ print_success(f"Created batch coverage constraint: {output}")
506
+
507
+ except Exception as e:
508
+ print_error(f"Failed to create batch coverage constraint: {e}")
509
+
510
+
511
+ @click.command()
512
+ @click.option(
513
+ "--property-expression",
514
+ type=str,
515
+ required=True,
516
+ help="Property expression",
517
+ )
518
+ @click.option(
519
+ "--target-distribution",
520
+ type=str,
521
+ required=True,
522
+ help="Target distribution (key=value pairs, comma-separated)",
523
+ )
524
+ @click.option(
525
+ "--tolerance",
526
+ type=float,
527
+ default=0.05,
528
+ help="Tolerance (default: 0.05)",
529
+ )
530
+ @click.option(
531
+ "--output",
532
+ "-o",
533
+ type=click.Path(path_type=Path),
534
+ required=True,
535
+ help="Output JSONL file",
536
+ )
537
+ @click.option(
538
+ "--priority",
539
+ type=int,
540
+ default=5,
541
+ help="Constraint priority (default: 5)",
542
+ )
543
+ def create_batch_balance(
544
+ property_expression: str,
545
+ target_distribution: str,
546
+ tolerance: float,
547
+ output: Path,
548
+ priority: int,
549
+ ) -> None:
550
+ r"""Create batch balance constraint.
551
+
552
+ Ensures balanced distribution across entire batch.
553
+
554
+ Examples
555
+ --------
556
+ $ bead lists create-batch-balance \\
557
+ --property-expression "item.metadata.condition" \\
558
+ --target-distribution "control=0.5,experimental=0.5" \\
559
+ -o constraints/batch_balance.jsonl
560
+ """
561
+ try:
562
+ dist_dict = parse_key_value_pairs(target_distribution)
563
+ dist_float = {k: float(v) for k, v in dist_dict.items()}
564
+
565
+ constraint = BatchBalanceConstraint(
566
+ property_expression=property_expression,
567
+ target_distribution=dist_float,
568
+ tolerance=tolerance,
569
+ priority=priority,
570
+ )
571
+
572
+ output.parent.mkdir(parents=True, exist_ok=True)
573
+ with open(output, "w") as f:
574
+ f.write(constraint.model_dump_json() + "\n")
575
+
576
+ print_success(f"Created batch balance constraint: {output}")
577
+
578
+ except Exception as e:
579
+ print_error(f"Failed to create batch balance constraint: {e}")
580
+
581
+
582
+ @click.command()
583
+ @click.option(
584
+ "--property-expression",
585
+ type=str,
586
+ required=True,
587
+ help="Property expression",
588
+ )
589
+ @click.option(
590
+ "--max-lists-per-value",
591
+ type=int,
592
+ required=True,
593
+ help="Maximum lists per value",
594
+ )
595
+ @click.option(
596
+ "--output",
597
+ "-o",
598
+ type=click.Path(path_type=Path),
599
+ required=True,
600
+ help="Output JSONL file",
601
+ )
602
+ @click.option(
603
+ "--priority",
604
+ type=int,
605
+ default=5,
606
+ help="Constraint priority (default: 5)",
607
+ )
608
+ def create_batch_diversity(
609
+ property_expression: str,
610
+ max_lists_per_value: int,
611
+ output: Path,
612
+ priority: int,
613
+ ) -> None:
614
+ r"""Create batch diversity constraint.
615
+
616
+ Prevents values from appearing in too many lists.
617
+
618
+ Examples
619
+ --------
620
+ $ bead lists create-batch-diversity \\
621
+ --property-expression "item.metadata.target_word" \\
622
+ --max-lists-per-value 3 \\
623
+ -o constraints/batch_diversity.jsonl
624
+ """
625
+ try:
626
+ constraint = BatchDiversityConstraint(
627
+ property_expression=property_expression,
628
+ max_lists_per_value=max_lists_per_value,
629
+ priority=priority,
630
+ )
631
+
632
+ output.parent.mkdir(parents=True, exist_ok=True)
633
+ with open(output, "w") as f:
634
+ f.write(constraint.model_dump_json() + "\n")
635
+
636
+ print_success(f"Created batch diversity constraint: {output}")
637
+
638
+ except Exception as e:
639
+ print_error(f"Failed to create batch diversity constraint: {e}")
640
+
641
+
642
+ @click.command()
643
+ @click.option(
644
+ "--property-expression",
645
+ type=str,
646
+ required=True,
647
+ help="Property expression",
648
+ )
649
+ @click.option(
650
+ "--min-occurrences",
651
+ type=int,
652
+ required=True,
653
+ help="Minimum occurrences per value",
654
+ )
655
+ @click.option(
656
+ "--output",
657
+ "-o",
658
+ type=click.Path(path_type=Path),
659
+ required=True,
660
+ help="Output JSONL file",
661
+ )
662
+ @click.option(
663
+ "--priority",
664
+ type=int,
665
+ default=5,
666
+ help="Constraint priority (default: 5)",
667
+ )
668
+ def create_batch_min_occurrence(
669
+ property_expression: str,
670
+ min_occurrences: int,
671
+ output: Path,
672
+ priority: int,
673
+ ) -> None:
674
+ r"""Create batch minimum occurrence constraint.
675
+
676
+ Ensures minimum occurrences per value across batch.
677
+
678
+ Examples
679
+ --------
680
+ $ bead lists create-batch-min-occurrence \\
681
+ --property-expression "item.metadata.construction" \\
682
+ --min-occurrences 5 \\
683
+ -o constraints/min_occurrence.jsonl
684
+ """
685
+ try:
686
+ constraint = BatchMinOccurrenceConstraint(
687
+ property_expression=property_expression,
688
+ min_occurrences=min_occurrences,
689
+ priority=priority,
690
+ )
691
+
692
+ output.parent.mkdir(parents=True, exist_ok=True)
693
+ with open(output, "w") as f:
694
+ f.write(constraint.model_dump_json() + "\n")
695
+
696
+ print_success(f"Created batch min occurrence constraint: {output}")
697
+
698
+ except Exception as e:
699
+ print_error(f"Failed to create batch min occurrence constraint: {e}")
700
+
701
+
702
+ # Export all commands
703
+ __all__ = [
704
+ "create_uniqueness",
705
+ "create_balance",
706
+ "create_quantile",
707
+ "create_grouped_quantile",
708
+ "create_diversity",
709
+ "create_size",
710
+ "create_batch_coverage",
711
+ "create_batch_balance",
712
+ "create_batch_diversity",
713
+ "create_batch_min_occurrence",
714
+ ]