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,411 @@
1
+ """Material Design CSS generation for jsPsych experiments.
2
+
3
+ This module provides the MaterialDesignStylesheet class for generating
4
+ Material Design 3 CSS for bead jsPsych experiments.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Literal
10
+
11
+
12
+ class MaterialDesignStylesheet:
13
+ """Generator for Material Design 3 CSS.
14
+
15
+ Generates Material Design 3 compliant CSS for jsPsych experiments.
16
+
17
+ Features
18
+ --------
19
+ - Color theming (light/dark/auto)
20
+ - Typography (Roboto font family)
21
+ - Elevation (shadows)
22
+ - Ripple effects for buttons
23
+ - Form controls (inputs, dropdowns, radio buttons)
24
+
25
+ See Also
26
+ --------
27
+ generate_css : Generate complete Material Design CSS with theme options.
28
+
29
+ Examples
30
+ --------
31
+ >>> stylesheet = MaterialDesignStylesheet()
32
+ >>> css = stylesheet.generate_css(theme="light")
33
+ >>> print(css[:100])
34
+ """
35
+
36
+ def __init__(self) -> None:
37
+ pass
38
+
39
+ def generate_css(
40
+ self,
41
+ theme: Literal["light", "dark", "auto"] = "light",
42
+ primary_color: str = "#6200EE",
43
+ secondary_color: str = "#03DAC6",
44
+ ) -> str:
45
+ """Generate complete Material Design CSS.
46
+
47
+ Parameters
48
+ ----------
49
+ theme : Literal["light", "dark", "auto"]
50
+ Color theme (light, dark, or auto).
51
+ primary_color : str
52
+ Primary color as hex code.
53
+ secondary_color : str
54
+ Secondary color as hex code.
55
+
56
+ Returns
57
+ -------
58
+ str
59
+ Complete CSS stylesheet as string.
60
+
61
+ Examples
62
+ --------
63
+ >>> stylesheet = MaterialDesignStylesheet()
64
+ >>> css = stylesheet.generate_css(theme="light", primary_color="#1976D2")
65
+ >>> "--primary-color" in css
66
+ True
67
+ """
68
+ # determine theme colors
69
+ if theme == "light":
70
+ background = "#FFFFFF"
71
+ surface = "#FFFFFF"
72
+ on_surface = "#000000"
73
+ on_primary = "#FFFFFF"
74
+ elif theme == "dark":
75
+ background = "#121212"
76
+ surface = "#1E1E1E"
77
+ on_surface = "#FFFFFF"
78
+ on_primary = "#000000"
79
+ else: # auto
80
+ # use CSS media query for system preference
81
+ background = "#FFFFFF"
82
+ surface = "#FFFFFF"
83
+ on_surface = "#000000"
84
+ on_primary = "#FFFFFF"
85
+
86
+ css = f"""
87
+ /* Material Design 3 Stylesheet for bead jsPsych Experiments */
88
+ /* Theme: {theme} */
89
+
90
+ @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
91
+
92
+ :root {{
93
+ /* Color palette */
94
+ --primary-color: {primary_color};
95
+ --secondary-color: {secondary_color};
96
+ --background: {background};
97
+ --surface: {surface};
98
+ --on-surface: {on_surface};
99
+ --on-primary: {on_primary};
100
+ --error: #B00020;
101
+ --on-error: #FFFFFF;
102
+
103
+ /* Typography */
104
+ --font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
105
+ --font-size-body: 16px;
106
+ --font-size-title: 24px;
107
+ --font-size-label: 14px;
108
+
109
+ /* Spacing */
110
+ --spacing-xs: 4px;
111
+ --spacing-sm: 8px;
112
+ --spacing-md: 16px;
113
+ --spacing-lg: 24px;
114
+ --spacing-xl: 32px;
115
+
116
+ /* Elevation shadows */
117
+ --elevation-1: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
118
+ --elevation-2: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
119
+ --elevation-3: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
120
+
121
+ /* Border radius */
122
+ --radius-sm: 4px;
123
+ --radius-md: 8px;
124
+ --radius-lg: 12px;
125
+ --radius-full: 9999px;
126
+ }}
127
+
128
+ /* Dark theme media query for auto mode */
129
+ @media (prefers-color-scheme: dark) {{
130
+ :root {{
131
+ --background: #121212;
132
+ --surface: #1E1E1E;
133
+ --on-surface: #FFFFFF;
134
+ --on-primary: #000000;
135
+ }}
136
+ }}
137
+
138
+ /* Base styles */
139
+ body {{
140
+ font-family: var(--font-family);
141
+ font-size: var(--font-size-body);
142
+ color: var(--on-surface);
143
+ background-color: var(--background);
144
+ margin: 0;
145
+ padding: 0;
146
+ line-height: 1.5;
147
+ }}
148
+
149
+ /* Button styles */
150
+ .bead-button {{
151
+ font-family: var(--font-family);
152
+ font-size: var(--font-size-body);
153
+ font-weight: 500;
154
+ padding: 10px 24px;
155
+ border: none;
156
+ border-radius: var(--radius-sm);
157
+ background-color: var(--primary-color);
158
+ color: var(--on-primary);
159
+ cursor: pointer;
160
+ box-shadow: var(--elevation-2);
161
+ transition: box-shadow 0.2s, background-color 0.2s;
162
+ text-transform: uppercase;
163
+ letter-spacing: 0.5px;
164
+ }}
165
+
166
+ .bead-button:hover {{
167
+ box-shadow: var(--elevation-3);
168
+ }}
169
+
170
+ .bead-button:active {{
171
+ box-shadow: var(--elevation-1);
172
+ }}
173
+
174
+ .bead-button:disabled {{
175
+ background-color: rgba(0, 0, 0, 0.12);
176
+ color: rgba(0, 0, 0, 0.38);
177
+ box-shadow: none;
178
+ cursor: not-allowed;
179
+ }}
180
+
181
+ /* Rating scale styles */
182
+ .bead-rating-container {{
183
+ max-width: 800px;
184
+ margin: 0 auto;
185
+ padding: var(--spacing-xl);
186
+ }}
187
+
188
+ .bead-rating-prompt {{
189
+ font-size: var(--font-size-title);
190
+ font-weight: 500;
191
+ margin-bottom: var(--spacing-lg);
192
+ text-align: center;
193
+ }}
194
+
195
+ .bead-rating-scale {{
196
+ display: flex;
197
+ justify-content: center;
198
+ gap: var(--spacing-sm);
199
+ margin: var(--spacing-lg) 0;
200
+ }}
201
+
202
+ .bead-rating-option {{
203
+ display: flex;
204
+ flex-direction: column;
205
+ align-items: center;
206
+ gap: var(--spacing-xs);
207
+ }}
208
+
209
+ .bead-rating-button {{
210
+ width: 48px;
211
+ height: 48px;
212
+ border: 2px solid var(--primary-color);
213
+ border-radius: var(--radius-full);
214
+ background-color: transparent;
215
+ color: var(--primary-color);
216
+ font-size: var(--font-size-body);
217
+ font-weight: 500;
218
+ cursor: pointer;
219
+ transition: background-color 0.2s, color 0.2s;
220
+ }}
221
+
222
+ .bead-rating-button:hover {{
223
+ background-color: rgba(98, 0, 238, 0.08);
224
+ }}
225
+
226
+ .bead-rating-button.selected {{
227
+ background-color: var(--primary-color);
228
+ color: var(--on-primary);
229
+ }}
230
+
231
+ .bead-rating-label {{
232
+ font-size: var(--font-size-label);
233
+ color: var(--on-surface);
234
+ text-align: center;
235
+ max-width: 80px;
236
+ }}
237
+
238
+ .bead-rating-button-container {{
239
+ display: flex;
240
+ justify-content: center;
241
+ margin-top: var(--spacing-xl);
242
+ }}
243
+
244
+ /* Text field styles */
245
+ .bead-text-field {{
246
+ font-family: var(--font-family);
247
+ font-size: var(--font-size-body);
248
+ padding: 12px 16px;
249
+ border: 1px solid rgba(0, 0, 0, 0.38);
250
+ border-radius: var(--radius-sm);
251
+ background-color: transparent;
252
+ color: var(--on-surface);
253
+ transition: border-color 0.2s;
254
+ }}
255
+
256
+ .bead-text-field:focus {{
257
+ outline: none;
258
+ border-color: var(--primary-color);
259
+ border-width: 2px;
260
+ }}
261
+
262
+ /* Dropdown styles */
263
+ .bead-dropdown {{
264
+ font-family: var(--font-family);
265
+ font-size: var(--font-size-body);
266
+ padding: 12px 16px;
267
+ border: 1px solid rgba(0, 0, 0, 0.38);
268
+ border-radius: var(--radius-sm);
269
+ background-color: var(--surface);
270
+ color: var(--on-surface);
271
+ cursor: pointer;
272
+ transition: border-color 0.2s;
273
+ }}
274
+
275
+ .bead-dropdown:focus {{
276
+ outline: none;
277
+ border-color: var(--primary-color);
278
+ border-width: 2px;
279
+ }}
280
+
281
+ /* Radio group styles */
282
+ .bead-radio-group {{
283
+ display: flex;
284
+ flex-direction: column;
285
+ gap: var(--spacing-sm);
286
+ }}
287
+
288
+ .bead-radio-option {{
289
+ display: flex;
290
+ align-items: center;
291
+ gap: var(--spacing-sm);
292
+ padding: var(--spacing-sm);
293
+ border-radius: var(--radius-sm);
294
+ cursor: pointer;
295
+ transition: background-color 0.2s;
296
+ }}
297
+
298
+ .bead-radio-option:hover {{
299
+ background-color: rgba(0, 0, 0, 0.04);
300
+ }}
301
+
302
+ /* Card styles */
303
+ .bead-card {{
304
+ background-color: var(--surface);
305
+ border-radius: var(--radius-md);
306
+ padding: var(--spacing-md);
307
+ box-shadow: var(--elevation-1);
308
+ transition: box-shadow 0.2s;
309
+ }}
310
+
311
+ .bead-card:hover {{
312
+ box-shadow: var(--elevation-2);
313
+ }}
314
+
315
+ /* Progress indicator styles */
316
+ .bead-progress {{
317
+ width: 100%;
318
+ height: 4px;
319
+ background-color: rgba(98, 0, 238, 0.12);
320
+ position: fixed;
321
+ top: 0;
322
+ left: 0;
323
+ z-index: 1000;
324
+ }}
325
+
326
+ .bead-progress-bar {{
327
+ height: 100%;
328
+ background-color: var(--primary-color);
329
+ transition: width 0.3s;
330
+ }}
331
+
332
+ /* Cloze task styles */
333
+ .bead-cloze-container {{
334
+ max-width: 900px;
335
+ margin: 0 auto;
336
+ padding: var(--spacing-xl);
337
+ }}
338
+
339
+ .bead-cloze-text {{
340
+ font-size: var(--font-size-body);
341
+ line-height: 2;
342
+ margin: var(--spacing-lg) 0;
343
+ }}
344
+
345
+ .bead-cloze-field {{
346
+ min-width: 120px;
347
+ margin: 0 var(--spacing-xs);
348
+ display: inline-block;
349
+ }}
350
+
351
+ .bead-cloze-button-container {{
352
+ display: flex;
353
+ justify-content: center;
354
+ margin-top: var(--spacing-xl);
355
+ }}
356
+
357
+ /* Forced choice styles */
358
+ .bead-forced-choice-container {{
359
+ max-width: 1000px;
360
+ margin: 0 auto;
361
+ padding: var(--spacing-xl);
362
+ }}
363
+
364
+ .bead-forced-choice-prompt {{
365
+ font-size: var(--font-size-title);
366
+ font-weight: 500;
367
+ margin-bottom: var(--spacing-lg);
368
+ text-align: center;
369
+ }}
370
+
371
+ .bead-forced-choice-alternatives {{
372
+ display: grid;
373
+ grid-template-columns: 1fr 1fr;
374
+ gap: var(--spacing-lg);
375
+ margin: var(--spacing-lg) 0;
376
+ }}
377
+
378
+ .bead-alternative {{
379
+ padding: var(--spacing-lg);
380
+ transition: transform 0.2s, box-shadow 0.2s;
381
+ }}
382
+
383
+ .bead-alternative:hover {{
384
+ transform: translateY(-2px);
385
+ }}
386
+
387
+ .bead-alternative.selected {{
388
+ border: 2px solid var(--primary-color);
389
+ box-shadow: var(--elevation-3);
390
+ }}
391
+
392
+ .bead-alternative-label {{
393
+ font-size: var(--font-size-label);
394
+ font-weight: 700;
395
+ color: var(--primary-color);
396
+ text-transform: uppercase;
397
+ letter-spacing: 1px;
398
+ margin-bottom: var(--spacing-sm);
399
+ }}
400
+
401
+ .bead-alternative-content {{
402
+ font-size: var(--font-size-body);
403
+ margin-bottom: var(--spacing-md);
404
+ min-height: 60px;
405
+ }}
406
+
407
+ .bead-choice-button {{
408
+ width: 100%;
409
+ }}
410
+ """
411
+ return css
bead/dsl/__init__.py ADDED
@@ -0,0 +1,80 @@
1
+ """Domain-Specific Language (DSL) for constraint expressions.
2
+
3
+ Supports boolean operators (and, or, not), comparison operators, membership
4
+ tests, arithmetic, function calls, attribute access, and list literals.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from bead.dsl.ast import (
10
+ ASTNode,
11
+ AttributeAccess,
12
+ BinaryOp,
13
+ FunctionCall,
14
+ ListLiteral,
15
+ Literal,
16
+ UnaryOp,
17
+ Variable,
18
+ )
19
+ from bead.dsl.context import EvaluationContext
20
+ from bead.dsl.errors import DSLError, EvaluationError, ParseError
21
+ from bead.dsl.evaluator import Evaluator
22
+ from bead.dsl.parser import parse
23
+ from bead.dsl.stdlib import SIMULATION_FUNCTIONS, STDLIB_FUNCTIONS, register_stdlib
24
+
25
+ __all__ = [
26
+ # AST nodes
27
+ "ASTNode",
28
+ "Literal",
29
+ "Variable",
30
+ "BinaryOp",
31
+ "UnaryOp",
32
+ "FunctionCall",
33
+ "ListLiteral",
34
+ "AttributeAccess",
35
+ # Errors
36
+ "DSLError",
37
+ "ParseError",
38
+ "EvaluationError",
39
+ # Parser
40
+ "parse",
41
+ # Evaluation
42
+ "Evaluator",
43
+ "EvaluationContext",
44
+ "evaluate",
45
+ # Standard library
46
+ "STDLIB_FUNCTIONS",
47
+ "SIMULATION_FUNCTIONS",
48
+ "register_stdlib",
49
+ ]
50
+
51
+
52
+ # Convenience function
53
+ def evaluate(node: ASTNode, context: EvaluationContext, use_cache: bool = True) -> Any:
54
+ """Evaluate a constraint expression.
55
+
56
+ Parameters
57
+ ----------
58
+ node : ASTNode
59
+ Parsed AST node to evaluate.
60
+ context : EvaluationContext
61
+ Evaluation context with variables and functions.
62
+ use_cache : bool
63
+ Whether to use caching.
64
+
65
+ Returns
66
+ -------
67
+ Any
68
+ Result of evaluation.
69
+
70
+ Examples
71
+ --------
72
+ >>> from bead.dsl import parse, EvaluationContext
73
+ >>> ctx = EvaluationContext()
74
+ >>> ctx.set_variable("x", 10)
75
+ >>> node = parse("x > 5")
76
+ >>> evaluate(node, ctx)
77
+ True
78
+ """
79
+ evaluator = Evaluator(use_cache=use_cache)
80
+ return evaluator.evaluate(node, context)
bead/dsl/ast.py ADDED
@@ -0,0 +1,168 @@
1
+ """Abstract Syntax Tree node definitions for constraint DSL.
2
+
3
+ This module defines the AST nodes that represent parsed constraint expressions.
4
+ Each node type corresponds to a construct in the constraint DSL grammar.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from bead.data.base import BeadBaseModel
10
+
11
+
12
+ class ASTNode(BeadBaseModel):
13
+ """Base class for all AST nodes.
14
+
15
+ All AST nodes inherit from BeadBaseModel to get:
16
+ - Automatic validation
17
+ - Serialization support
18
+ - Metadata tracking
19
+ """
20
+
21
+ pass
22
+
23
+
24
+ class Literal(ASTNode):
25
+ """Literal value node (string, number, boolean).
26
+
27
+ Examples
28
+ --------
29
+ >>> node = Literal(value="hello")
30
+ >>> node.value
31
+ 'hello'
32
+ >>> node = Literal(value=42)
33
+ >>> node.value
34
+ 42
35
+ """
36
+
37
+ value: str | int | float | bool
38
+
39
+
40
+ class Variable(ASTNode):
41
+ """Variable reference node.
42
+
43
+ References a variable in the evaluation context (e.g., item attributes).
44
+
45
+ Examples
46
+ --------
47
+ >>> node = Variable(name="lemma")
48
+ >>> node.name
49
+ 'lemma'
50
+ """
51
+
52
+ name: str
53
+
54
+
55
+ class BinaryOp(ASTNode):
56
+ """Binary operation node.
57
+
58
+ Represents operations like: a == b, x > y, p and q
59
+
60
+ Attributes
61
+ ----------
62
+ operator : str
63
+ The operator (==, !=, <, >, <=, >=, and, or, in, etc.)
64
+ left : ASTNode
65
+ Left operand
66
+ right : ASTNode
67
+ Right operand
68
+
69
+ Examples
70
+ --------
71
+ >>> left = Variable(name="pos")
72
+ >>> right = Literal(value="VERB")
73
+ >>> node = BinaryOp(operator="==", left=left, right=right)
74
+ >>> node.operator
75
+ '=='
76
+ """
77
+
78
+ operator: str
79
+ left: ASTNode
80
+ right: ASTNode
81
+
82
+
83
+ class UnaryOp(ASTNode):
84
+ """Unary operation node.
85
+
86
+ Represents operations like: not x, -y
87
+
88
+ Examples
89
+ --------
90
+ >>> operand = Variable(name="is_transitive")
91
+ >>> node = UnaryOp(operator="not", operand=operand)
92
+ >>> node.operator
93
+ 'not'
94
+ """
95
+
96
+ operator: str
97
+ operand: ASTNode
98
+
99
+
100
+ class FunctionCall(ASTNode):
101
+ """Function call node.
102
+
103
+ Represents function calls and method calls like:
104
+ - len(x), startswith("pre")
105
+ - obj.method(arg)
106
+
107
+ Examples
108
+ --------
109
+ >>> func = Variable(name="len")
110
+ >>> arg = Variable(name="lemma")
111
+ >>> node = FunctionCall(function=func, arguments=[arg])
112
+ >>> node.function.name
113
+ 'len'
114
+ """
115
+
116
+ function: ASTNode # Variable for functions, AttributeAccess for methods
117
+ arguments: list[ASTNode]
118
+
119
+
120
+ class ListLiteral(ASTNode):
121
+ """List literal node.
122
+
123
+ Represents list literals like: ["a", "b", "c"]
124
+
125
+ Examples
126
+ --------
127
+ >>> elements = [Literal(value="a"), Literal(value="b")]
128
+ >>> node = ListLiteral(elements=elements)
129
+ >>> len(node.elements)
130
+ 2
131
+ """
132
+
133
+ elements: list[ASTNode]
134
+
135
+
136
+ class AttributeAccess(ASTNode):
137
+ """Attribute access node.
138
+
139
+ Represents attribute access like: item.lemma, obj.property
140
+
141
+ Examples
142
+ --------
143
+ >>> obj = Variable(name="item")
144
+ >>> node = AttributeAccess(object=obj, attribute="lemma")
145
+ >>> node.attribute
146
+ 'lemma'
147
+ """
148
+
149
+ object: ASTNode
150
+ attribute: str
151
+
152
+
153
+ class Subscript(ASTNode):
154
+ """Subscript access node.
155
+
156
+ Represents subscript access like: item['key'], obj[0]
157
+
158
+ Examples
159
+ --------
160
+ >>> obj = Variable(name="item")
161
+ >>> key = Literal(value="key")
162
+ >>> node = Subscript(object=obj, index=key)
163
+ >>> node.index.value
164
+ 'key'
165
+ """
166
+
167
+ object: ASTNode
168
+ index: ASTNode