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

206 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-10-18 09:29 +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_ENTITIES, 

20 CONF_ICON, 

21 CONF_ID, 

22 CONF_NAME, 

23 CONF_PLATFORM, 

24 CONF_TARGET, 

25 CONF_URL, 

26 STATE_HOME, 

27 STATE_NOT_HOME, 

28 Platform, 

29) 

30from homeassistant.helpers import config_validation as cv 

31 

32from custom_components.supernotify.common import format_timestamp as format_timestamp 

33 

34DOMAIN = "supernotify" 

35 

36PLATFORMS = [Platform.NOTIFY] 

37TEMPLATE_DIR = "/config/templates/supernotify" 

38MEDIA_DIR = "supernotify/media" 

39 

40CONF_ACTIONS = "actions" 

41CONF_TITLE = "title" 

42CONF_URI = "uri" 

43CONF_RECIPIENTS = "recipients" 

44CONF_TEMPLATE_PATH = "template_path" 

45CONF_MEDIA_PATH = "media_path" 

46CONF_HOUSEKEEPING = "housekeeping" 

47CONF_HOUSEKEEPING_TIME = "housekeeping_time" 

48CONF_ARCHIVE_PATH = "archive_path" 

49CONF_ARCHIVE = "archive" 

50CONF_ARCHIVE_DAYS = "archive_days" 

51CONF_ARCHIVE_MQTT_TOPIC = "archive_mqtt_topic" 

52CONF_ARCHIVE_MQTT_QOS = "archive_mqtt_qos" 

53CONF_ARCHIVE_MQTT_RETAIN = "archive_mqtt_retain" 

54CONF_TEMPLATE = "template" 

55CONF_LINKS = "links" 

56CONF_PERSON = "person" 

57CONF_METHOD = "method" 

58CONF_METHODS = "methods" 

59CONF_DELIVERY = "delivery" 

60CONF_SELECTION = "selection" 

61 

62CONF_DATA: str = "data" 

63CONF_OPTIONS: str = "options" 

64CONF_MOBILE: str = "mobile" 

65CONF_NOTIFY: str = "notify" 

66CONF_NOTIFY_ACTION: str = "notify_action" 

67CONF_PHONE_NUMBER: str = "phone_number" 

68CONF_PRIORITY: str = "priority" 

69CONF_OCCUPANCY: str = "occupancy" 

70CONF_SCENARIOS: str = "scenarios" 

71CONF_MANUFACTURER: str = "manufacturer" 

72CONF_DEVICE_TRACKER: str = "device_tracker" 

73CONF_DEVICE_NAME: str = "device_name" 

74CONF_DEVICE_LABELS: str = "device_labels" 

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" 

93 

94OCCUPANCY_ANY_IN = "any_in" 

95OCCUPANCY_ANY_OUT = "any_out" 

96OCCUPANCY_ALL_IN = "all_in" 

97OCCUPANCY_ALL = "all" 

98OCCUPANCY_NONE = "none" 

99OCCUPANCY_ALL_OUT = "all_out" 

100OCCUPANCY_ONLY_IN = "only_in" 

101OCCUPANCY_ONLY_OUT = "only_out" 

102 

103ATTR_PRIORITY = "priority" 

104ATTR_ACTION = "action" 

105ATTR_SCENARIOS_REQUIRE = "require_scenarios" 

106ATTR_SCENARIOS_APPLY = "apply_scenarios" 

107ATTR_SCENARIOS_CONSTRAIN = "constrain_scenarios" 

108ATTR_DELIVERY = "delivery" 

109ATTR_DEFAULT = "default" 

110ATTR_NOTIFICATION_ID = "notification_id" 

111ATTR_DELIVERY_SELECTION = "delivery_selection" 

112ATTR_RECIPIENTS = "recipients" 

113ATTR_DATA = "data" 

114ATTR_MEDIA = "media" 

115ATTR_TITLE = "title" 

116ATTR_MEDIA_SNAPSHOT_URL = "snapshot_url" 

117ATTR_MEDIA_CAMERA_ENTITY_ID = "camera_entity_id" 

118ATTR_MEDIA_CAMERA_DELAY = "camera_delay" 

119ATTR_MEDIA_CAMERA_PTZ_PRESET = "camera_ptz_preset" 

120ATTR_MEDIA_CLIP_URL = "clip_url" 

121ATTR_ACTION_GROUPS = "action_groups" 

122CONF_ACTION_GROUP_NAMES = "action_groups" 

123ATTR_ACTION_CATEGORY = "action_category" 

124ATTR_ACTION_URL = "action_url" 

125ATTR_ACTION_URL_TITLE = "action_url_title" 

126ATTR_MESSAGE_HTML = "message_html" 

127ATTR_JPEG_FLAGS = "jpeg_flags" 

128ATTR_TIMESTAMP = "timestamp" 

129ATTR_DEBUG = "debug" 

130ATTR_ACTIONS = "actions" 

131ATTR_USER_ID = "user_id" 

132 

133DELIVERY_SELECTION_IMPLICIT = "implicit" 

134DELIVERY_SELECTION_EXPLICIT = "explicit" 

135DELIVERY_SELECTION_FIXED = "fixed" 

136 

137DELIVERY_SELECTION_VALUES = [DELIVERY_SELECTION_EXPLICIT, DELIVERY_SELECTION_FIXED, DELIVERY_SELECTION_IMPLICIT] 

138PTZ_METHOD_ONVIF = "onvif" 

139PTZ_METHOD_FRIGATE = "frigate" 

140PTZ_METHOD_VALUES = [PTZ_METHOD_ONVIF, PTZ_METHOD_FRIGATE] 

141 

142SELECTION_FALLBACK_ON_ERROR = "fallback_on_error" 

143SELECTION_FALLBACK = "fallback" 

144SELECTION_BY_SCENARIO = "scenario" 

145SELECTION_DEFAULT = "default" 

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

147 

148OCCUPANCY_VALUES = [ 

149 OCCUPANCY_ALL_IN, 

150 OCCUPANCY_ALL_OUT, 

151 OCCUPANCY_ANY_IN, 

152 OCCUPANCY_ANY_OUT, 

153 OCCUPANCY_ONLY_IN, 

154 OCCUPANCY_ONLY_OUT, 

155 OCCUPANCY_ALL, 

156 OCCUPANCY_NONE, 

157] 

158 

159PRIORITY_CRITICAL = "critical" 

160PRIORITY_HIGH = "high" 

161PRIORITY_MEDIUM = "medium" 

162PRIORITY_LOW = "low" 

163 

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

165METHOD_SMS = "sms" 

166METHOD_EMAIL = "email" 

167METHOD_ALEXA = "alexa" 

168METHOD_ALEXA_MEDIA_PLAYER = "alexa_media_player" 

169METHOD_MOBILE_PUSH = "mobile_push" 

170METHOD_MEDIA = "media" 

171METHOD_CHIME = "chime" 

172METHOD_GENERIC = "generic" 

173METHOD_PERSISTENT = "persistent" 

174METHOD_VALUES = [ 

175 METHOD_SMS, 

176 METHOD_ALEXA, 

177 METHOD_ALEXA_MEDIA_PLAYER, 

178 METHOD_MOBILE_PUSH, 

179 METHOD_CHIME, 

180 METHOD_EMAIL, 

181 METHOD_MEDIA, 

182 METHOD_PERSISTENT, 

183 METHOD_GENERIC, 

184] 

185 

186SCENARIO_DEFAULT = "DEFAULT" 

187SCENARIO_NULL = "NULL" 

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

189 

190RESERVED_DELIVERY_NAMES = ["ALL"] 

191RESERVED_SCENARIO_NAMES = [SCENARIO_DEFAULT, SCENARIO_NULL] 

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

193 

194CONF_DUPE_CHECK = "dupe_check" 

195CONF_DUPE_POLICY = "dupe_policy" 

196CONF_TTL = "ttl" 

197CONF_SIZE = "size" 

198ATTR_DUPE_POLICY_MTSLP = "dupe_policy_message_title_same_or_lower_priority" 

199ATTR_DUPE_POLICY_NONE = "dupe_policy_none" 

200 

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

202MOBILE_DEVICE_SCHEMA = vol.Schema({ 

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

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

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

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

207}) 

208NOTIFICATION_DUPE_SCHEMA = vol.Schema({ 

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

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

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

212}) 

213DELIVERY_CUSTOMIZE_SCHEMA = vol.Schema({ 

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

215 vol.Optional(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), 

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}) 

226METHOD_DEFAULTS_SCHEMA = vol.Schema({ 

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

228 vol.Optional(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), 

229 vol.Optional(CONF_ACTION): cv.service, 

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

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

232 vol.Optional(CONF_DATA): DATA_SCHEMA, 

233}) 

234RECIPIENT_SCHEMA = vol.Schema({ 

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

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

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

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

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

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

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

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

243}) 

244CAMERA_SCHEMA = vol.Schema({ 

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

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

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

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

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

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

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

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

253}) 

254MEDIA_SCHEMA = vol.Schema({ 

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

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

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

258 # URL fragments allowed 

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

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

261 vol.Optional(ATTR_JPEG_FLAGS): dict, 

262}) 

263DELIVERY_SCHEMA = vol.Schema({ 

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

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

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

267 vol.Optional(CONF_PLATFORM): cv.string, 

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

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

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

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

272 vol.Optional(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), 

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

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

275 vol.Optional(CONF_DATA): DATA_SCHEMA, 

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

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

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

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

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

281}) 

282 

283SCENARIO_SCHEMA = vol.Schema({ 

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

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

286 vol.Optional(CONF_MEDIA): MEDIA_SCHEMA, 

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

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

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

290}) 

291ACTION_CALL_SCHEMA = vol.Schema( 

292 { 

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

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

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

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

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

298 }, 

299 extra=vol.ALLOW_EXTRA, 

300) 

301ACTION_SCHEMA = vol.Schema( 

302 { 

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

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

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

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

307 }, 

308 extra=vol.ALLOW_EXTRA, 

309) 

310 

311 

312ARCHIVE_SCHEMA = vol.Schema({ 

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

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

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

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

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

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

319}) 

320 

321HOUSEKEEPING_SCHEMA = vol.Schema({ 

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

323}) 

324 

325PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 

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

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

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

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

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

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

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

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

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

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

336 vol.Optional(CONF_METHODS, default=dict): {cv.string: METHOD_DEFAULTS_SCHEMA}, 

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

338}) 

339SUPERNOTIFY_SCHEMA = PLATFORM_SCHEMA 

340 

341ACTION_DATA_SCHEMA = vol.Schema( 

342 { 

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

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

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

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

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

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

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

350 vol.Optional(ATTR_MEDIA): MEDIA_SCHEMA, 

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

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

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

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

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

356 }, 

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

358) 

359 

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

361 

362 

363class TargetType(StrEnum): 

364 pass 

365 

366 

367class GlobalTargetType(TargetType): 

368 NONCRITICAL = "NONCRITICAL" 

369 EVERYTHING = "EVERYTHING" 

370 

371 

372class RecipientType(StrEnum): 

373 USER = "USER" 

374 EVERYONE = "EVERYONE" 

375 

376 

377class QualifiedTargetType(TargetType): 

378 METHOD = "METHOD" 

379 DELIVERY = "DELIVERY" 

380 CAMERA = "CAMERA" 

381 PRIORITY = "PRIORITY" 

382 ACTION = "ACTION" 

383 

384 

385class CommandType(StrEnum): 

386 SNOOZE = "SNOOZE" 

387 SILENCE = "SILENCE" 

388 NORMAL = "NORMAL" 

389 

390 

391@dataclass 

392class ConditionVariables: 

393 """Variables presented to all condition evaluations 

394 

395 Attributes 

396 ---------- 

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

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

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

400 notification_priority (str): Priority of the notification 

401 notification_message (str): Message of the notification 

402 notification_title (str): Title of the notification 

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

404 

405 """ 

406 

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

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

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

410 notification_priority: str = PRIORITY_MEDIUM 

411 notification_message: str = "" 

412 notification_title: str = "" 

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

414 

415 def __init__( 

416 self, 

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

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

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

420 delivery_priority: str | None = PRIORITY_MEDIUM, 

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

422 message: str | None = None, 

423 title: str | None = None, 

424 ) -> None: 

425 occupiers = occupiers or {} 

426 self.occupancy = [] 

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

428 self.occupancy.append("ALL_HOME") 

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

430 self.occupancy.append("ALL_AWAY") 

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

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

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

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

435 self.applied_scenarios = applied_scenarios or [] 

436 self.required_scenarios = required_scenarios or [] 

437 self.constrain_scenarios = constrain_scenarios or [] 

438 self.notification_priority = delivery_priority or PRIORITY_MEDIUM 

439 self.notification_message = message or "" 

440 self.notification_title = title or "" 

441 

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

443 return { 

444 "applied_scenarios": self.applied_scenarios, 

445 "required_scenarios": self.required_scenarios, 

446 "constrain_scenarios": self.constrain_scenarios, 

447 "notification_message": self.notification_message, 

448 "notification_title": self.notification_title, 

449 "occupancy": self.occupancy, 

450 }