Skip to content

Commit c25cd15

Browse files
author
brandon
committed
Adds ability to create alerts with multiple actions
1 parent e10713a commit c25cd15

2 files changed

Lines changed: 156 additions & 2 deletions

File tree

src/groundlight/experimental_api.py

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from groundlight_openapi_client.api.detector_reset_api import DetectorResetApi
1717
from groundlight_openapi_client.api.image_queries_api import ImageQueriesApi
1818
from groundlight_openapi_client.api.notes_api import NotesApi
19+
1920
from groundlight_openapi_client.model.action_request import ActionRequest
2021
from groundlight_openapi_client.model.channel_enum import ChannelEnum
2122
from groundlight_openapi_client.model.condition_request import ConditionRequest
@@ -27,12 +28,12 @@
2728
from groundlight_openapi_client.model.rule_request import RuleRequest
2829
from groundlight_openapi_client.model.status_enum import StatusEnum
2930
from groundlight_openapi_client.model.verb_enum import VerbEnum
30-
from model import ROI, BBoxGeometry, Detector, DetectorGroup, ModeEnum, PaginatedRuleList, Rule
31+
from model import ROI, Action, ActionList, BBoxGeometry, Condition, Detector, DetectorGroup, ModeEnum, PaginatedRuleList, Rule
3132

3233
from groundlight.images import parse_supported_image_types
3334
from groundlight.optional_imports import Image, np
3435

35-
from .client import DEFAULT_REQUEST_TIMEOUT, Groundlight
36+
from .client import DEFAULT_REQUEST_TIMEOUT, Groundlight, logger
3637

3738

3839
class ExperimentalApi(Groundlight):
@@ -93,6 +94,141 @@ def __init__(
9394

9495
ITEMS_PER_PAGE = 100
9596

97+
def make_condition(
98+
self,
99+
verb: str,
100+
parameters: dict
101+
) -> Condition:
102+
"""
103+
Creates a Condition object for use in creating alerts
104+
105+
This function serves as a convenience method; Condition objects can also be created directly.
106+
107+
**Example usage**::
108+
109+
gl = ExperimentalApi()
110+
111+
# Create a condition for a rule
112+
condition = gl.make_condition("CHANGED_TO", {"label": "YES"})
113+
114+
:param verb: The condition verb to use. One of "ANSWERED_CONSECUTIVELY", "ANSWERED_WITHIN_TIME",
115+
"CHANGED_TO", "NO_CHANGE", "NO_QUERIES"
116+
:param condition_parameters: Additional parameters for the condition, dependant on the verb:
117+
- For ANSWERED_CONSECUTIVELY: {"num_consecutive_labels": N, "label": "YES/NO"}
118+
- For CHANGED_TO: {"label": "YES/NO"}
119+
- For ANSWERED_WITHIN_TIME: {"time_value": N, "time_unit": "MINUTES/HOURS/DAYS"}
120+
121+
:return: The created Condition object
122+
"""
123+
return Condition(verb=verb, parameters=parameters)
124+
125+
def make_action(
126+
self,
127+
channel: str,
128+
recipient: str,
129+
include_image: bool,
130+
) -> Action:
131+
"""
132+
Creates an Action object for use in creating alerts
133+
134+
This function serves as a convenience method; Action objects can also be created directly.
135+
136+
**Example usage**::
137+
138+
gl = ExperimentalApi()
139+
140+
# Create an action for a rule
141+
action = gl.make_action("EMAIL", "example@example.com", include_image=True)
142+
143+
:param channel: The notification channel to use. One of "EMAIL" or "TEXT"
144+
:param recipient: The email address or phone number to send notifications to
145+
:param include_image: Whether to include the triggering image in notifications
146+
"""
147+
return Action(
148+
channel=channel,
149+
recipient=recipient,
150+
include_image=include_image,
151+
)
152+
153+
def create_alert(
154+
self,
155+
detector: Union[str, Detector],
156+
name,
157+
condition: Condition,
158+
actions: Union[Action, List[Action], ActionList],
159+
*,
160+
enabled: bool = True,
161+
snooze_time_enabled: bool = False,
162+
snooze_time_value: int = 3600,
163+
snooze_time_unit: str = "SECONDS",
164+
human_review_required: bool = False,
165+
) -> Rule:
166+
"""
167+
Creates an alert for a detector that will trigger actions based on specified conditions.
168+
169+
An alert allows you to configure automated actions when certain conditions are met,
170+
such as when a detector's prediction changes or maintains a particular state.
171+
172+
.. note::
173+
Currently, only binary mode detectors (YES/NO answers) are supported for notification rules.
174+
175+
**Example usage**::
176+
177+
gl = ExperimentalApi()
178+
179+
# Create a rule to send email alerts when door is detected as open
180+
condition = gl.make_condition(
181+
verb="CHANGED_TO",
182+
parameters={"label": "YES"}
183+
)
184+
action1 = gl.make_action(
185+
"EMAIL",
186+
"alerts@company.com",
187+
include_image=True
188+
)
189+
action2 = gl.make_action(
190+
"TEXT",
191+
"+1234567890",
192+
include_image=False
193+
)
194+
alert = gl.create_alert(
195+
detector="det_idhere",
196+
name="Door Open Alert",
197+
condition=condition,
198+
actions=[action1, action2]
199+
)
200+
201+
:param detector: The detector ID or Detector object to add the rule to
202+
:param name: A unique name to identify this rule
203+
:param enabled: Whether the rule should be active when created (default True)
204+
:param snooze_time_enabled: Enable notification snoozing to prevent alert spam (default False)
205+
:param snooze_time_value: Duration of snooze period (default 3600)
206+
:param snooze_time_unit: Unit for snooze duration - "SECONDS", "MINUTES", "HOURS", or "DAYS" (default "SECONDS")
207+
:param human_review_required: Require human verification before sending alerts (default False)
208+
209+
:return: The created Alert object
210+
"""
211+
if isinstance(actions, Action):
212+
actions = [actions]
213+
elif isinstance(actions, ActionList):
214+
actions = actions.root
215+
if isinstance(detector, Detector):
216+
detector = detector.id
217+
# translate pydantic type to the openapi type
218+
actions = [ActionRequest(channel=ChannelEnum(action.channel), recipient=action.recipient, include_image=action.include_image) for action in actions]
219+
rule_input = RuleRequest(
220+
detector_id=detector,
221+
name=name,
222+
enabled=enabled,
223+
action=actions,
224+
condition=ConditionRequest(verb=VerbEnum(condition.verb), parameters=condition.parameters),
225+
snooze_time_enabled=snooze_time_enabled,
226+
snooze_time_value=snooze_time_value,
227+
snooze_time_unit=snooze_time_unit,
228+
human_review_required=human_review_required,
229+
)
230+
return Rule.model_validate(self.actions_api.create_rule(detector, rule_input).to_dict())
231+
96232
def create_rule( # pylint: disable=too-many-locals # noqa: PLR0913
97233
self,
98234
detector: Union[str, Detector],
@@ -168,6 +304,9 @@ def create_rule( # pylint: disable=too-many-locals # noqa: PLR0913
168304
169305
:return: The created Rule object
170306
"""
307+
308+
logger.warning("create_rule is no longer supported. Please use create_alert instead.")
309+
171310
if condition_parameters is None:
172311
condition_parameters = {}
173312
if isinstance(alert_on, str):

test/unit/test_actions.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,18 @@ def test_delete_action(gl_experimental: ExperimentalApi):
5252
gl_experimental.delete_rule(rule.id)
5353
with pytest.raises(NotFoundException) as _:
5454
gl_experimental.get_rule(rule.id)
55+
56+
57+
def test_create_alert_multiple_actions(gl_experimental: ExperimentalApi):
58+
name = f"Test {datetime.utcnow()}"
59+
det = gl_experimental.get_or_create_detector(name, "test_query")
60+
condition = gl_experimental.make_condition("CHANGED_TO", {"label": "YES"})
61+
action1 = gl_experimental.make_action("EMAIL", "test@groundlight.ai", False)
62+
action2 = gl_experimental.make_action("EMAIL", "test@groundlight.ai", False)
63+
alert = gl_experimental.create_alert(
64+
det,
65+
f"test_alert_{name}",
66+
condition,
67+
[action1, action2],
68+
)
69+
assert len(alert.action.root) == 2

0 commit comments

Comments
 (0)