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,493 @@
1
+ """Trial configuration commands for jsPsych deployment.
2
+
3
+ This module provides commands for configuring jsPsych trial parameters,
4
+ including timing, response collection, and trial-type-specific settings.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ import click
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+ from rich.table import Table
17
+
18
+ from bead.cli.utils import print_error, print_success
19
+
20
+ console = Console()
21
+
22
+
23
+ @click.group(name="trials")
24
+ def deployment_trials() -> None:
25
+ r"""Trial configuration commands for jsPsych experiments.
26
+
27
+ Commands for configuring trial parameters, including timing,
28
+ response collection, and trial-type-specific settings.
29
+
30
+ \b
31
+ Examples:
32
+ $ bead deployment trials configure-rating \\
33
+ --min-value 1 --max-value 7 \\
34
+ --min-label "Completely unnatural" \\
35
+ --max-label "Completely natural" \\
36
+ --output rating_config.json
37
+
38
+ $ bead deployment trials configure-choice \\
39
+ --button-html '<button class="choice-btn">%choice%</button>' \\
40
+ --output choice_config.json
41
+ """
42
+
43
+
44
+ @click.command()
45
+ @click.argument("output_file", type=click.Path(path_type=Path))
46
+ @click.option(
47
+ "--min-value",
48
+ type=int,
49
+ default=1,
50
+ help="Minimum value of the rating scale (default: 1)",
51
+ )
52
+ @click.option(
53
+ "--max-value",
54
+ type=int,
55
+ default=7,
56
+ help="Maximum value of the rating scale (default: 7)",
57
+ )
58
+ @click.option(
59
+ "--step",
60
+ type=int,
61
+ default=1,
62
+ help="Step size for the scale (default: 1)",
63
+ )
64
+ @click.option(
65
+ "--min-label",
66
+ default="Strongly Disagree",
67
+ help="Label for minimum value",
68
+ )
69
+ @click.option(
70
+ "--max-label",
71
+ default="Strongly Agree",
72
+ help="Label for maximum value",
73
+ )
74
+ @click.option(
75
+ "--show-numeric-labels",
76
+ is_flag=True,
77
+ help="Show numeric labels on buttons (Likert only)",
78
+ )
79
+ @click.option(
80
+ "--required",
81
+ is_flag=True,
82
+ default=True,
83
+ help="Require response (slider only)",
84
+ )
85
+ @click.pass_context
86
+ def configure_rating(
87
+ ctx: click.Context,
88
+ output_file: Path,
89
+ min_value: int,
90
+ max_value: int,
91
+ step: int,
92
+ min_label: str,
93
+ max_label: str,
94
+ show_numeric_labels: bool,
95
+ required: bool,
96
+ ) -> None:
97
+ r"""Configure rating scale trial parameters.
98
+
99
+ Creates a configuration file for rating scale trials (Likert or slider).
100
+ This configuration can be used when generating jsPsych experiments.
101
+
102
+ Parameters
103
+ ----------
104
+ ctx : click.Context
105
+ Click context object.
106
+ output_file : Path
107
+ Output path for configuration JSON file.
108
+ min_value : int
109
+ Minimum value of the rating scale.
110
+ max_value : int
111
+ Maximum value of the rating scale.
112
+ step : int
113
+ Step size for the scale.
114
+ min_label : str
115
+ Label for minimum value.
116
+ max_label : str
117
+ Label for maximum value.
118
+ show_numeric_labels : bool
119
+ Show numeric labels on buttons (Likert only).
120
+ required : bool
121
+ Require response (slider only).
122
+
123
+ Examples
124
+ --------
125
+ $ bead deployment trials configure-rating rating_config.json
126
+
127
+ $ bead deployment trials configure-rating rating_config.json \\
128
+ --min-value 1 --max-value 5 --step 1 \\
129
+ --min-label "Completely unnatural" \\
130
+ --max-label "Completely natural"
131
+
132
+ $ bead deployment trials configure-rating likert_config.json \\
133
+ --min-value 1 --max-value 7 --show-numeric-labels
134
+ """
135
+ try:
136
+ # Validate parameters
137
+ if min_value >= max_value:
138
+ print_error("min-value must be less than max-value")
139
+ ctx.exit(1)
140
+
141
+ if step <= 0:
142
+ print_error("step must be positive")
143
+ ctx.exit(1)
144
+
145
+ if (max_value - min_value) % step != 0:
146
+ print_error(
147
+ f"Range ({max_value - min_value}) must be divisible by step ({step})"
148
+ )
149
+ ctx.exit(1)
150
+
151
+ # Create configuration
152
+ config: dict[str, Any] = {
153
+ "type": "rating_scale",
154
+ "min_value": min_value,
155
+ "max_value": max_value,
156
+ "step": step,
157
+ "min_label": min_label,
158
+ "max_label": max_label,
159
+ "show_numeric_labels": show_numeric_labels,
160
+ "required": required,
161
+ }
162
+
163
+ # Create output directory if needed
164
+ output_file.parent.mkdir(parents=True, exist_ok=True)
165
+
166
+ # Write configuration
167
+ output_file.write_text(
168
+ json.dumps(config, indent=2, ensure_ascii=False),
169
+ encoding="utf-8",
170
+ )
171
+
172
+ print_success(f"Rating configuration saved: {output_file}")
173
+
174
+ # Show summary table
175
+ table = Table(title="Rating Scale Configuration", show_header=False)
176
+ table.add_column("Parameter", style="cyan")
177
+ table.add_column("Value", style="green")
178
+
179
+ table.add_row("Min Value", str(min_value))
180
+ table.add_row("Max Value", str(max_value))
181
+ table.add_row("Step", str(step))
182
+ table.add_row("Min Label", min_label)
183
+ table.add_row("Max Label", max_label)
184
+ table.add_row("Show Numeric Labels", "Yes" if show_numeric_labels else "No")
185
+ table.add_row("Required", "Yes" if required else "No")
186
+ table.add_row("Scale Points", str((max_value - min_value) // step + 1))
187
+
188
+ console.print(table)
189
+
190
+ except Exception as e:
191
+ print_error(f"Failed to create rating configuration: {e}")
192
+ ctx.exit(1)
193
+
194
+
195
+ @click.command()
196
+ @click.argument("output_file", type=click.Path(path_type=Path))
197
+ @click.option(
198
+ "--button-html",
199
+ default='<button class="jspsych-btn">%choice%</button>',
200
+ help="HTML template for choice buttons (%choice% is replaced with option text)",
201
+ )
202
+ @click.option(
203
+ "--enable-keyboard",
204
+ is_flag=True,
205
+ default=True,
206
+ help="Enable keyboard shortcuts for choices",
207
+ )
208
+ @click.option(
209
+ "--randomize-position",
210
+ is_flag=True,
211
+ default=False,
212
+ help="Randomize position of choices (for forced choice)",
213
+ )
214
+ @click.pass_context
215
+ def configure_choice(
216
+ ctx: click.Context,
217
+ output_file: Path,
218
+ button_html: str,
219
+ enable_keyboard: bool,
220
+ randomize_position: bool,
221
+ ) -> None:
222
+ r"""Configure choice trial parameters.
223
+
224
+ Creates a configuration file for choice trials (binary or forced choice).
225
+ This configuration can be used when generating jsPsych experiments.
226
+
227
+ Parameters
228
+ ----------
229
+ ctx : click.Context
230
+ Click context object.
231
+ output_file : Path
232
+ Output path for configuration JSON file.
233
+ button_html : str
234
+ HTML template for choice buttons.
235
+ enable_keyboard : bool
236
+ Enable keyboard shortcuts for choices.
237
+ randomize_position : bool
238
+ Randomize position of choices.
239
+
240
+ Examples
241
+ --------
242
+ $ bead deployment trials configure-choice choice_config.json
243
+
244
+ $ bead deployment trials configure-choice choice_config.json \\
245
+ --button-html '<button class="my-btn">%choice%</button>' \\
246
+ --randomize-position
247
+
248
+ $ bead deployment trials configure-choice binary_config.json \\
249
+ --button-html '<button class="yes-no-btn">%choice%</button>' \\
250
+ --enable-keyboard
251
+ """
252
+ try:
253
+ # Validate button HTML
254
+ if "%choice%" not in button_html:
255
+ print_error("button-html must contain %choice% placeholder")
256
+ ctx.exit(1)
257
+
258
+ # Create configuration
259
+ config: dict[str, Any] = {
260
+ "type": "choice",
261
+ "button_html": button_html,
262
+ "enable_keyboard": enable_keyboard,
263
+ "randomize_position": randomize_position,
264
+ }
265
+
266
+ # Create output directory if needed
267
+ output_file.parent.mkdir(parents=True, exist_ok=True)
268
+
269
+ # Write configuration
270
+ output_file.write_text(
271
+ json.dumps(config, indent=2, ensure_ascii=False),
272
+ encoding="utf-8",
273
+ )
274
+
275
+ print_success(f"Choice configuration saved: {output_file}")
276
+
277
+ # Show summary
278
+ kb_status = "Enabled" if enable_keyboard else "Disabled"
279
+ rand_status = "Yes" if randomize_position else "No"
280
+ summary_panel = Panel(
281
+ f"[cyan]Button HTML:[/cyan]\n{button_html}\n\n"
282
+ f"[cyan]Keyboard Shortcuts:[/cyan] {kb_status}\n"
283
+ f"[cyan]Randomize Position:[/cyan] {rand_status}",
284
+ title="[bold]Choice Configuration Summary[/bold]",
285
+ border_style="green",
286
+ )
287
+ console.print(summary_panel)
288
+
289
+ except Exception as e:
290
+ print_error(f"Failed to create choice configuration: {e}")
291
+ ctx.exit(1)
292
+
293
+
294
+ @click.command()
295
+ @click.argument("output_file", type=click.Path(path_type=Path))
296
+ @click.option(
297
+ "--duration-ms",
298
+ type=int,
299
+ help="Duration each chunk is displayed (milliseconds)",
300
+ )
301
+ @click.option(
302
+ "--isi-ms",
303
+ type=int,
304
+ default=0,
305
+ help="Inter-stimulus interval between chunks (milliseconds, default: 0)",
306
+ )
307
+ @click.option(
308
+ "--timeout-ms",
309
+ type=int,
310
+ help="Maximum time to wait for response (milliseconds, optional)",
311
+ )
312
+ @click.option(
313
+ "--mask-char",
314
+ default="",
315
+ help="Character to use for masking hidden text (e.g., '#')",
316
+ )
317
+ @click.option(
318
+ "--cumulative",
319
+ is_flag=True,
320
+ default=False,
321
+ help="Display chunks cumulatively (each chunk remains visible)",
322
+ )
323
+ @click.pass_context
324
+ def configure_timing(
325
+ ctx: click.Context,
326
+ output_file: Path,
327
+ duration_ms: int | None,
328
+ isi_ms: int,
329
+ timeout_ms: int | None,
330
+ mask_char: str,
331
+ cumulative: bool,
332
+ ) -> None:
333
+ r"""Configure timing parameters for trials.
334
+
335
+ Creates a configuration file for presentation timing (e.g., self-paced
336
+ reading, RSVP). This configuration can be used when generating jsPsych
337
+ experiments with timed presentation.
338
+
339
+ Parameters
340
+ ----------
341
+ ctx : click.Context
342
+ Click context object.
343
+ output_file : Path
344
+ Output path for configuration JSON file.
345
+ duration_ms : int | None
346
+ Duration each chunk is displayed (milliseconds).
347
+ isi_ms : int
348
+ Inter-stimulus interval between chunks (milliseconds).
349
+ timeout_ms : int | None
350
+ Maximum time to wait for response (milliseconds).
351
+ mask_char : str
352
+ Character to use for masking hidden text.
353
+ cumulative : bool
354
+ Display chunks cumulatively.
355
+
356
+ Examples
357
+ --------
358
+ $ bead deployment trials configure-timing timing_config.json \\
359
+ --duration-ms 500 --isi-ms 100
360
+
361
+ $ bead deployment trials configure-timing rsvp_config.json \\
362
+ --duration-ms 300 --isi-ms 50 --mask-char "#"
363
+
364
+ $ bead deployment trials configure-timing spr_config.json \\
365
+ --isi-ms 0 --cumulative --mask-char "#"
366
+ """
367
+ try:
368
+ # Validate parameters
369
+ if isi_ms < 0:
370
+ print_error("isi-ms must be non-negative")
371
+ ctx.exit(1)
372
+
373
+ if duration_ms is not None and duration_ms <= 0:
374
+ print_error("duration-ms must be positive")
375
+ ctx.exit(1)
376
+
377
+ if timeout_ms is not None and timeout_ms <= 0:
378
+ print_error("timeout-ms must be positive")
379
+ ctx.exit(1)
380
+
381
+ # Create configuration
382
+ config: dict[str, Any] = {
383
+ "type": "timing",
384
+ "isi_ms": isi_ms,
385
+ "cumulative": cumulative,
386
+ }
387
+
388
+ if duration_ms is not None:
389
+ config["duration_ms"] = duration_ms
390
+
391
+ if timeout_ms is not None:
392
+ config["timeout_ms"] = timeout_ms
393
+
394
+ if mask_char:
395
+ config["mask_char"] = mask_char
396
+
397
+ # Create output directory if needed
398
+ output_file.parent.mkdir(parents=True, exist_ok=True)
399
+
400
+ # Write configuration
401
+ output_file.write_text(
402
+ json.dumps(config, indent=2, ensure_ascii=False),
403
+ encoding="utf-8",
404
+ )
405
+
406
+ print_success(f"Timing configuration saved: {output_file}")
407
+
408
+ # Show summary table
409
+ table = Table(title="Timing Configuration", show_header=False)
410
+ table.add_column("Parameter", style="cyan")
411
+ table.add_column("Value", style="green")
412
+
413
+ if duration_ms is not None:
414
+ table.add_row("Duration", f"{duration_ms} ms")
415
+ else:
416
+ table.add_row("Duration", "Self-paced")
417
+
418
+ table.add_row("ISI", f"{isi_ms} ms")
419
+
420
+ if timeout_ms is not None:
421
+ table.add_row("Timeout", f"{timeout_ms} ms")
422
+
423
+ if mask_char:
424
+ table.add_row("Mask Character", f"'{mask_char}'")
425
+
426
+ table.add_row("Cumulative", "Yes" if cumulative else "No")
427
+
428
+ console.print(table)
429
+
430
+ except Exception as e:
431
+ print_error(f"Failed to create timing configuration: {e}")
432
+ ctx.exit(1)
433
+
434
+
435
+ @click.command()
436
+ @click.argument("config_files", nargs=-1, type=click.Path(exists=True, path_type=Path))
437
+ @click.pass_context
438
+ def show_config(ctx: click.Context, config_files: tuple[Path, ...]) -> None:
439
+ """Display trial configuration files.
440
+
441
+ Shows the contents of one or more trial configuration JSON files
442
+ in a formatted table.
443
+
444
+ Parameters
445
+ ----------
446
+ ctx : click.Context
447
+ Click context object.
448
+ config_files : tuple[Path, ...]
449
+ Paths to configuration files to display.
450
+
451
+ Examples
452
+ --------
453
+ $ bead deployment trials show-config rating_config.json
454
+
455
+ $ bead deployment trials show-config rating_config.json choice_config.json
456
+ """
457
+ try:
458
+ if not config_files:
459
+ print_error("No configuration files specified")
460
+ ctx.exit(1)
461
+
462
+ for config_file in config_files:
463
+ # Load configuration
464
+ config = json.loads(config_file.read_text(encoding="utf-8"))
465
+
466
+ # Create table
467
+ table = Table(
468
+ title=f"Configuration: {config_file.name}",
469
+ show_header=False,
470
+ )
471
+ table.add_column("Parameter", style="cyan")
472
+ table.add_column("Value", style="green")
473
+
474
+ # Display all key-value pairs
475
+ for key, value in sorted(config.items()):
476
+ table.add_row(key, str(value))
477
+
478
+ console.print(table)
479
+ console.print() # Blank line between tables
480
+
481
+ except json.JSONDecodeError as e:
482
+ print_error(f"Invalid JSON in configuration file: {e}")
483
+ ctx.exit(1)
484
+ except Exception as e:
485
+ print_error(f"Failed to show configuration: {e}")
486
+ ctx.exit(1)
487
+
488
+
489
+ # Register commands
490
+ deployment_trials.add_command(configure_rating)
491
+ deployment_trials.add_command(configure_choice)
492
+ deployment_trials.add_command(configure_timing)
493
+ deployment_trials.add_command(show_config)