bynn-decisionnode 0.3.1__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.
@@ -0,0 +1,262 @@
1
+ """Question builders.
2
+
3
+ Each builder returns the question in its wire form, a plain ``dict``. A ``dict``
4
+ written by hand in the same form is accepted wherever a builder is, so a request
5
+ copied from the API documentation runs unchanged.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, Union
11
+
12
+ from typing_extensions import Literal, NotRequired, TypedDict
13
+
14
+ __all__ = [
15
+ "TruthQuestion",
16
+ "ChoiceQuestion",
17
+ "ScoreQuestion",
18
+ "NumberQuestion",
19
+ "PointQuestion",
20
+ "BoxQuestion",
21
+ "Question",
22
+ "truth",
23
+ "choice",
24
+ "score",
25
+ "number",
26
+ "point",
27
+ "box",
28
+ ]
29
+
30
+
31
+ class TruthQuestion(TypedDict):
32
+ """A yes-or-no question, answered by the probability that the answer is yes."""
33
+
34
+ type: Literal["truth"]
35
+ instructions: NotRequired[Any]
36
+ criteria: NotRequired[Dict[str, Any]]
37
+
38
+
39
+ class ChoiceQuestion(TypedDict):
40
+ """Pick exactly one option of a set."""
41
+
42
+ type: Literal["choice"]
43
+ instructions: NotRequired[Any]
44
+ criteria: Dict[str, Any]
45
+
46
+
47
+ class ScoreQuestion(TypedDict):
48
+ """Place the state on an ordered scale of levels, low to high."""
49
+
50
+ type: Literal["score"]
51
+ instructions: NotRequired[Any]
52
+ criteria: List[Any]
53
+
54
+
55
+ class NumberQuestion(TypedDict):
56
+ """Read a number off the state: a value of the grid from ``min`` to ``max`` in ``step``."""
57
+
58
+ type: Literal["number"]
59
+ instructions: NotRequired[Any]
60
+ min: NotRequired[Union[int, float]]
61
+ max: NotRequired[Union[int, float]]
62
+ step: NotRequired[Union[int, float]]
63
+
64
+
65
+ class PointQuestion(TypedDict):
66
+ """Point to one spot in an image: ``x`` and ``y``; with ``max``, up to that many."""
67
+
68
+ type: Literal["point"]
69
+ instructions: NotRequired[Any]
70
+ image: NotRequired[str]
71
+ units: NotRequired[Literal["pixels", "normalized"]]
72
+ max: NotRequired[int]
73
+
74
+
75
+ class BoxQuestion(TypedDict):
76
+ """Draw a box around something in an image: ``x1``, ``y1``, ``x2`` and ``y2``; with
77
+ ``max``, around up to that many."""
78
+
79
+ type: Literal["box"]
80
+ instructions: NotRequired[Any]
81
+ image: NotRequired[str]
82
+ units: NotRequired[Literal["pixels", "normalized"]]
83
+ max: NotRequired[int]
84
+
85
+
86
+ Question = Union[
87
+ TruthQuestion, ChoiceQuestion, ScoreQuestion, NumberQuestion, PointQuestion, BoxQuestion, Mapping[str, Any]
88
+ ]
89
+ """A question built by :func:`truth`, :func:`choice`, :func:`score` or :func:`number`, or a wire-form mapping."""
90
+
91
+
92
+ def truth(instructions: Any = None, if_true: Any = None, if_false: Any = None) -> TruthQuestion:
93
+ """A ``truth`` question: how likely is it that the answer is yes?
94
+
95
+ Args:
96
+ instructions: The question or statement to judge. A string, or any
97
+ JSON-serialisable value.
98
+ if_true: What a yes means (optional).
99
+ if_false: What a no means (optional).
100
+
101
+ The criteria are left out when neither side is given.
102
+
103
+ >>> truth("Escalate to a human now?")
104
+ {'type': 'truth', 'instructions': 'Escalate to a human now?'}
105
+ """
106
+ question: Dict[str, Any] = {"type": "truth"}
107
+ if instructions is not None:
108
+ question["instructions"] = instructions
109
+ criteria: Dict[str, Any] = {}
110
+ if if_true is not None:
111
+ criteria["true"] = if_true
112
+ if if_false is not None:
113
+ criteria["false"] = if_false
114
+ if criteria:
115
+ question["criteria"] = criteria
116
+ return question # type: ignore[return-value]
117
+
118
+
119
+ def choice(instructions: Any = None, options: Union[Mapping[str, Any], Iterable[str], None] = None) -> ChoiceQuestion:
120
+ """A ``choice`` question: which one of these options?
121
+
122
+ Args:
123
+ instructions: What to decide. A string, or any JSON-serialisable value.
124
+ options: A mapping of option name to description, or an iterable of names (each
125
+ description is then ``None``). Names are returned byte for byte.
126
+
127
+ >>> choice("Where should this go?", ["billing", "bug"])
128
+ {'type': 'choice', 'instructions': 'Where should this go?', 'criteria': {'billing': None, 'bug': None}}
129
+ """
130
+ if options is None:
131
+ raise ValueError("choice() needs options: a mapping of name to description, or a list of names")
132
+ if isinstance(options, Mapping):
133
+ criteria = dict(options)
134
+ elif isinstance(options, (str, bytes)):
135
+ raise ValueError("choice() options must be a mapping or a list of names, not a single string")
136
+ else:
137
+ criteria = {name: None for name in options}
138
+ question: Dict[str, Any] = {"type": "choice"}
139
+ if instructions is not None:
140
+ question["instructions"] = instructions
141
+ question["criteria"] = criteria
142
+ return question # type: ignore[return-value]
143
+
144
+
145
+ def score(instructions: Any = None, levels: Optional[Sequence[Any]] = None) -> ScoreQuestion:
146
+ """A ``score`` question: where on this scale?
147
+
148
+ Args:
149
+ instructions: What to measure. A string, or any JSON-serialisable value.
150
+ levels: The levels of the scale, ordered low to high. Each is a string or
151
+ any JSON-serialisable value, and comes back in the answer's ``legend``.
152
+
153
+ >>> score("How urgent is this?", ["routine", "today", "urgent"])
154
+ {'type': 'score', 'instructions': 'How urgent is this?', 'criteria': ['routine', 'today', 'urgent']}
155
+ """
156
+ if levels is None:
157
+ raise ValueError("score() needs levels: a list ordered low to high")
158
+ if isinstance(levels, (str, bytes, Mapping)):
159
+ raise ValueError("score() levels must be a list ordered low to high")
160
+ question: Dict[str, Any] = {"type": "score"}
161
+ if instructions is not None:
162
+ question["instructions"] = instructions
163
+ question["criteria"] = list(levels)
164
+ return question # type: ignore[return-value]
165
+
166
+
167
+ def number(
168
+ instructions: Any = None,
169
+ min: Union[int, float, None] = None, # noqa: A002 - the wire name
170
+ max: Union[int, float, None] = None, # noqa: A002 - the wire name
171
+ step: Union[int, float, None] = None,
172
+ ) -> NumberQuestion:
173
+ """A ``number`` question: which value of the grid ``min``, ``min + step``, ... up to ``max``?
174
+
175
+ Args:
176
+ instructions: What to count or read. A string, or any JSON-serialisable value.
177
+ Required by the API.
178
+ min: The lowest value (the API's default is 0). At most 6 decimals.
179
+ max: The highest value the grid may reach (default 255).
180
+ step: The spacing, above 0 (default 1). At most 6 decimals.
181
+
182
+ An argument left out is left out of the wire form, so the API's default applies.
183
+ A grid holds at most 4,096 values. The grid is checked before the request is sent.
184
+
185
+ >>> number("How many cars are visible?", min=0, max=50)
186
+ {'type': 'number', 'instructions': 'How many cars are visible?', 'min': 0, 'max': 50}
187
+ """
188
+ question: Dict[str, Any] = {"type": "number"}
189
+ if instructions is not None:
190
+ question["instructions"] = instructions
191
+ for name, value in (("min", min), ("max", max), ("step", step)):
192
+ if value is not None:
193
+ question[name] = value
194
+ return question # type: ignore[return-value]
195
+
196
+
197
+ def point(
198
+ instructions: Any = None,
199
+ image: Optional[str] = None,
200
+ units: Optional[str] = None,
201
+ max: Optional[int] = None, # noqa: A002 - the API's name for it
202
+ ) -> PointQuestion:
203
+ """A ``point`` question: where in the image is it? Answered with ``x`` and ``y``, or,
204
+ with ``max``, with up to ``max`` points (a :class:`PointsAnswer`).
205
+
206
+ Args:
207
+ instructions: What to find: a phrase, a description with conditions, or a
208
+ question. Required by the API.
209
+ image: The id of the image to look in (a string), required when the request
210
+ has two or more images. A reference by number ("Picture 2") goes in the
211
+ instructions.
212
+ units: ``"pixels"`` (the API's default) or ``"normalized"`` (fractions of the
213
+ width and height, from 0 to 1).
214
+ max: Several places: the most points you can take, from 2 to 16, one per thing
215
+ the instructions describe ("each person on the platform"). Left out, the
216
+ one best match.
217
+
218
+ >>> point("Point to the yellow package closest to the robot arm.")
219
+ {'type': 'point', 'instructions': 'Point to the yellow package closest to the robot arm.'}
220
+ """
221
+ return _place("point", instructions, image, units, max) # type: ignore[return-value]
222
+
223
+
224
+ def box(
225
+ instructions: Any = None,
226
+ image: Optional[str] = None,
227
+ units: Optional[str] = None,
228
+ max: Optional[int] = None, # noqa: A002 - the API's name for it
229
+ ) -> BoxQuestion:
230
+ """A ``box`` question: draw a box around it. Answered with the top-left corner
231
+ (``x1``, ``y1``) and the bottom-right corner (``x2``, ``y2``), or, with ``max``, with
232
+ up to ``max`` boxes (a :class:`BoxesAnswer`).
233
+
234
+ Args:
235
+ instructions: What to find. Required by the API.
236
+ image: The id of the image to look in (a string), required when the request
237
+ has two or more images. A reference by number ("Picture 2") goes in the
238
+ instructions.
239
+ units: ``"pixels"`` (the API's default) or ``"normalized"``.
240
+ max: Several places: the most boxes you can take, from 2 to 16, one per thing
241
+ the instructions describe ("every car in the lot"). Left out, the one best
242
+ match.
243
+
244
+ >>> box("Draw a box around the damaged package.", image="belt")
245
+ {'type': 'box', 'instructions': 'Draw a box around the damaged package.', 'image': 'belt'}
246
+ """
247
+ return _place("box", instructions, image, units, max) # type: ignore[return-value]
248
+
249
+
250
+ def _place(
251
+ kind: str, instructions: Any, image: Optional[str], units: Optional[str], most: Optional[int] = None
252
+ ) -> Dict[str, Any]:
253
+ question: Dict[str, Any] = {"type": kind}
254
+ if instructions is not None:
255
+ question["instructions"] = instructions
256
+ if image is not None:
257
+ question["image"] = image
258
+ if units is not None:
259
+ question["units"] = units
260
+ if most is not None:
261
+ question["max"] = most
262
+ return question