Coverage for custom_components/supernotify/__init__.py: 99%

214 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-10-26 08:54 +0000

1"""The SuperNotification integration""" 

2 

3from dataclasses import dataclass, field 

4from enum import StrEnum 

5from typing import Any 

6 

7import voluptuous as vol 

8from homeassistant.components.notify import PLATFORM_SCHEMA 

9from homeassistant.const import ( 

10 ATTR_DOMAIN, 

11 ATTR_SERVICE, 

12 CONF_ACTION, 

13 CONF_ALIAS, 

14 CONF_CONDITION, 

15 CONF_DEFAULT, 

16 CONF_DESCRIPTION, 

17 CONF_EMAIL, 

18 CONF_ENABLED, 

19 CONF_ICON, 

20 CONF_ID, 

21 CONF_NAME, 

22 CONF_TARGET, 

23 CONF_URL, 

24 STATE_HOME, 

25 STATE_NOT_HOME, 

26 Platform, 

27) 

28from homeassistant.helpers import config_validation as cv 

29 

30from custom_components.supernotify.common import format_timestamp as format_timestamp 

31 

32DOMAIN = "supernotify" 

33 

34PLATFORMS = [Platform.NOTIFY] 

35TEMPLATE_DIR = "/config/templates/supernotify" 

36MEDIA_DIR = "supernotify/media" 

37 

38CONF_ACTIONS = "actions" 

39CONF_TITLE = "title" 

40CONF_URI = "uri" 

41CONF_RECIPIENTS = "recipients" 

42CONF_TEMPLATE_PATH = "template_path" 

43CONF_MEDIA_PATH = "media_path" 

44CONF_HOUSEKEEPING = "housekeeping" 

45CONF_HOUSEKEEPING_TIME = "housekeeping_time" 

46CONF_ARCHIVE_PATH = "archive_path" 

47CONF_ARCHIVE = "archive" 

48CONF_ARCHIVE_DAYS = "archive_days" 

49CONF_ARCHIVE_MQTT_TOPIC = "archive_mqtt_topic" 

50CONF_ARCHIVE_MQTT_QOS = "archive_mqtt_qos" 

51CONF_ARCHIVE_MQTT_RETAIN = "archive_mqtt_retain" 

52CONF_TEMPLATE = "template" 

53CONF_LINKS = "links" 

54CONF_PERSON = "person" 

55CONF_METHOD = "method" 

56CONF_METHODS = "methods" 

57CONF_DELIVERY = "delivery" 

58CONF_SELECTION = "selection" 

59 

60CONF_DATA: str = "data" 

61CONF_OPTIONS: str = "options" 

62CONF_MOBILE: str = "mobile" 

63CONF_NOTIFY: str = "notify" 

64CONF_NOTIFY_ACTION: str = "notify_action" 

65CONF_PHONE_NUMBER: str = "phone_number" 

66CONF_PRIORITY: str = "priority" 

67CONF_OCCUPANCY: str = "occupancy" 

68CONF_SCENARIOS: str = "scenarios" 

69CONF_MANUFACTURER: str = "manufacturer" 

70CONF_DEVICE_DISCOVERY: str = "device_discovery" 

71CONF_DEVICE_TRACKER: str = "device_tracker" 

72CONF_DEVICE_NAME: str = "device_name" 

73CONF_DEVICE_LABELS: str = "device_labels" 

74CONF_DEVICE_DOMAIN: str = "device_domain" 

75CONF_MODEL: str = "model" 

76CONF_MESSAGE: str = "message" 

77CONF_TARGETS_REQUIRED: str = "targets_required" 

78CONF_MOBILE_DEVICES: str = "mobile_devices" 

79CONF_MOBILE_DISCOVERY: str = "mobile_discovery" 

80CONF_ACTION_TEMPLATE: str = "action_template" 

81CONF_ACTION_GROUPS: str = "action_groups" 

82CONF_TITLE_TEMPLATE: str = "title_template" 

83CONF_DELIVERY_SELECTION: str = "delivery_selection" 

84CONF_MEDIA: str = "media" 

85CONF_CAMERA: str = "camera" 

86CONF_CLIP_URL: str = "clip_url" 

87CONF_SNAPSHOT_URL: str = "snapshot_url" 

88CONF_PTZ_DELAY: str = "ptz_delay" 

89CONF_PTZ_METHOD: str = "ptz_method" 

90CONF_PTZ_PRESET_DEFAULT: str = "ptz_default_preset" 

91CONF_ALT_CAMERA: str = "alt_camera" 

92CONF_CAMERAS: str = "cameras" 

93CONF_DEFAULT_ACTION: str = "default_action" 

94 

95OCCUPANCY_ANY_IN = "any_in" 

96OCCUPANCY_ANY_OUT = "any_out" 

97OCCUPANCY_ALL_IN = "all_in" 

98OCCUPANCY_ALL = "all" 

99OCCUPANCY_NONE = "none" 

100OCCUPANCY_ALL_OUT = "all_out" 

101OCCUPANCY_ONLY_IN = "only_in" 

102OCCUPANCY_ONLY_OUT = "only_out" 

103 

104ATTR_PRIORITY = "priority" 

105ATTR_ACTION = "action" 

106ATTR_SCENARIOS_REQUIRE = "require_scenarios" 

107ATTR_SCENARIOS_APPLY = "apply_scenarios" 

108ATTR_SCENARIOS_CONSTRAIN = "constrain_scenarios" 

109ATTR_DELIVERY = "delivery" 

110ATTR_DEFAULT = "default" 

111ATTR_NOTIFICATION_ID = "notification_id" 

112ATTR_DELIVERY_SELECTION = "delivery_selection" 

113ATTR_RECIPIENTS = "recipients" 

114ATTR_DATA = "data" 

115ATTR_MEDIA = "media" 

116ATTR_TITLE = "title" 

117ATTR_MEDIA_SNAPSHOT_URL = "snapshot_url" 

118ATTR_MEDIA_CAMERA_ENTITY_ID = "camera_entity_id" 

119ATTR_MEDIA_CAMERA_DELAY = "camera_delay" 

120ATTR_MEDIA_CAMERA_PTZ_PRESET = "camera_ptz_preset" 

121ATTR_MEDIA_CLIP_URL = "clip_url" 

122ATTR_ACTION_GROUPS = "action_groups" 

123CONF_ACTION_GROUP_NAMES = "action_groups" 

124ATTR_ACTION_CATEGORY = "action_category" 

125ATTR_ACTION_URL = "action_url" 

126ATTR_ACTION_URL_TITLE = "action_url_title" 

127ATTR_MESSAGE_HTML = "message_html" 

128ATTR_JPEG_OPTS = "jpeg_opts" 

129ATTR_TIMESTAMP = "timestamp" 

130ATTR_DEBUG = "debug" 

131ATTR_ACTIONS = "actions" 

132ATTR_USER_ID = "user_id" 

133 

134DELIVERY_SELECTION_IMPLICIT = "implicit" 

135DELIVERY_SELECTION_EXPLICIT = "explicit" 

136DELIVERY_SELECTION_FIXED = "fixed" 

137 

138DELIVERY_SELECTION_VALUES = [DELIVERY_SELECTION_EXPLICIT, DELIVERY_SELECTION_FIXED, DELIVERY_SELECTION_IMPLICIT] 

139PTZ_METHOD_ONVIF = "onvif" 

140PTZ_METHOD_FRIGATE = "frigate" 

141PTZ_METHOD_VALUES = [PTZ_METHOD_ONVIF, PTZ_METHOD_FRIGATE] 

142 

143SELECTION_FALLBACK_ON_ERROR = "fallback_on_error" 

144SELECTION_FALLBACK = "fallback" 

145SELECTION_BY_SCENARIO = "scenario" 

146SELECTION_DEFAULT = "default" 

147SELECTION_VALUES = [SELECTION_FALLBACK_ON_ERROR, SELECTION_BY_SCENARIO, SELECTION_DEFAULT, SELECTION_FALLBACK] 

148 

149OCCUPANCY_VALUES = [ 

150 OCCUPANCY_ALL_IN, 

151 OCCUPANCY_ALL_OUT, 

152 OCCUPANCY_ANY_IN, 

153 OCCUPANCY_ANY_OUT, 

154 OCCUPANCY_ONLY_IN, 

155 OCCUPANCY_ONLY_OUT, 

156 OCCUPANCY_ALL, 

157 OCCUPANCY_NONE, 

158] 

159 

160PRIORITY_CRITICAL = "critical" 

161PRIORITY_HIGH = "high" 

162PRIORITY_MEDIUM = "medium" 

163PRIORITY_LOW = "low" 

164 

165PRIORITY_VALUES = [PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_CRITICAL] 

166METHOD_SMS = "sms" 

167METHOD_EMAIL = "email" 

168METHOD_ALEXA = "alexa_devices" 

169METHOD_ALEXA_MEDIA_PLAYER = "alexa_media_player" 

170METHOD_MOBILE_PUSH = "mobile_push" 

171METHOD_MEDIA = "media" 

172METHOD_CHIME = "chime" 

173METHOD_GENERIC = "generic" 

174METHOD_PERSISTENT = "persistent" 

175METHOD_VALUES = [ 

176 METHOD_SMS, 

177 METHOD_ALEXA, 

178 METHOD_ALEXA_MEDIA_PLAYER, 

179 METHOD_MOBILE_PUSH, 

180 METHOD_CHIME, 

181 METHOD_EMAIL, 

182 METHOD_MEDIA, 

183 METHOD_PERSISTENT, 

184 METHOD_GENERIC, 

185] 

186 

187SCENARIO_DEFAULT = "DEFAULT" 

188SCENARIO_NULL = "NULL" 

189SCENARIO_TEMPLATE_ATTRS = ("message_template", "title_template") 

190 

191RESERVED_DELIVERY_NAMES = ["ALL"] 

192RESERVED_SCENARIO_NAMES = [SCENARIO_DEFAULT, SCENARIO_NULL] 

193RESERVED_DATA_KEYS = [ATTR_DOMAIN, ATTR_SERVICE, "action"] 

194 

195CONF_DUPE_CHECK = "dupe_check" 

196CONF_DUPE_POLICY = "dupe_policy" 

197CONF_TTL = "ttl" 

198CONF_SIZE = "size" 

199ATTR_DUPE_POLICY_MTSLP = "dupe_policy_message_title_same_or_lower_priority" 

200ATTR_DUPE_POLICY_NONE = "dupe_policy_none" 

201 

202DATA_SCHEMA = vol.Schema({vol.NotIn(RESERVED_DATA_KEYS): vol.Any(str, int, bool, float, dict, list)}) 

203MOBILE_DEVICE_SCHEMA = vol.Schema({ 

204 vol.Optional(CONF_MANUFACTURER): cv.string, 

205 vol.Optional(CONF_MODEL): cv.string, 

206 vol.Optional(CONF_NOTIFY_ACTION): cv.string, 

207 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id, 

208}) 

209NOTIFICATION_DUPE_SCHEMA = vol.Schema({ 

210 vol.Optional(CONF_TTL): cv.positive_int, 

211 vol.Optional(CONF_SIZE, default=100): cv.positive_int, 

212 vol.Optional(CONF_DUPE_POLICY, default=ATTR_DUPE_POLICY_MTSLP): vol.In([ATTR_DUPE_POLICY_MTSLP, ATTR_DUPE_POLICY_NONE]), 

213}) 

214DELIVERY_CUSTOMIZE_SCHEMA = vol.Schema({ 

215 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

216 vol.Optional(CONF_ENABLED, default=True): cv.boolean, 

217 vol.Optional(CONF_DATA): DATA_SCHEMA, 

218}) 

219LINK_SCHEMA = vol.Schema({ 

220 vol.Optional(CONF_ID): cv.string, 

221 vol.Required(CONF_URL): cv.url, 

222 vol.Optional(CONF_ICON): cv.icon, 

223 vol.Required(CONF_DESCRIPTION): cv.string, 

224 vol.Optional(CONF_NAME): cv.string, 

225}) 

226DELIVERY_CONFIG_SCHEMA = vol.Schema({ 

227 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

228 vol.Optional(CONF_ACTION): cv.service, # previously 'service:' 

229 vol.Optional(CONF_OPTIONS, default=dict): dict, 

230 vol.Optional(CONF_DATA): DATA_SCHEMA, 

231 vol.Optional(CONF_SELECTION, default=[SELECTION_DEFAULT]): vol.All(cv.ensure_list, [vol.In(SELECTION_VALUES)]), 

232 vol.Optional(CONF_PRIORITY, default=PRIORITY_VALUES): vol.All(cv.ensure_list, [vol.In(PRIORITY_VALUES)]), 

233}) 

234METHOD_SCHEMA = vol.Schema({ 

235 vol.Optional(CONF_TARGETS_REQUIRED): cv.boolean, 

236 vol.Optional(CONF_DEVICE_DOMAIN): vol.All(cv.ensure_list, [cv.string]), 

237 vol.Optional(CONF_DEVICE_DISCOVERY): cv.boolean, 

238 vol.Optional(CONF_DEFAULT): DELIVERY_CONFIG_SCHEMA, 

239}) 

240RECIPIENT_SCHEMA = vol.Schema({ 

241 vol.Required(CONF_PERSON): cv.entity_id, 

242 vol.Optional(CONF_ALIAS): cv.string, 

243 vol.Optional(CONF_EMAIL): cv.string, 

244 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

245 vol.Optional(CONF_PHONE_NUMBER): cv.string, 

246 vol.Optional(CONF_MOBILE_DISCOVERY, default=True): cv.boolean, 

247 vol.Optional(CONF_MOBILE_DEVICES, default=list): vol.All(cv.ensure_list, [MOBILE_DEVICE_SCHEMA]), 

248 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_CUSTOMIZE_SCHEMA}, 

249}) 

250CAMERA_SCHEMA = vol.Schema({ 

251 vol.Required(CONF_CAMERA): cv.entity_id, 

252 vol.Optional(CONF_ALT_CAMERA): vol.All(cv.ensure_list, [cv.entity_id]), 

253 vol.Optional(CONF_ALIAS): cv.string, 

254 vol.Optional(CONF_URL): cv.url, 

255 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id, 

256 vol.Optional(CONF_PTZ_PRESET_DEFAULT, default=1): vol.Any(cv.positive_int, cv.string), 

257 vol.Optional(CONF_PTZ_DELAY, default=0): int, 

258 vol.Optional(CONF_PTZ_METHOD, default=PTZ_METHOD_ONVIF): vol.In(PTZ_METHOD_VALUES), 

259}) 

260MEDIA_SCHEMA = vol.Schema({ 

261 vol.Optional(ATTR_MEDIA_CAMERA_ENTITY_ID): cv.entity_id, 

262 vol.Optional(ATTR_MEDIA_CAMERA_DELAY, default=0): int, 

263 vol.Optional(ATTR_MEDIA_CAMERA_PTZ_PRESET): vol.Any(cv.positive_int, cv.string), 

264 # URL fragments allowed 

265 vol.Optional(ATTR_MEDIA_CLIP_URL): vol.Any(cv.url, cv.string), 

266 vol.Optional(ATTR_MEDIA_SNAPSHOT_URL): vol.Any(cv.url, cv.string), 

267 vol.Optional(ATTR_JPEG_OPTS): dict, 

268}) 

269 

270DELIVERY_SCHEMA = DELIVERY_CONFIG_SCHEMA.extend({ 

271 vol.Optional(CONF_ALIAS): cv.string, 

272 vol.Required(CONF_METHOD): vol.In(METHOD_VALUES), 

273 vol.Optional(CONF_TEMPLATE): cv.string, 

274 vol.Optional(CONF_DEFAULT, default=False): cv.boolean, 

275 vol.Optional(CONF_MESSAGE): vol.Any(None, cv.string), 

276 vol.Optional(CONF_TITLE): vol.Any(None, cv.string), 

277 vol.Optional(CONF_ENABLED, default=True): cv.boolean, 

278 vol.Optional(CONF_OCCUPANCY, default=OCCUPANCY_ALL): vol.In(OCCUPANCY_VALUES), 

279 vol.Optional(CONF_CONDITION): cv.CONDITION_SCHEMA, 

280}) 

281 

282SCENARIO_SCHEMA = vol.Schema({ 

283 vol.Optional(CONF_ALIAS): cv.string, 

284 vol.Optional(CONF_CONDITION): cv.CONDITION_SCHEMA, 

285 vol.Optional(CONF_MEDIA): MEDIA_SCHEMA, 

286 vol.Optional(CONF_ACTION_GROUP_NAMES, default=[]): vol.All(cv.ensure_list, [cv.string]), 

287 vol.Optional(CONF_DELIVERY_SELECTION): vol.In(DELIVERY_SELECTION_VALUES), 

288 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)}, 

289}) 

290ACTION_CALL_SCHEMA = vol.Schema( 

291 { 

292 vol.Optional(ATTR_ACTION): cv.string, 

293 vol.Optional(ATTR_TITLE): cv.string, 

294 vol.Optional(ATTR_ACTION_CATEGORY): cv.string, 

295 vol.Optional(ATTR_ACTION_URL): cv.url, 

296 vol.Optional(ATTR_ACTION_URL_TITLE): cv.string, 

297 }, 

298 extra=vol.ALLOW_EXTRA, 

299) 

300ACTION_SCHEMA = vol.Schema( 

301 { 

302 vol.Exclusive(CONF_ACTION, CONF_ACTION_TEMPLATE): cv.string, 

303 vol.Exclusive(CONF_TITLE, CONF_TITLE_TEMPLATE): cv.string, 

304 vol.Optional(CONF_URI): cv.url, 

305 vol.Optional(CONF_ICON): cv.string, 

306 }, 

307 extra=vol.ALLOW_EXTRA, 

308) 

309 

310 

311ARCHIVE_SCHEMA = vol.Schema({ 

312 vol.Optional(CONF_ARCHIVE_PATH): cv.path, 

313 vol.Optional(CONF_ENABLED, default=False): cv.boolean, 

314 vol.Optional(CONF_ARCHIVE_DAYS, default=3): cv.positive_int, 

315 vol.Optional(CONF_ARCHIVE_MQTT_TOPIC): cv.string, 

316 vol.Optional(CONF_ARCHIVE_MQTT_QOS, default=0): cv.positive_int, 

317 vol.Optional(CONF_ARCHIVE_MQTT_RETAIN, default=True): cv.boolean, 

318}) 

319 

320HOUSEKEEPING_SCHEMA = vol.Schema({ 

321 vol.Optional(CONF_HOUSEKEEPING_TIME, default="00:00:01"): cv.time, 

322}) 

323 

324PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 

325 vol.Optional(CONF_TEMPLATE_PATH, default=TEMPLATE_DIR): cv.path, 

326 vol.Optional(CONF_MEDIA_PATH, default=MEDIA_DIR): cv.path, 

327 vol.Optional(CONF_ARCHIVE, default={CONF_ENABLED: False}): ARCHIVE_SCHEMA, 

328 vol.Optional(CONF_HOUSEKEEPING, default={}): HOUSEKEEPING_SCHEMA, 

329 vol.Optional(CONF_DUPE_CHECK, default=dict): NOTIFICATION_DUPE_SCHEMA, 

330 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_SCHEMA}, 

331 vol.Optional(CONF_ACTION_GROUPS, default=dict): {cv.string: [ACTION_SCHEMA]}, 

332 vol.Optional(CONF_RECIPIENTS, default=list): vol.All(cv.ensure_list, [RECIPIENT_SCHEMA]), 

333 vol.Optional(CONF_LINKS, default=list): vol.All(cv.ensure_list, [LINK_SCHEMA]), 

334 vol.Optional(CONF_SCENARIOS, default=dict): {cv.string: SCENARIO_SCHEMA}, 

335 vol.Optional(CONF_METHODS, default=dict): {cv.string: METHOD_SCHEMA}, 

336 vol.Optional(CONF_CAMERAS, default=list): vol.All(cv.ensure_list, [CAMERA_SCHEMA]), 

337}) 

338SUPERNOTIFY_SCHEMA = PLATFORM_SCHEMA 

339 

340ACTION_DATA_SCHEMA = vol.Schema( 

341 { 

342 vol.Optional(ATTR_DELIVERY): vol.Any(cv.string, [cv.string], {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)}), 

343 vol.Optional(ATTR_PRIORITY): vol.In(PRIORITY_VALUES), 

344 vol.Optional(ATTR_SCENARIOS_REQUIRE): vol.All(cv.ensure_list, [cv.string]), 

345 vol.Optional(ATTR_SCENARIOS_APPLY): vol.All(cv.ensure_list, [cv.string]), 

346 vol.Optional(ATTR_SCENARIOS_CONSTRAIN): vol.All(cv.ensure_list, [cv.string]), 

347 vol.Optional(ATTR_DELIVERY_SELECTION): vol.In(DELIVERY_SELECTION_VALUES), 

348 vol.Optional(ATTR_RECIPIENTS): vol.All(cv.ensure_list, [cv.entity_id]), 

349 vol.Optional(ATTR_MEDIA): MEDIA_SCHEMA, 

350 vol.Optional(ATTR_MESSAGE_HTML): cv.string, 

351 vol.Optional(ATTR_ACTION_GROUPS, default=[]): vol.All(cv.ensure_list, [cv.string]), 

352 vol.Optional(ATTR_ACTIONS, default=[]): vol.All(cv.ensure_list, [ACTION_CALL_SCHEMA]), 

353 vol.Optional(ATTR_DEBUG, default=False): cv.boolean, 

354 vol.Optional(ATTR_DATA): vol.Any(None, DATA_SCHEMA), 

355 }, 

356 extra=vol.ALLOW_EXTRA, # allow other data, e.g. the android/ios mobile push 

357) 

358 

359STRICT_ACTION_DATA_SCHEMA = ACTION_DATA_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) 

360 

361 

362class TargetType(StrEnum): 

363 pass 

364 

365 

366class GlobalTargetType(TargetType): 

367 NONCRITICAL = "NONCRITICAL" 

368 EVERYTHING = "EVERYTHING" 

369 

370 

371class RecipientType(StrEnum): 

372 USER = "USER" 

373 EVERYONE = "EVERYONE" 

374 

375 

376class QualifiedTargetType(TargetType): 

377 METHOD = "METHOD" 

378 DELIVERY = "DELIVERY" 

379 CAMERA = "CAMERA" 

380 PRIORITY = "PRIORITY" 

381 ACTION = "ACTION" 

382 

383 

384class CommandType(StrEnum): 

385 SNOOZE = "SNOOZE" 

386 SILENCE = "SILENCE" 

387 NORMAL = "NORMAL" 

388 

389 

390class MessageOnlyPolicy(StrEnum): 

391 STANDARD = "STANDARD" # independent title and message 

392 USE_TITLE = "USE_TITLE" # use title in place of message, no title 

393 COMBINE_TITLE = "COMBINE_TITLE" # use combined title and message as message, no title 

394 

395 

396@dataclass 

397class ConditionVariables: 

398 """Variables presented to all condition evaluations 

399 

400 Attributes 

401 ---------- 

402 applied_scenarios (list[str]): Scenarios that have been applied 

403 required_scenarios (list[str]): Scenarios that must be applied 

404 constrain_scenarios (list[str]): Only scenarios in this list, or in explicit apply_scenarios, can be applied 

405 notification_priority (str): Priority of the notification 

406 notification_message (str): Message of the notification 

407 notification_title (str): Title of the notification 

408 occupancy (list[str]): List of occupancy scenarios 

409 

410 """ 

411 

412 applied_scenarios: list[str] = field(default_factory=list) 

413 required_scenarios: list[str] = field(default_factory=list) 

414 constrain_scenarios: list[str] = field(default_factory=list) 

415 notification_priority: str = PRIORITY_MEDIUM 

416 notification_message: str = "" 

417 notification_title: str = "" 

418 occupancy: list[str] = field(default_factory=list) 

419 

420 def __init__( 

421 self, 

422 applied_scenarios: list[str] | None = None, 

423 required_scenarios: list[str] | None = None, 

424 constrain_scenarios: list[str] | None = None, 

425 delivery_priority: str | None = PRIORITY_MEDIUM, 

426 occupiers: dict[str, list[dict[str, Any]]] | None = None, 

427 message: str | None = None, 

428 title: str | None = None, 

429 ) -> None: 

430 occupiers = occupiers or {} 

431 self.occupancy = [] 

432 if not occupiers.get(STATE_NOT_HOME) and occupiers.get(STATE_HOME): 

433 self.occupancy.append("ALL_HOME") 

434 elif occupiers.get(STATE_NOT_HOME) and not occupiers.get(STATE_HOME): 

435 self.occupancy.append("ALL_AWAY") 

436 if len(occupiers.get(STATE_HOME, [])) == 1: 

437 self.occupancy.extend(["LONE_HOME", "SOME_HOME"]) 

438 elif len(occupiers.get(STATE_HOME, [])) > 1 and occupiers.get(STATE_NOT_HOME): 

439 self.occupancy.extend(["MULTI_HOME", "SOME_HOME"]) 

440 self.applied_scenarios = applied_scenarios or [] 

441 self.required_scenarios = required_scenarios or [] 

442 self.constrain_scenarios = constrain_scenarios or [] 

443 self.notification_priority = delivery_priority or PRIORITY_MEDIUM 

444 self.notification_message = message or "" 

445 self.notification_title = title or "" 

446 

447 def as_dict(self) -> dict[str, Any]: 

448 return { 

449 "applied_scenarios": self.applied_scenarios, 

450 "required_scenarios": self.required_scenarios, 

451 "constrain_scenarios": self.constrain_scenarios, 

452 "notification_message": self.notification_message, 

453 "notification_title": self.notification_title, 

454 "occupancy": self.occupancy, 

455 }