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.
- bead/__init__.py +11 -0
- bead/__main__.py +11 -0
- bead/active_learning/__init__.py +15 -0
- bead/active_learning/config.py +231 -0
- bead/active_learning/loop.py +566 -0
- bead/active_learning/models/__init__.py +24 -0
- bead/active_learning/models/base.py +852 -0
- bead/active_learning/models/binary.py +910 -0
- bead/active_learning/models/categorical.py +943 -0
- bead/active_learning/models/cloze.py +862 -0
- bead/active_learning/models/forced_choice.py +956 -0
- bead/active_learning/models/free_text.py +773 -0
- bead/active_learning/models/lora.py +365 -0
- bead/active_learning/models/magnitude.py +835 -0
- bead/active_learning/models/multi_select.py +795 -0
- bead/active_learning/models/ordinal_scale.py +811 -0
- bead/active_learning/models/peft_adapter.py +155 -0
- bead/active_learning/models/random_effects.py +639 -0
- bead/active_learning/selection.py +354 -0
- bead/active_learning/strategies.py +391 -0
- bead/active_learning/trainers/__init__.py +26 -0
- bead/active_learning/trainers/base.py +210 -0
- bead/active_learning/trainers/data_collator.py +172 -0
- bead/active_learning/trainers/dataset_utils.py +261 -0
- bead/active_learning/trainers/huggingface.py +304 -0
- bead/active_learning/trainers/lightning.py +324 -0
- bead/active_learning/trainers/metrics.py +424 -0
- bead/active_learning/trainers/mixed_effects.py +551 -0
- bead/active_learning/trainers/model_wrapper.py +509 -0
- bead/active_learning/trainers/registry.py +104 -0
- bead/adapters/__init__.py +11 -0
- bead/adapters/huggingface.py +61 -0
- bead/behavioral/__init__.py +116 -0
- bead/behavioral/analytics.py +646 -0
- bead/behavioral/extraction.py +343 -0
- bead/behavioral/merging.py +343 -0
- bead/cli/__init__.py +11 -0
- bead/cli/active_learning.py +513 -0
- bead/cli/active_learning_commands.py +779 -0
- bead/cli/completion.py +359 -0
- bead/cli/config.py +624 -0
- bead/cli/constraint_builders.py +286 -0
- bead/cli/deployment.py +859 -0
- bead/cli/deployment_trials.py +493 -0
- bead/cli/deployment_ui.py +332 -0
- bead/cli/display.py +378 -0
- bead/cli/items.py +960 -0
- bead/cli/items_factories.py +776 -0
- bead/cli/list_constraints.py +714 -0
- bead/cli/lists.py +490 -0
- bead/cli/main.py +430 -0
- bead/cli/models.py +877 -0
- bead/cli/resource_loaders.py +621 -0
- bead/cli/resources.py +1036 -0
- bead/cli/shell.py +356 -0
- bead/cli/simulate.py +840 -0
- bead/cli/templates.py +1158 -0
- bead/cli/training.py +1080 -0
- bead/cli/utils.py +614 -0
- bead/cli/workflow.py +1273 -0
- bead/config/__init__.py +68 -0
- bead/config/active_learning.py +1009 -0
- bead/config/config.py +192 -0
- bead/config/defaults.py +118 -0
- bead/config/deployment.py +217 -0
- bead/config/env.py +147 -0
- bead/config/item.py +45 -0
- bead/config/list.py +193 -0
- bead/config/loader.py +149 -0
- bead/config/logging.py +42 -0
- bead/config/model.py +49 -0
- bead/config/paths.py +46 -0
- bead/config/profiles.py +320 -0
- bead/config/resources.py +47 -0
- bead/config/serialization.py +210 -0
- bead/config/simulation.py +206 -0
- bead/config/template.py +238 -0
- bead/config/validation.py +267 -0
- bead/data/__init__.py +65 -0
- bead/data/base.py +87 -0
- bead/data/identifiers.py +97 -0
- bead/data/language_codes.py +61 -0
- bead/data/metadata.py +270 -0
- bead/data/range.py +123 -0
- bead/data/repository.py +358 -0
- bead/data/serialization.py +249 -0
- bead/data/timestamps.py +89 -0
- bead/data/validation.py +349 -0
- bead/data_collection/__init__.py +11 -0
- bead/data_collection/jatos.py +223 -0
- bead/data_collection/merger.py +154 -0
- bead/data_collection/prolific.py +198 -0
- bead/deployment/__init__.py +5 -0
- bead/deployment/distribution.py +402 -0
- bead/deployment/jatos/__init__.py +1 -0
- bead/deployment/jatos/api.py +200 -0
- bead/deployment/jatos/exporter.py +210 -0
- bead/deployment/jspsych/__init__.py +9 -0
- bead/deployment/jspsych/biome.json +44 -0
- bead/deployment/jspsych/config.py +411 -0
- bead/deployment/jspsych/generator.py +598 -0
- bead/deployment/jspsych/package.json +51 -0
- bead/deployment/jspsych/pnpm-lock.yaml +2141 -0
- bead/deployment/jspsych/randomizer.py +299 -0
- bead/deployment/jspsych/src/lib/list-distributor.test.ts +327 -0
- bead/deployment/jspsych/src/lib/list-distributor.ts +1282 -0
- bead/deployment/jspsych/src/lib/randomizer.test.ts +232 -0
- bead/deployment/jspsych/src/lib/randomizer.ts +367 -0
- bead/deployment/jspsych/src/plugins/cloze-dropdown.ts +252 -0
- bead/deployment/jspsych/src/plugins/forced-choice.ts +265 -0
- bead/deployment/jspsych/src/plugins/plugins.test.ts +141 -0
- bead/deployment/jspsych/src/plugins/rating.ts +248 -0
- bead/deployment/jspsych/src/slopit/index.ts +9 -0
- bead/deployment/jspsych/src/types/jatos.d.ts +256 -0
- bead/deployment/jspsych/src/types/jspsych.d.ts +228 -0
- bead/deployment/jspsych/templates/experiment.css +1 -0
- bead/deployment/jspsych/templates/experiment.js.template +289 -0
- bead/deployment/jspsych/templates/index.html +51 -0
- bead/deployment/jspsych/templates/randomizer.js +241 -0
- bead/deployment/jspsych/templates/randomizer.js.template +313 -0
- bead/deployment/jspsych/trials.py +723 -0
- bead/deployment/jspsych/tsconfig.json +23 -0
- bead/deployment/jspsych/tsup.config.ts +30 -0
- bead/deployment/jspsych/ui/__init__.py +1 -0
- bead/deployment/jspsych/ui/components.py +383 -0
- bead/deployment/jspsych/ui/styles.py +411 -0
- bead/dsl/__init__.py +80 -0
- bead/dsl/ast.py +168 -0
- bead/dsl/context.py +178 -0
- bead/dsl/errors.py +71 -0
- bead/dsl/evaluator.py +570 -0
- bead/dsl/grammar.lark +81 -0
- bead/dsl/parser.py +231 -0
- bead/dsl/stdlib.py +929 -0
- bead/evaluation/__init__.py +13 -0
- bead/evaluation/convergence.py +485 -0
- bead/evaluation/interannotator.py +398 -0
- bead/items/__init__.py +40 -0
- bead/items/adapters/__init__.py +70 -0
- bead/items/adapters/anthropic.py +224 -0
- bead/items/adapters/api_utils.py +167 -0
- bead/items/adapters/base.py +216 -0
- bead/items/adapters/google.py +259 -0
- bead/items/adapters/huggingface.py +1074 -0
- bead/items/adapters/openai.py +323 -0
- bead/items/adapters/registry.py +202 -0
- bead/items/adapters/sentence_transformers.py +224 -0
- bead/items/adapters/togetherai.py +309 -0
- bead/items/binary.py +515 -0
- bead/items/cache.py +558 -0
- bead/items/categorical.py +593 -0
- bead/items/cloze.py +757 -0
- bead/items/constructor.py +784 -0
- bead/items/forced_choice.py +413 -0
- bead/items/free_text.py +681 -0
- bead/items/generation.py +432 -0
- bead/items/item.py +396 -0
- bead/items/item_template.py +787 -0
- bead/items/magnitude.py +573 -0
- bead/items/multi_select.py +621 -0
- bead/items/ordinal_scale.py +569 -0
- bead/items/scoring.py +448 -0
- bead/items/validation.py +723 -0
- bead/lists/__init__.py +30 -0
- bead/lists/balancer.py +263 -0
- bead/lists/constraints.py +1067 -0
- bead/lists/experiment_list.py +286 -0
- bead/lists/list_collection.py +378 -0
- bead/lists/partitioner.py +1141 -0
- bead/lists/stratification.py +254 -0
- bead/participants/__init__.py +73 -0
- bead/participants/collection.py +699 -0
- bead/participants/merging.py +312 -0
- bead/participants/metadata_spec.py +491 -0
- bead/participants/models.py +276 -0
- bead/resources/__init__.py +29 -0
- bead/resources/adapters/__init__.py +19 -0
- bead/resources/adapters/base.py +104 -0
- bead/resources/adapters/cache.py +128 -0
- bead/resources/adapters/glazing.py +508 -0
- bead/resources/adapters/registry.py +117 -0
- bead/resources/adapters/unimorph.py +796 -0
- bead/resources/classification.py +856 -0
- bead/resources/constraint_builders.py +329 -0
- bead/resources/constraints.py +165 -0
- bead/resources/lexical_item.py +223 -0
- bead/resources/lexicon.py +744 -0
- bead/resources/loaders.py +209 -0
- bead/resources/template.py +441 -0
- bead/resources/template_collection.py +707 -0
- bead/resources/template_generation.py +349 -0
- bead/simulation/__init__.py +29 -0
- bead/simulation/annotators/__init__.py +15 -0
- bead/simulation/annotators/base.py +175 -0
- bead/simulation/annotators/distance_based.py +135 -0
- bead/simulation/annotators/lm_based.py +114 -0
- bead/simulation/annotators/oracle.py +182 -0
- bead/simulation/annotators/random.py +181 -0
- bead/simulation/dsl_extension/__init__.py +3 -0
- bead/simulation/noise_models/__init__.py +13 -0
- bead/simulation/noise_models/base.py +42 -0
- bead/simulation/noise_models/random_noise.py +82 -0
- bead/simulation/noise_models/systematic.py +132 -0
- bead/simulation/noise_models/temperature.py +86 -0
- bead/simulation/runner.py +144 -0
- bead/simulation/strategies/__init__.py +23 -0
- bead/simulation/strategies/base.py +123 -0
- bead/simulation/strategies/binary.py +103 -0
- bead/simulation/strategies/categorical.py +123 -0
- bead/simulation/strategies/cloze.py +224 -0
- bead/simulation/strategies/forced_choice.py +127 -0
- bead/simulation/strategies/free_text.py +105 -0
- bead/simulation/strategies/magnitude.py +116 -0
- bead/simulation/strategies/multi_select.py +129 -0
- bead/simulation/strategies/ordinal_scale.py +131 -0
- bead/templates/__init__.py +27 -0
- bead/templates/adapters/__init__.py +17 -0
- bead/templates/adapters/base.py +128 -0
- bead/templates/adapters/cache.py +178 -0
- bead/templates/adapters/huggingface.py +312 -0
- bead/templates/combinatorics.py +103 -0
- bead/templates/filler.py +605 -0
- bead/templates/renderers.py +177 -0
- bead/templates/resolver.py +178 -0
- bead/templates/strategies.py +1806 -0
- bead/templates/streaming.py +195 -0
- bead-0.1.0.dist-info/METADATA +212 -0
- bead-0.1.0.dist-info/RECORD +231 -0
- bead-0.1.0.dist-info/WHEEL +4 -0
- bead-0.1.0.dist-info/entry_points.txt +2 -0
- bead-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2024",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"exactOptionalPropertyTypes": true,
|
|
9
|
+
"noUncheckedIndexedAccess": true,
|
|
10
|
+
"noImplicitOverride": true,
|
|
11
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"outDir": "dist",
|
|
18
|
+
"rootDir": "src",
|
|
19
|
+
"skipLibCheck": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["src"],
|
|
22
|
+
"exclude": ["node_modules", "dist"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: {
|
|
5
|
+
// Plugins
|
|
6
|
+
"plugins/rating": "src/plugins/rating.ts",
|
|
7
|
+
"plugins/forced-choice": "src/plugins/forced-choice.ts",
|
|
8
|
+
"plugins/cloze-dropdown": "src/plugins/cloze-dropdown.ts",
|
|
9
|
+
// Library
|
|
10
|
+
"lib/list-distributor": "src/lib/list-distributor.ts",
|
|
11
|
+
"lib/randomizer": "src/lib/randomizer.ts",
|
|
12
|
+
// Slopit bundle (behavioral capture)
|
|
13
|
+
"slopit-bundle": "src/slopit/index.ts",
|
|
14
|
+
},
|
|
15
|
+
format: ["esm"],
|
|
16
|
+
dts: false, // browser code, no type exports needed
|
|
17
|
+
sourcemap: true,
|
|
18
|
+
clean: true,
|
|
19
|
+
target: "es2020",
|
|
20
|
+
splitting: false,
|
|
21
|
+
treeshake: true,
|
|
22
|
+
minify: false,
|
|
23
|
+
external: ["jspsych"],
|
|
24
|
+
// Generate IIFE bundles for browser usage
|
|
25
|
+
esbuildOptions(options) {
|
|
26
|
+
options.banner = {
|
|
27
|
+
js: "/* @bead/jspsych-deployment - Generated by bead */",
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""UI components and styles for jsPsych experiments."""
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""Python helpers for generating UI components for jsPsych experiments.
|
|
2
|
+
|
|
3
|
+
This module provides functions to generate UI component configurations
|
|
4
|
+
from bead models, inferring widget types from slot constraints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
from bead.data.base import JsonValue
|
|
12
|
+
from bead.dsl import ast
|
|
13
|
+
from bead.dsl.parser import parse
|
|
14
|
+
from bead.items.item import UnfilledSlot
|
|
15
|
+
from bead.resources.constraints import Constraint
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_rating_scale(
|
|
19
|
+
scale_min: int,
|
|
20
|
+
scale_max: int,
|
|
21
|
+
labels: dict[int, str] | None = None,
|
|
22
|
+
) -> dict[str, int | dict[int, str]]:
|
|
23
|
+
"""Generate jsPsych rating scale configuration.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
scale_min : int
|
|
28
|
+
Minimum value of the scale.
|
|
29
|
+
scale_max : int
|
|
30
|
+
Maximum value of the scale.
|
|
31
|
+
labels : dict[int, str] | None
|
|
32
|
+
Optional labels for specific scale points.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
dict[str, int | dict[int, str]]
|
|
37
|
+
Rating scale configuration dictionary.
|
|
38
|
+
|
|
39
|
+
Examples
|
|
40
|
+
--------
|
|
41
|
+
>>> config = create_rating_scale(
|
|
42
|
+
... 1, 7, {1: "Strongly Disagree", 7: "Strongly Agree"}
|
|
43
|
+
... )
|
|
44
|
+
>>> config["scale_min"]
|
|
45
|
+
1
|
|
46
|
+
>>> config["scale_max"]
|
|
47
|
+
7
|
|
48
|
+
"""
|
|
49
|
+
return {
|
|
50
|
+
"scale_min": scale_min,
|
|
51
|
+
"scale_max": scale_max,
|
|
52
|
+
"scale_labels": labels or {},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def create_cloze_fields(
|
|
57
|
+
unfilled_slots: list[UnfilledSlot],
|
|
58
|
+
constraints: dict[UUID, Constraint],
|
|
59
|
+
lexicon: dict[UUID, str] | None = None,
|
|
60
|
+
) -> list[dict[str, JsonValue]]:
|
|
61
|
+
"""Generate cloze field configurations from slots and constraints.
|
|
62
|
+
|
|
63
|
+
Infers widget type from slot constraints:
|
|
64
|
+
- Constraint with "self.id in [...]" pattern: dropdown with specific items
|
|
65
|
+
- Other DSL constraints: text input with validation expression
|
|
66
|
+
- No constraints: free text input
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
unfilled_slots : list[UnfilledSlot]
|
|
71
|
+
List of unfilled slots in the cloze task.
|
|
72
|
+
constraints : dict[UUID, Constraint]
|
|
73
|
+
Dictionary of constraints keyed by UUID (from slot.constraint_ids).
|
|
74
|
+
lexicon : dict[UUID, str] | None
|
|
75
|
+
Optional mapping from item UUIDs to surface forms for dropdown options.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
list[dict[str, JsonValue]]
|
|
80
|
+
List of field configuration dictionaries with keys:
|
|
81
|
+
- slot_name: Name of the slot
|
|
82
|
+
- position: Token position
|
|
83
|
+
- type: Widget type ("dropdown" or "text")
|
|
84
|
+
- options: List of allowed values (for dropdown)
|
|
85
|
+
- placeholder: Placeholder text
|
|
86
|
+
- dsl_expression: Constraint expression for validation (optional)
|
|
87
|
+
- dsl_context: Constraint context variables (optional)
|
|
88
|
+
|
|
89
|
+
Examples
|
|
90
|
+
--------
|
|
91
|
+
>>> from bead.items.item import UnfilledSlot
|
|
92
|
+
>>> from bead.resources.constraints import Constraint
|
|
93
|
+
>>> from uuid import uuid4
|
|
94
|
+
>>> constraint_id = uuid4()
|
|
95
|
+
>>> slot = UnfilledSlot(
|
|
96
|
+
... slot_name="determiner",
|
|
97
|
+
... position=0,
|
|
98
|
+
... constraint_ids=[constraint_id]
|
|
99
|
+
... )
|
|
100
|
+
>>> id1, id2 = uuid4(), uuid4()
|
|
101
|
+
>>> constraint = Constraint(
|
|
102
|
+
... expression=f"self.id in [UUID('{id1}'), UUID('{id2}')]"
|
|
103
|
+
... )
|
|
104
|
+
>>> lexicon = {id1: "the", id2: "a"}
|
|
105
|
+
>>> fields = create_cloze_fields([slot], {constraint_id: constraint}, lexicon)
|
|
106
|
+
>>> len(fields)
|
|
107
|
+
1
|
|
108
|
+
>>> fields[0]["type"]
|
|
109
|
+
'dropdown'
|
|
110
|
+
>>> len(fields[0]["options"])
|
|
111
|
+
2
|
|
112
|
+
"""
|
|
113
|
+
fields: list[dict[str, JsonValue]] = []
|
|
114
|
+
|
|
115
|
+
for slot in unfilled_slots:
|
|
116
|
+
# infer widget type
|
|
117
|
+
widget_type = infer_widget_type(slot.constraint_ids, constraints)
|
|
118
|
+
|
|
119
|
+
field_config: dict[str, JsonValue] = {
|
|
120
|
+
"slot_name": slot.slot_name,
|
|
121
|
+
"position": slot.position,
|
|
122
|
+
"type": widget_type,
|
|
123
|
+
"options": [],
|
|
124
|
+
"placeholder": slot.slot_name,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# analyze constraints to extract options and validation info
|
|
128
|
+
for constraint_id in slot.constraint_ids:
|
|
129
|
+
if constraint_id not in constraints:
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
constraint = constraints[constraint_id]
|
|
133
|
+
|
|
134
|
+
# include constraint expression for client-side validation
|
|
135
|
+
field_config["dsl_expression"] = constraint.expression
|
|
136
|
+
if constraint.context:
|
|
137
|
+
# convert context to JsonValue compatible format
|
|
138
|
+
field_config["dsl_context"] = {
|
|
139
|
+
k: list(v) if isinstance(v, set) else v
|
|
140
|
+
for k, v in constraint.context.items()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# if dropdown, extract allowed item UUIDs and map to surface forms
|
|
144
|
+
if widget_type == "dropdown":
|
|
145
|
+
allowed_items = _extract_allowed_items_from_expression(
|
|
146
|
+
constraint.expression, constraint.context
|
|
147
|
+
)
|
|
148
|
+
if allowed_items:
|
|
149
|
+
# map UUIDs to surface forms if lexicon provided
|
|
150
|
+
if lexicon:
|
|
151
|
+
options = [
|
|
152
|
+
lexicon.get(item_id, str(item_id))
|
|
153
|
+
for item_id in allowed_items
|
|
154
|
+
]
|
|
155
|
+
else:
|
|
156
|
+
# no lexicon; use UUID strings
|
|
157
|
+
options = [str(item_id) for item_id in allowed_items]
|
|
158
|
+
|
|
159
|
+
field_config["options"] = sorted(options)
|
|
160
|
+
field_config["item_ids"] = [
|
|
161
|
+
str(item_id) for item_id in allowed_items
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
fields.append(field_config)
|
|
165
|
+
|
|
166
|
+
return fields
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def create_forced_choice_config(
|
|
170
|
+
alternatives: list[str],
|
|
171
|
+
randomize_position: bool = True,
|
|
172
|
+
enable_keyboard: bool = True,
|
|
173
|
+
) -> dict[str, list[str] | bool]:
|
|
174
|
+
"""Generate forced choice configuration.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
alternatives : list[str]
|
|
179
|
+
List of alternative options to choose from.
|
|
180
|
+
randomize_position : bool
|
|
181
|
+
Whether to randomize left/right position.
|
|
182
|
+
enable_keyboard : bool
|
|
183
|
+
Whether to enable keyboard responses.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
dict[str, list[str] | bool]
|
|
188
|
+
Forced choice configuration dictionary.
|
|
189
|
+
|
|
190
|
+
Examples
|
|
191
|
+
--------
|
|
192
|
+
>>> config = create_forced_choice_config(["Option A", "Option B"])
|
|
193
|
+
>>> config["randomize_position"]
|
|
194
|
+
True
|
|
195
|
+
>>> len(config["alternatives"])
|
|
196
|
+
2
|
|
197
|
+
"""
|
|
198
|
+
return {
|
|
199
|
+
"alternatives": alternatives,
|
|
200
|
+
"randomize_position": randomize_position,
|
|
201
|
+
"enable_keyboard": enable_keyboard,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _extract_allowed_items_from_expression(
|
|
206
|
+
expression: str,
|
|
207
|
+
context: dict[str, str | int | float | bool | list[str] | set[str] | set[UUID]]
|
|
208
|
+
| None = None,
|
|
209
|
+
) -> set[UUID] | None:
|
|
210
|
+
"""Extract allowed item UUIDs from a DSL constraint expression.
|
|
211
|
+
|
|
212
|
+
Detects patterns like:
|
|
213
|
+
- self.id in [uuid1, uuid2, ...] (inline list)
|
|
214
|
+
- self.id in allowed_items (context variable)
|
|
215
|
+
- str(self.id) in ['uuid-str1', 'uuid-str2', ...]
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
expression : str
|
|
220
|
+
DSL constraint expression.
|
|
221
|
+
context : dict[str, str | int | float | bool | list[str] | set[str] \
|
|
222
|
+
| set[UUID]] | None
|
|
223
|
+
Constraint context variables.
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
set[UUID] | None
|
|
228
|
+
Set of allowed UUIDs if pattern detected, None otherwise.
|
|
229
|
+
|
|
230
|
+
Examples
|
|
231
|
+
--------
|
|
232
|
+
>>> from uuid import UUID
|
|
233
|
+
>>> expr = "self.id in [UUID('...'), UUID('...')]"
|
|
234
|
+
>>> # Would return set of UUIDs if parseable
|
|
235
|
+
"""
|
|
236
|
+
try:
|
|
237
|
+
node = parse(expression)
|
|
238
|
+
except Exception:
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
# look for pattern: self.id in [...]
|
|
242
|
+
if isinstance(node, ast.BinaryOp) and node.operator == "in":
|
|
243
|
+
# check if left side is self.id or str(self.id)
|
|
244
|
+
left = node.left
|
|
245
|
+
is_self_id = False
|
|
246
|
+
|
|
247
|
+
if isinstance(left, ast.AttributeAccess):
|
|
248
|
+
# self.id
|
|
249
|
+
if (
|
|
250
|
+
isinstance(left.object, ast.Variable)
|
|
251
|
+
and left.object.name == "self"
|
|
252
|
+
and left.attribute == "id"
|
|
253
|
+
):
|
|
254
|
+
is_self_id = True
|
|
255
|
+
elif isinstance(left, ast.FunctionCall):
|
|
256
|
+
# str(self.id)
|
|
257
|
+
if (
|
|
258
|
+
isinstance(left.function, ast.Variable)
|
|
259
|
+
and left.function.name == "str"
|
|
260
|
+
and len(left.arguments) == 1
|
|
261
|
+
):
|
|
262
|
+
arg = left.arguments[0]
|
|
263
|
+
if isinstance(arg, ast.AttributeAccess):
|
|
264
|
+
if (
|
|
265
|
+
isinstance(arg.object, ast.Variable)
|
|
266
|
+
and arg.object.name == "self"
|
|
267
|
+
and arg.attribute == "id"
|
|
268
|
+
):
|
|
269
|
+
is_self_id = True
|
|
270
|
+
|
|
271
|
+
if not is_self_id:
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
# check right side; could be inline list or context variable
|
|
275
|
+
if isinstance(node.right, ast.ListLiteral):
|
|
276
|
+
# inline list: self.id in [UUID(...), ...]
|
|
277
|
+
uuids: set[UUID] = set()
|
|
278
|
+
for elem in node.right.elements:
|
|
279
|
+
# handle UUID(...) function calls
|
|
280
|
+
if isinstance(elem, ast.FunctionCall):
|
|
281
|
+
if (
|
|
282
|
+
isinstance(elem.function, ast.Variable)
|
|
283
|
+
and elem.function.name == "UUID"
|
|
284
|
+
and len(elem.arguments) == 1
|
|
285
|
+
):
|
|
286
|
+
arg = elem.arguments[0]
|
|
287
|
+
if isinstance(arg, ast.Literal) and isinstance(arg.value, str):
|
|
288
|
+
try:
|
|
289
|
+
uuids.add(UUID(arg.value))
|
|
290
|
+
except (ValueError, AttributeError):
|
|
291
|
+
pass
|
|
292
|
+
# handle string literals (for str(self.id) pattern)
|
|
293
|
+
elif isinstance(elem, ast.Literal) and isinstance(elem.value, str):
|
|
294
|
+
try:
|
|
295
|
+
uuids.add(UUID(elem.value))
|
|
296
|
+
except (ValueError, AttributeError):
|
|
297
|
+
pass
|
|
298
|
+
|
|
299
|
+
if uuids:
|
|
300
|
+
return uuids
|
|
301
|
+
|
|
302
|
+
elif isinstance(node.right, ast.Variable) and context:
|
|
303
|
+
# context variable: self.id in allowed_items
|
|
304
|
+
var_name = node.right.name
|
|
305
|
+
if var_name in context:
|
|
306
|
+
value = context[var_name]
|
|
307
|
+
# check if it's a set or list of UUIDs
|
|
308
|
+
if isinstance(value, set | list):
|
|
309
|
+
uuids = set()
|
|
310
|
+
for item in value:
|
|
311
|
+
if isinstance(item, UUID):
|
|
312
|
+
uuids.add(item)
|
|
313
|
+
elif isinstance(item, str):
|
|
314
|
+
try:
|
|
315
|
+
uuids.add(UUID(item))
|
|
316
|
+
except (ValueError, AttributeError):
|
|
317
|
+
pass
|
|
318
|
+
if uuids:
|
|
319
|
+
return uuids
|
|
320
|
+
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def infer_widget_type(
|
|
325
|
+
constraint_ids: list[UUID],
|
|
326
|
+
constraints: dict[UUID, Constraint],
|
|
327
|
+
) -> str:
|
|
328
|
+
"""Infer UI widget type from slot constraints.
|
|
329
|
+
|
|
330
|
+
Analyzes the constraint DSL expressions to determine the most appropriate
|
|
331
|
+
UI widget for collecting user input.
|
|
332
|
+
|
|
333
|
+
Widget type inference logic:
|
|
334
|
+
- Constraint with pattern "self.id in [uuid1, uuid2, ...]": "dropdown"
|
|
335
|
+
- Other DSL expressions: "text"
|
|
336
|
+
- No constraints: "text"
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
constraint_ids : list[UUID]
|
|
341
|
+
List of constraint IDs for the slot.
|
|
342
|
+
constraints : dict[UUID, Constraint]
|
|
343
|
+
Dictionary of constraint objects keyed by UUID.
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
str
|
|
348
|
+
Widget type: "dropdown" or "text".
|
|
349
|
+
|
|
350
|
+
Examples
|
|
351
|
+
--------
|
|
352
|
+
>>> from bead.resources.constraints import Constraint
|
|
353
|
+
>>> from uuid import uuid4
|
|
354
|
+
>>> constraint_id = uuid4()
|
|
355
|
+
>>> id1, id2 = uuid4(), uuid4()
|
|
356
|
+
>>> constraint = Constraint(expression=f"self.id in [UUID('{id1}'), UUID('{id2}')]")
|
|
357
|
+
>>> widget = infer_widget_type([constraint_id], {constraint_id: constraint})
|
|
358
|
+
>>> widget
|
|
359
|
+
'dropdown'
|
|
360
|
+
>>> widget2 = infer_widget_type([], {})
|
|
361
|
+
>>> widget2
|
|
362
|
+
'text'
|
|
363
|
+
"""
|
|
364
|
+
if not constraint_ids:
|
|
365
|
+
return "text"
|
|
366
|
+
|
|
367
|
+
# check each constraint for extensional pattern
|
|
368
|
+
for constraint_id in constraint_ids:
|
|
369
|
+
if constraint_id not in constraints:
|
|
370
|
+
continue
|
|
371
|
+
|
|
372
|
+
constraint = constraints[constraint_id]
|
|
373
|
+
|
|
374
|
+
# try to extract allowed items from expression (with context)
|
|
375
|
+
allowed_items = _extract_allowed_items_from_expression(
|
|
376
|
+
constraint.expression, constraint.context
|
|
377
|
+
)
|
|
378
|
+
if allowed_items:
|
|
379
|
+
# found a fixed set of allowed items; use dropdown
|
|
380
|
+
return "dropdown"
|
|
381
|
+
|
|
382
|
+
# default to text input
|
|
383
|
+
return "text"
|