nodekit 0.0.1a1__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.

Potentially problematic release.


This version of nodekit might be problematic. Click here for more details.

Files changed (50) hide show
  1. nodekit/__init__.py +46 -0
  2. nodekit/_internal/__init__.py +0 -0
  3. nodekit/_internal/compilers/__init__.py +0 -0
  4. nodekit/_internal/compilers/events.py +59 -0
  5. nodekit/_internal/compilers/html_rendering.py +44 -0
  6. nodekit/_internal/compilers/node_graph_site_template.j2 +109 -0
  7. nodekit/_internal/models/__init__.py +0 -0
  8. nodekit/_internal/models/assets/__init__.py +0 -0
  9. nodekit/_internal/models/assets/base.py +43 -0
  10. nodekit/_internal/models/fields.py +60 -0
  11. nodekit/_internal/models/node_engine/__init__.py +0 -0
  12. nodekit/_internal/models/node_engine/base.py +22 -0
  13. nodekit/_internal/models/node_engine/board.py +9 -0
  14. nodekit/_internal/models/node_engine/bonus_policy.py +50 -0
  15. nodekit/_internal/models/node_engine/cards/__init__.py +0 -0
  16. nodekit/_internal/models/node_engine/cards/base.py +25 -0
  17. nodekit/_internal/models/node_engine/cards/cards.py +60 -0
  18. nodekit/_internal/models/node_engine/effects/__init__.py +0 -0
  19. nodekit/_internal/models/node_engine/effects/base.py +30 -0
  20. nodekit/_internal/models/node_engine/fields.py +85 -0
  21. nodekit/_internal/models/node_engine/node_graph.py +118 -0
  22. nodekit/_internal/models/node_engine/reinforcer_maps/__init__.py +0 -0
  23. nodekit/_internal/models/node_engine/reinforcer_maps/base.py +14 -0
  24. nodekit/_internal/models/node_engine/reinforcer_maps/reinforcer/__init__.py +0 -0
  25. nodekit/_internal/models/node_engine/reinforcer_maps/reinforcer/reinforcer.py +14 -0
  26. nodekit/_internal/models/node_engine/reinforcer_maps/reinforcer_maps.py +43 -0
  27. nodekit/_internal/models/node_engine/runtime_metrics.py +24 -0
  28. nodekit/_internal/models/node_engine/sensors/__init__.py +0 -0
  29. nodekit/_internal/models/node_engine/sensors/actions/__init__.py +0 -0
  30. nodekit/_internal/models/node_engine/sensors/actions/actions.py +48 -0
  31. nodekit/_internal/models/node_engine/sensors/actions/base.py +18 -0
  32. nodekit/_internal/models/node_engine/sensors/base.py +25 -0
  33. nodekit/_internal/models/node_engine/sensors/sensors.py +49 -0
  34. nodekit/_internal/rule_engine/__init__.py +0 -0
  35. nodekit/_internal/rule_engine/compute_bonus.py +32 -0
  36. nodekit/actions/__init__.py +13 -0
  37. nodekit/assets/__init__.py +9 -0
  38. nodekit/bonus_rules/__init__.py +10 -0
  39. nodekit/cards/__init__.py +13 -0
  40. nodekit/compile/__init__.py +10 -0
  41. nodekit/effects/__init__.py +7 -0
  42. nodekit/events/__init__.py +23 -0
  43. nodekit/reinforcer_maps/__init__.py +10 -0
  44. nodekit/sensors/__init__.py +12 -0
  45. nodekit/types/__init__.py +27 -0
  46. nodekit-0.0.1a1.dist-info/METADATA +217 -0
  47. nodekit-0.0.1a1.dist-info/RECORD +50 -0
  48. nodekit-0.0.1a1.dist-info/WHEEL +5 -0
  49. nodekit-0.0.1a1.dist-info/licenses/LICENSE +201 -0
  50. nodekit-0.0.1a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,118 @@
1
+ from typing import List, Self
2
+ from uuid import uuid4
3
+
4
+ import pydantic
5
+
6
+ from nodekit._internal.models.fields import (
7
+ PayableMonetaryAmountUsd, DatetimeUTC
8
+ )
9
+ from nodekit._internal.models.node_engine.board import Board
10
+ from nodekit._internal.models.node_engine.bonus_policy import BonusRule
11
+ from nodekit._internal.models.node_engine.cards.cards import Card
12
+ from nodekit._internal.models.node_engine.effects.base import Effect
13
+ from nodekit._internal.models.node_engine.fields import NodeId
14
+ from nodekit._internal.models.node_engine.reinforcer_maps.reinforcer_maps import ReinforcerMap
15
+ from nodekit._internal.models.node_engine.runtime_metrics import RuntimeMetrics
16
+ from nodekit._internal.models.node_engine.sensors.actions.actions import Action
17
+ from nodekit._internal.models.node_engine.sensors.sensors import Sensor
18
+
19
+
20
+ # %% Node
21
+ class Node(pydantic.BaseModel):
22
+ node_id: NodeId = pydantic.Field(default_factory=uuid4)
23
+
24
+ board: Board = pydantic.Field(
25
+ default_factory=Board
26
+ )
27
+
28
+ cards: List[Card] = pydantic.Field(
29
+ description="List of Cards that will be placed on the Board, in back-to-front order (i.e. the first Card is at the bottom of the Board, in the z-direction)",
30
+ min_length=1,
31
+ )
32
+
33
+ sensors: List[Sensor] = pydantic.Field(min_length=1)
34
+ reinforcer_maps: List[ReinforcerMap] = pydantic.Field(default_factory=list)
35
+ effects: List[Effect] = pydantic.Field(default_factory=list)
36
+
37
+ @pydantic.model_validator(mode='after')
38
+ def check_node_is_well_formed(self) -> Self:
39
+
40
+ # Run basic checks
41
+ # Check Cards have unique IDs
42
+ card_id_to_card = {card.card_id: card for card in self.cards}
43
+ if len(card_id_to_card) != len(self.cards):
44
+ raise ValueError("Cards must have unique IDs.")
45
+
46
+ # Check Sensors:
47
+ sensor_id_to_sensor = {sensor.sensor_id: sensor for sensor in self.sensors}
48
+ if len(sensor_id_to_sensor) != len(self.sensors):
49
+ raise ValueError("Sensors must have unique IDs.")
50
+ for sensor in self.sensors:
51
+
52
+ # Ensure Sensor references a valid target
53
+ if sensor.card_id and sensor.card_id not in card_id_to_card:
54
+ raise ValueError(f"Sensor {sensor.sensor_id} is not attached to a valid Card or the Board.")
55
+
56
+ # Todo: check that the Sensor and Target type are compatible:
57
+ ...
58
+
59
+ # Check Reinforcer Maps
60
+ for rmap in self.reinforcer_maps:
61
+ if rmap.sensor_id not in sensor_id_to_sensor:
62
+ raise ValueError(f"ReinforcerMap {rmap.reinforcer_map_id} is not attached to a valid Sensor.")
63
+
64
+ # Todo: apply rules for ReinforcerMap-Sensor compatibility
65
+ ...
66
+
67
+ return self
68
+
69
+
70
+ # %% NodeGraph
71
+ class NodeGraph(pydantic.BaseModel):
72
+ nodes: List[Node]
73
+
74
+ # Payment information
75
+ base_payment_usd: PayableMonetaryAmountUsd = pydantic.Field(
76
+ description="The base payment (in USD) that a Participant receives upon successfully completing a TaskRun. Explicitly disclosed to the Participant ahead of time.",
77
+ )
78
+ bonus_rules: List[BonusRule] = pydantic.Field(
79
+ default_factory=list,
80
+ description='A list of bonus rules that apply to the TaskRun. These rules determine additional payments based on Participant behavior during the TaskRun.'
81
+ 'These are not disclosed to the Participant by default, but might be described elsewhere, like the description or in a Node.'
82
+ )
83
+
84
+ # Duration
85
+ max_duration_sec: int = pydantic.Field(
86
+ gt=0,
87
+ description="The maximum of time in seconds a Participant has to complete a TaskRun, before it is marked as failed."
88
+ )
89
+
90
+ # Metadata that is disclosed to the Participant:
91
+ title: str = pydantic.Field(
92
+ min_length=1,
93
+ description='The title of the Recruitment Platform posting for the task.'
94
+ )
95
+ description: str = pydantic.Field(
96
+ min_length=1,
97
+ description='A detailed description of the task in the Recruitment Platform posting.'
98
+ )
99
+ keywords: List[str] = pydantic.Field(
100
+ description='Keywords that Participants may use to discover this task on the Recruitment Platform.'
101
+ )
102
+
103
+
104
+
105
+ # %%
106
+ class NodeResult(pydantic.BaseModel):
107
+ """
108
+ Describes the result of a NodePlay.
109
+ """
110
+
111
+ node_id: str = pydantic.Field(description='The ID of the Node from which this NodeResult was produced.')
112
+ node_execution_index: int = pydantic.Field(description='The index of the Node execution in the NodeGraph. This is used to identify the order of Node executions in a TaskRun.')
113
+
114
+ timestamp_start: DatetimeUTC
115
+ timestamp_end: DatetimeUTC
116
+
117
+ action: Action
118
+ runtime_metrics: RuntimeMetrics
@@ -0,0 +1,14 @@
1
+ from abc import ABC
2
+ from typing import Any
3
+
4
+ from nodekit._internal.models.node_engine.base import DslModel
5
+ from nodekit._internal.models.node_engine.fields import SensorId
6
+
7
+
8
+ class BaseReinforcerMap(DslModel, ABC):
9
+ """
10
+ Represents a map from a fully qualified Action emitted by a particular Sensor to an Outcome.
11
+ """
12
+ reinforcer_map_type: str
13
+ reinforcer_map_parameters: Any
14
+ sensor_id: SensorId
@@ -0,0 +1,14 @@
1
+
2
+ from typing import List
3
+
4
+ import pydantic
5
+
6
+ from nodekit._internal.models.node_engine.base import DslModel
7
+ from nodekit._internal.models.node_engine.cards.cards import Card
8
+
9
+
10
+ class Reinforcer(DslModel):
11
+
12
+ reinforcer_cards: List[Card] = pydantic.Field(
13
+ description='Cards which configure the (noninteractive) Reinforcer delivery phase.'
14
+ )
@@ -0,0 +1,43 @@
1
+ from typing import Literal
2
+
3
+ from nodekit._internal.models.node_engine.base import DslModel, NullParameters
4
+ from nodekit._internal.models.node_engine.reinforcer_maps.base import BaseReinforcerMap
5
+ from nodekit._internal.models.node_engine.reinforcer_maps.reinforcer.reinforcer import Reinforcer
6
+
7
+ from typing import Annotated, Union
8
+
9
+ import pydantic
10
+
11
+
12
+ # %%
13
+ class ConstantReinforcerMap(BaseReinforcerMap):
14
+ class Parameters(DslModel):
15
+ reinforcer: Reinforcer = pydantic.Field(description='The Outcome to return for any Action emitted by the Sensor it is attached to.')
16
+
17
+ """
18
+ An OutcomeMap which always returns the same Outcome for any Action emitted by the Sensor it is attached to.
19
+ """
20
+ reinforcer_map_type: Literal['ConstantReinforcerMap'] = 'ConstantReinforcerMap'
21
+ reinforcer_map_parameters: Parameters
22
+
23
+
24
+ # %%
25
+ class NullReinforcerMap(BaseReinforcerMap):
26
+ """
27
+ A convenience class which represents an ReinforcerMap which yields a NullReinforcer.
28
+ """
29
+ reinforcer_map_type: Literal['NullReinforcerMap'] = 'NullReinforcerMap'
30
+ reinforcer_map_parameters: NullParameters = pydantic.Field(
31
+ default_factory=NullParameters, frozen=True
32
+ )
33
+
34
+
35
+ # %%
36
+ ReinforcerMap = Annotated[
37
+ Union[
38
+ NullReinforcerMap,
39
+ ConstantReinforcerMap,
40
+ # Add other OutcomeMap types here as needed
41
+ ],
42
+ pydantic.Field(discriminator='reinforcer_map_type')
43
+ ]
@@ -0,0 +1,24 @@
1
+ import pydantic
2
+
3
+ from nodekit._internal.models.node_engine.base import DslModel
4
+
5
+
6
+ # %% Measurements
7
+ class PixelArea(DslModel):
8
+ width_px: int = pydantic.Field(description="Width of the area in pixels.", ge=0)
9
+ height_px: int = pydantic.Field(description="Height of the area in pixels.", ge=0)
10
+
11
+
12
+ class RuntimeMetrics(DslModel):
13
+ model_config = pydantic.ConfigDict(
14
+ extra='allow',
15
+ frozen=True,
16
+ )
17
+
18
+ # Board
19
+ display_area: PixelArea = pydantic.Field(description="Metrics of the display area in which the board is rendered.")
20
+ viewport_area: PixelArea = pydantic.Field(description="Metrics of the viewport in which the board is rendered.")
21
+ board_area: PixelArea = pydantic.Field(description="Metrics of the board area in which the cards are rendered.")
22
+
23
+ # User agent string
24
+ user_agent: str = pydantic.Field(description="User agent string of the browser or application rendering the board.")
@@ -0,0 +1,48 @@
1
+ from typing import Literal, Union, Annotated
2
+
3
+ import pydantic
4
+
5
+ from nodekit._internal.models.node_engine.base import DslModel, NullValue
6
+ from nodekit._internal.models.node_engine.sensors.actions.base import BaseAction
7
+
8
+
9
+ # %%
10
+
11
+ class ClickAction(BaseAction):
12
+ """
13
+ A fully-qualified description of a Sensor emission.
14
+ """
15
+
16
+ class Value(DslModel):
17
+ """
18
+ A fully-qualified description of a Sensor emission.
19
+ """
20
+ click_x: float = pydantic.Field(description='The x-coordinate of the click, in Board units.')
21
+ click_y: float = pydantic.Field(description='The y-coordinate of the click, in Board units.')
22
+
23
+ action_type: Literal['ClickAction'] = 'ClickAction'
24
+ action_value: Value
25
+
26
+
27
+ # %%
28
+ class DoneAction(BaseAction):
29
+ action_type: Literal['DoneAction'] = 'DoneAction'
30
+ action_value: NullValue = pydantic.Field(default_factory=NullValue, frozen=True)
31
+
32
+
33
+ # %%
34
+ class TimeoutAction(BaseAction):
35
+ action_type: Literal['TimeoutAction'] = 'TimeoutAction'
36
+ action_value: NullValue = pydantic.Field(default_factory=NullValue, frozen=True)
37
+
38
+
39
+ # %%
40
+ Action = Annotated[
41
+ Union[
42
+ ClickAction,
43
+ DoneAction,
44
+ TimeoutAction,
45
+ # Add other Action types here as needed
46
+ ],
47
+ pydantic.Field(discriminator='action_type')
48
+ ]
@@ -0,0 +1,18 @@
1
+ from abc import ABC
2
+ from typing import Any
3
+
4
+ import pydantic
5
+
6
+ from nodekit._internal.models.node_engine.base import DslModel
7
+ from nodekit._internal.models.node_engine.fields import TimePointMsec, SensorId
8
+
9
+
10
+ class BaseAction(DslModel, ABC):
11
+ sensor_id: SensorId = pydantic.Field(
12
+ description='Identifier of the Sensor that emitted this Action.'
13
+ )
14
+ action_type: str
15
+ action_value: Any
16
+ reaction_time_msec: TimePointMsec = pydantic.Field(
17
+ description='Measured from the onset of the earliest possible time the Action could be emitted.'
18
+ )
@@ -0,0 +1,25 @@
1
+ from abc import ABC
2
+ from typing import Any
3
+
4
+ import pydantic
5
+
6
+ from nodekit._internal.models.node_engine.base import DslModel
7
+ from nodekit._internal.models.node_engine.fields import Timespan, SensorId, CardId
8
+ from uuid import uuid4
9
+
10
+
11
+ class BaseSensor(DslModel, ABC):
12
+ """
13
+ A Sensor represents a listener for Participant behavior. Sensors are bound to a specific Card, or to the Board.
14
+ When a Sensor is triggered, it emits a fully qualified Action.
15
+ """
16
+
17
+ # Sensor
18
+ sensor_id: SensorId = pydantic.Field(default_factory=uuid4)
19
+ sensor_type: str
20
+ sensor_parameters: Any
21
+ # Temporal
22
+ sensor_timespan: Timespan
23
+
24
+ # Sensor target
25
+ card_id: CardId | None = pydantic.Field(description='Identifier of the Entity (Card or Board) that this Sensor is attached to.')
@@ -0,0 +1,49 @@
1
+ from typing import Literal, Union, Annotated
2
+
3
+ import pydantic
4
+
5
+ from nodekit._internal.models.node_engine.base import DslModel, NullParameters
6
+ from nodekit._internal.models.node_engine.fields import TimeDurationMsec
7
+ from nodekit._internal.models.node_engine.sensors.base import BaseSensor
8
+
9
+ from nodekit._internal.models.node_engine.fields import CardId
10
+
11
+
12
+ # %%
13
+ class TimeoutSensor(BaseSensor):
14
+ """
15
+ Attaches to the Board and triggers an Action after a specified timeout period.
16
+ """
17
+
18
+ class TimeoutSensorParameters(DslModel):
19
+ timeout_msec: TimeDurationMsec
20
+
21
+ sensor_type: Literal['TimeoutSensor'] = 'TimeoutSensor'
22
+ sensor_parameters: TimeoutSensorParameters
23
+ card_id: None = pydantic.Field(default=None, frozen=True) # Only binds to Board
24
+
25
+
26
+ # %%
27
+ class DoneSensor(BaseSensor):
28
+ sensor_type: Literal['DoneSensor'] = 'DoneSensor'
29
+ sensor_parameters: NullParameters = pydantic.Field(default_factory=NullParameters, frozen=True)
30
+ card_id: CardId = pydantic.Field(default=None, frozen=True) # Only binds to Card
31
+
32
+
33
+ # %%
34
+ class ClickSensor(BaseSensor):
35
+ sensor_type: Literal['ClickSensor'] = pydantic.Field(default='ClickSensor', frozen=True)
36
+ sensor_parameters: NullParameters = pydantic.Field(default_factory=NullParameters, frozen=True)
37
+ card_id: CardId = pydantic.Field(default=None, frozen=True) # Only binds to Card
38
+
39
+
40
+ # %%
41
+ Sensor = Annotated[
42
+ Union[
43
+ TimeoutSensor,
44
+ DoneSensor,
45
+ ClickSensor,
46
+ # Add other Sensor types here as needed
47
+ ],
48
+ pydantic.Field(discriminator='sensor_type')
49
+ ]
File without changes
@@ -0,0 +1,32 @@
1
+ from decimal import Decimal
2
+ from typing import List
3
+
4
+ from nodekit._internal.models.node_engine.bonus_policy import BonusRule, ConstantBonusRule
5
+ from nodekit._internal.models.node_engine.node_graph import NodeResult
6
+
7
+
8
+ # %% BonusPolicy Rule Engine
9
+ def compute_bonus(
10
+ bonus_rules: List[BonusRule],
11
+ node_results: List[NodeResult]
12
+ ) -> Decimal:
13
+
14
+ calculated_amount = Decimal('0')
15
+
16
+ for node_result in node_results:
17
+ action = node_result.action
18
+
19
+ # Perform scan through rules
20
+ for rule in bonus_rules:
21
+ # Dynamic dispatch
22
+ if isinstance(rule, ConstantBonusRule):
23
+ rule: ConstantBonusRule
24
+ if action.sensor_id == rule.bonus_rule_parameters.sensor_id:
25
+ # Check currency match
26
+ calculated_amount += Decimal(rule.bonus_rule_parameters.bonus_amount_usd)
27
+
28
+ # Clip at minimum of 0:
29
+ if calculated_amount < Decimal('0'):
30
+ calculated_amount = Decimal('0.00')
31
+
32
+ return calculated_amount
@@ -0,0 +1,13 @@
1
+ __all__ = [
2
+ "Action",
3
+ "ClickAction",
4
+ "DoneAction",
5
+ "TimeoutAction",
6
+ ]
7
+
8
+ from nodekit._internal.models.node_engine.sensors.actions.actions import (
9
+ Action,
10
+ ClickAction,
11
+ DoneAction,
12
+ TimeoutAction,
13
+ )
@@ -0,0 +1,9 @@
1
+ __all__ = [
2
+ "AssetLink",
3
+ "ImageLink",
4
+ ]
5
+
6
+ from nodekit._internal.models.assets.base import (
7
+ AssetLink,
8
+ ImageLink,
9
+ )
@@ -0,0 +1,10 @@
1
+ __all__ = [
2
+ 'ConstantBonusRule',
3
+ 'compute_bonus',
4
+ ]
5
+
6
+ from nodekit._internal.models.node_engine.bonus_policy import (
7
+ ConstantBonusRule,
8
+ )
9
+
10
+ from nodekit._internal.rule_engine.compute_bonus import compute_bonus
@@ -0,0 +1,13 @@
1
+ __all__ = [
2
+ 'FixationPointCard',
3
+ 'MarkdownPagesCard',
4
+ 'ImageCard',
5
+ 'TextCard',
6
+ ]
7
+
8
+ from nodekit._internal.models.node_engine.cards.cards import (
9
+ FixationPointCard,
10
+ MarkdownPagesCard,
11
+ ImageCard,
12
+ TextCard,
13
+ )
@@ -0,0 +1,10 @@
1
+ __all__ = [
2
+ 'html',
3
+ 'CompileHtmlOptions',
4
+ ]
5
+
6
+ from nodekit._internal.compilers.html_rendering import (
7
+ html,
8
+ CompileHtmlOptions,
9
+ )
10
+
@@ -0,0 +1,7 @@
1
+ __all__ = [
2
+ 'HidePointerEffect',
3
+ ]
4
+
5
+ from nodekit._internal.models.node_engine.effects.base import (
6
+ HidePointerEffect
7
+ )
@@ -0,0 +1,23 @@
1
+ __all__ = [
2
+ "EventTypeEnum",
3
+ "Event",
4
+
5
+ # Concrete classes:
6
+ "StartEvent",
7
+ "EndEvent",
8
+ "NodeResultEvent",
9
+
10
+ # Client-server models:
11
+ "SubmitEventRequest",
12
+ "SubmitEventResponse",
13
+ ]
14
+
15
+ from nodekit._internal.compilers.events import (
16
+ Event,
17
+ StartEvent,
18
+ EndEvent,
19
+ NodeResultEvent,
20
+ SubmitEventRequest,
21
+ SubmitEventResponse,
22
+ EventTypeEnum,
23
+ )
@@ -0,0 +1,10 @@
1
+ __all__ = [
2
+ 'ConstantReinforcerMap',
3
+ 'Reinforcer',
4
+ ]
5
+
6
+ from nodekit._internal.models.node_engine.reinforcer_maps.reinforcer_maps import (
7
+ ConstantReinforcerMap,
8
+ Reinforcer,
9
+ )
10
+
@@ -0,0 +1,12 @@
1
+ __all__ = [
2
+ 'TimeoutSensor',
3
+ 'DoneSensor',
4
+ 'ClickSensor',
5
+ ]
6
+
7
+ from nodekit._internal.models.node_engine.sensors.sensors import (
8
+ TimeoutSensor,
9
+ DoneSensor,
10
+ ClickSensor,
11
+ )
12
+
@@ -0,0 +1,27 @@
1
+ __all__ = [
2
+ # Reusable structural types used as field values in models:
3
+ 'TextContent',
4
+ 'Board',
5
+ 'BoardRectangle',
6
+ 'BoardLocation',
7
+ 'Timespan',
8
+
9
+ 'RuntimeMetrics',
10
+ 'PixelArea',
11
+ ]
12
+
13
+ from nodekit._internal.models.node_engine.fields import (
14
+ TextContent,
15
+ BoardRectangle,
16
+ BoardLocation,
17
+ Timespan,
18
+ )
19
+
20
+ from nodekit._internal.models.node_engine.runtime_metrics import (
21
+ PixelArea,
22
+ RuntimeMetrics,
23
+ )
24
+
25
+ from nodekit._internal.models.node_engine.board import Board
26
+
27
+