diff --git a/README.md b/README.md index 89913cf..ddf29bb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Quality Scale: Platinum](https://img.shields.io/badge/Quality%20Scale-platinum-platinum.svg)](https://github.com/ccpk1/choreops) [![Quality Gates](https://img.shields.io/github/actions/workflow/status/ccpk1/choreops/lint-validation.yaml?branch=main&label=Quality%20Gates)](https://github.com/ccpk1/choreops/actions/workflows/lint-validation.yaml) [![Crowdin](https://badges.crowdin.net/choreops-translations/localized.svg)](https://crowdin.com/project/choreops-translations) -[![License](https://img.shields.io/static/v1?label=License&message=GPL-3.0&color=1E88E5&labelColor=555)](https://github.com/ccpk1/choreops/blob/main/LICENSE) +[![License](https://img.shields.io/static/v1?label=License&message=GPL-3.0&color=1E88E5&labelColor=555)](https://github.com/ccpk1/choreops/blob/main/LICENSE) [![HACS Custom](https://img.shields.io/static/v1?label=HACS&message=custom&color=1E88E5&labelColor=555)](https://github.com/custom-components/hacs)
[![Version](https://img.shields.io/github/v/release/ccpk1/choreops?include_prereleases&label=Version&color=1E88E5)](https://github.com/ccpk1/choreops/releases) [![Stars](https://img.shields.io/github/stars/ccpk1/choreops)](https://github.com/ccpk1/choreops/stargazers) @@ -98,9 +98,13 @@ ChoreOps ships with a functional dashboard starter experience, but it is designe - **Rich sensor data**: granular attributes for dashboards and analytics - **Service-level control**: automate create/claim/approve/redeem/adjust actions +- **Live chore CRUD updates**: chore create, edit, and delete update runtime sensors, workflow buttons, and dashboard helper payloads without a full integration reload - **Automation-first architecture**: integrate with scripts, automations, dashboards, voice, and Node-RED - **Multi-instance support**: run multiple ChoreOps entries in the same Home Assistant instance +> [!TIP] +> Chore create, edit, and delete now update runtime chore entities live. Sanctioned system settings changes still reload the integration when that remains the correct Home Assistant boundary. + --- ### 📸 See ChoreOps in Action diff --git a/custom_components/choreops/__init__.py b/custom_components/choreops/__init__.py index a2c5160..ec7122d 100644 --- a/custom_components/choreops/__init__.py +++ b/custom_components/choreops/__init__.py @@ -433,6 +433,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ChoreOpsConfigEntry) -> coordinator = entry.runtime_data if coordinator: coordinator._persist(immediate=True) # Unload must be immediate + coordinator.ui_manager.clear_runtime_state() const.LOGGER.debug("Forced immediate persist before unload") # Clear translation cache to prevent stale translations on reload diff --git a/custom_components/choreops/button.py b/custom_components/choreops/button.py index 5887e93..bc5caa0 100644 --- a/custom_components/choreops/button.py +++ b/custom_components/choreops/button.py @@ -21,6 +21,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity_registry import async_get from . import const from .coordinator import ChoreOpsConfigEntry, ChoreOpsDataCoordinator @@ -48,6 +49,153 @@ PARALLEL_UPDATES = 1 +_async_add_entities_callback: AddEntitiesCallback | None = None + + +def register_chore_button_callback(async_add_entities: AddEntitiesCallback) -> None: + """Register async_add_entities callback for dynamic chore button creation.""" + global _async_add_entities_callback # noqa: PLW0603 + _async_add_entities_callback = async_add_entities + + +def _button_entity_exists( + coordinator: ChoreOpsDataCoordinator, + unique_id: str, +) -> bool: + """Return whether a button entity with the given unique ID already exists.""" + entity_registry = async_get(coordinator.hass) + return ( + entity_registry.async_get_entity_id("button", const.DOMAIN, unique_id) + is not None + ) + + +def create_chore_button_entities( + coordinator: ChoreOpsDataCoordinator, + chore_id: str, + *, + assignee_ids: list[str] | None = None, + replace_existing: bool = False, +) -> int: + """Create missing chore-linked workflow buttons for a chore. + + Args: + coordinator: Runtime coordinator. + chore_id: Internal ID of the chore. + assignee_ids: Optional subset of assignee IDs to create buttons for. + When omitted, create buttons for all currently assigned assignees. + replace_existing: If True, do not filter existing registry entities. This is + intended for flows that have already removed the prior entity set. + + Returns: + Count of entities handed to Home Assistant for creation. + """ + if _async_add_entities_callback is None: + const.LOGGER.warning("Cannot create chore buttons: callback not registered") + return 0 + + chore_info = coordinator.chores_data.get(chore_id) + if not chore_info: + const.LOGGER.warning( + "Cannot create chore buttons: chore %s not found", chore_id + ) + return 0 + + chore_name = str( + chore_info.get( + const.DATA_CHORE_NAME, f"{const.TRANS_KEY_LABEL_CHORE} {chore_id}" + ) + ) + assigned_assignees_ids = chore_info.get(const.DATA_CHORE_ASSIGNED_USER_IDS, []) + target_assignee_ids = assigned_assignees_ids + if assignee_ids is not None: + target_assignee_ids = [ + assignee_id + for assignee_id in assigned_assignees_ids + if assignee_id in assignee_ids + ] + + chore_claim_icon = chore_info.get(const.DATA_CHORE_ICON, const.SENTINEL_EMPTY) + chore_approve_icon = chore_info.get(const.DATA_CHORE_ICON, const.SENTINEL_EMPTY) + entry = coordinator.config_entry + entities: list[ButtonEntity] = [] + + for assignee_id in target_assignee_ids: + assignee_name = ( + get_assignee_name_by_id(coordinator, assignee_id) + or f"{const.TRANS_KEY_LABEL_ASSIGNEE} {assignee_id}" + ) + + claim_unique_id = f"{entry.entry_id}_{assignee_id}_{chore_id}{const.BUTTON_KC_UID_SUFFIX_CLAIM}" + if should_create_entity_for_user_assignee( + const.BUTTON_KC_UID_SUFFIX_CLAIM, + coordinator, + assignee_id, + ) and ( + replace_existing or not _button_entity_exists(coordinator, claim_unique_id) + ): + entities.append( + AssigneeChoreClaimButton( + coordinator=coordinator, + entry=entry, + assignee_id=assignee_id, + assignee_name=assignee_name, + chore_id=chore_id, + chore_name=chore_name, + icon=chore_claim_icon, + ) + ) + + approve_unique_id = f"{entry.entry_id}_{assignee_id}_{chore_id}{const.BUTTON_KC_UID_SUFFIX_APPROVE}" + if should_create_entity_for_user_assignee( + const.BUTTON_KC_UID_SUFFIX_APPROVE, + coordinator, + assignee_id, + ) and ( + replace_existing + or not _button_entity_exists(coordinator, approve_unique_id) + ): + entities.append( + ApproverChoreApproveButton( + coordinator=coordinator, + entry=entry, + assignee_id=assignee_id, + assignee_name=assignee_name, + chore_id=chore_id, + chore_name=chore_name, + icon=chore_approve_icon, + ) + ) + + disapprove_unique_id = f"{entry.entry_id}_{assignee_id}_{chore_id}{const.BUTTON_KC_UID_SUFFIX_DISAPPROVE}" + if should_create_entity_for_user_assignee( + const.BUTTON_KC_UID_SUFFIX_DISAPPROVE, + coordinator, + assignee_id, + ) and ( + replace_existing + or not _button_entity_exists(coordinator, disapprove_unique_id) + ): + entities.append( + ApproverChoreDisapproveButton( + coordinator=coordinator, + entry=entry, + assignee_id=assignee_id, + assignee_name=assignee_name, + chore_id=chore_id, + chore_name=chore_name, + ) + ) + + if entities: + _async_add_entities_callback(entities) + const.LOGGER.debug( + "Created %d chore-linked buttons for chore: %s", len(entities), chore_name + ) + + return len(entities) + + async def async_setup_entry( hass: HomeAssistant, entry: ChoreOpsConfigEntry, @@ -55,6 +203,7 @@ async def async_setup_entry( ) -> None: """Set up dynamic buttons.""" coordinator = entry.runtime_data + register_chore_button_callback(async_add_entities) points_label = entry.options.get( const.CONF_POINTS_LABEL, const.DEFAULT_POINTS_LABEL diff --git a/custom_components/choreops/const.py b/custom_components/choreops/const.py index f454f98..6758740 100644 --- a/custom_components/choreops/const.py +++ b/custom_components/choreops/const.py @@ -2274,6 +2274,9 @@ def set_default_timezone(hass): TRANS_KEY_PURPOSE_ACHIEVEMENT_PROGRESS: Final = "purpose_achievement_progress" TRANS_KEY_PURPOSE_CHALLENGE_PROGRESS: Final = "purpose_challenge_progress" TRANS_KEY_PURPOSE_DASHBOARD_HELPER: Final = "purpose_dashboard_helper" +TRANS_KEY_PURPOSE_DASHBOARD_CHORE_SHARD_HELPER: Final = ( + "purpose_dashboard_chore_shard_helper" +) TRANS_KEY_PURPOSE_DASHBOARD_TRANSLATION: Final = "purpose_dashboard_translation" TRANS_KEY_PURPOSE_SYSTEM_DASHBOARD_HELPER: Final = "purpose_system_dashboard_helper" # Legacy sensor purposes (sensor_legacy.py) @@ -2517,12 +2520,17 @@ def set_default_timezone(hass): # Dashboard Helper Sensor Attributes ATTR_CHORES_BY_LABEL: Final = "chores_by_label" +ATTR_CHORE_HELPER_EIDS: Final = "chore_helper_eids" ATTR_CHORE_CLAIMED_BY: Final = "claimed_by" ATTR_CHORE_COMPLETED_BY: Final = "completed_by" ATTR_CHORE_DUE_DATE: Final = "due_date" +ATTR_HELPER_CONTRACT_VERSION: Final = "helper_contract_version" ATTR_CHORE_IS_TODAY_AM: Final = "is_today_am" ATTR_CHORE_LABELS: Final = "labels" ATTR_CHORE_PRIMARY_GROUP: Final = "primary_group" +ATTR_SHARD_COUNT: Final = "shard_count" +ATTR_SHARD_INDEX: Final = "shard_index" +ATTR_SHARD_RUNTIME: Final = "shard_runtime" ATTR_UI_CONTROL: Final = "ui_control" ATTR_UI_ROOT: Final = "ui_root" ATTR_UI_ROOT_SHARED_ADMIN: Final = "shared_admin" @@ -2552,6 +2560,13 @@ def set_default_timezone(hass): PRIMARY_GROUP_THIS_WEEK = "this_week" PRIMARY_GROUP_OTHER = "other" +HELPER_CONTRACT_VERSION_V1: Final = 1 +HELPER_SHARD_FAMILY_CHORES: Final = "chores" +HELPER_SHARD_MODE_INLINE: Final = "inline" +HELPER_SHARD_MODE_SHARDED: Final = "sharded" +HELPER_SHARD_ENTER_BYTES: Final = 14 * 1024 +HELPER_SHARD_EXIT_BYTES: Final = 12 * 1024 + # ================================================================================================ # Entity UID Constants (SUFFIX patterns) @@ -2595,6 +2610,9 @@ def set_default_timezone(hass): # Sensor Entity ID constants still in active use (runtime) SENSOR_KC_UID_SUFFIX_UI_DASHBOARD_HELPER: Final = "_dashboard_helper" +SENSOR_KC_UID_SUFFIX_UI_DASHBOARD_CHORE_SHARD_HELPER: Final = ( + "_dashboard_chore_shard_helper" +) SENSOR_KC_UID_SUFFIX_SYSTEM_DASHBOARD_HELPER: Final = "_system_dashboard_helper" # System-level dashboard translation sensor (one per language in use) @@ -3010,6 +3028,7 @@ class EntityRequirement(StrEnum): SENSOR_KC_UID_SUFFIX_CHORE_STATUS_SENSOR: EntityRequirement.ALWAYS, SENSOR_KC_UID_SUFFIX_CHORES_SENSOR: EntityRequirement.ALWAYS, SENSOR_KC_UID_SUFFIX_UI_DASHBOARD_HELPER: EntityRequirement.ALWAYS, + SENSOR_KC_UID_SUFFIX_UI_DASHBOARD_CHORE_SHARD_HELPER: EntityRequirement.ALWAYS, SENSOR_KC_UID_SUFFIX_SYSTEM_DASHBOARD_HELPER: EntityRequirement.ALWAYS, # === SENSORS: Gamification === SENSOR_KC_UID_SUFFIX_ASSIGNEE_POINTS_SENSOR: EntityRequirement.GAMIFICATION, @@ -3635,6 +3654,9 @@ class EntityRequirement(StrEnum): TRANS_KEY_SENSOR_DASHBOARD_TRANSLATION: Final = "system_dashboard_translation_sensor" TRANS_KEY_SENSOR_SYSTEM_DASHBOARD_HELPER: Final = "system_dashboard_helper_sensor" TRANS_KEY_SENSOR_DASHBOARD_HELPER: Final = "assignee_dashboard_helper_sensor" +TRANS_KEY_SENSOR_DASHBOARD_CHORE_LIST_HELPER: Final = ( + "assignee_dashboard_chore_list_helper_sensor" +) # Sensor Attributes Translation Keys diff --git a/custom_components/choreops/coordinator.py b/custom_components/choreops/coordinator.py index 895175c..65ffaa8 100644 --- a/custom_components/choreops/coordinator.py +++ b/custom_components/choreops/coordinator.py @@ -12,10 +12,11 @@ """ import asyncio +from dataclasses import dataclass from datetime import timedelta import sys import time -from typing import Any, cast +from typing import Any, Literal, cast from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -24,6 +25,11 @@ from . import const from .engines.statistics_engine import StatisticsEngine +from .helpers.entity_helpers import ( + remove_entities_by_item_id, + remove_orphaned_assignee_chore_entities, + remove_orphaned_shared_chore_sensors, +) from .managers import ( ChoreManager, EconomyManager, @@ -43,6 +49,8 @@ BadgesCollection, BonusesCollection, ChallengesCollection, + ChoreData, + ChoreEntitySyncContext, ChoresCollection, PenaltiesCollection, RewardsCollection, @@ -55,6 +63,16 @@ type ChoreOpsConfigEntry = ConfigEntry["ChoreOpsDataCoordinator"] +@dataclass(slots=True) +class ChoreEntitySyncResult: + """Result summary for chore runtime entity synchronization.""" + + sensors_created: int = 0 + buttons_created: int = 0 + orphaned_assignee_entities_removed: int = 0 + orphaned_shared_sensors_removed: int = 0 + + class ChoreOpsDataCoordinator(DataUpdateCoordinator): """Coordinator for ChoreOps integration. @@ -387,6 +405,171 @@ async def async_sync_entities_after_service_create(self) -> None: await self.hass.config_entries.async_reload(self.config_entry.entry_id) + async def async_sync_chore_entities( + self, + sync_context: ChoreEntitySyncContext, + ) -> ChoreEntitySyncResult: + """Synchronize chore-linked runtime entities without reloading the entry. + + This orchestration handles graph mutations only. It does not write storage; + callers must invoke it only after manager-owned persistence succeeds. + """ + from .button import create_chore_button_entities + from .sensor import create_chore_entities + + chore_id = sync_context["chore_id"] + mutation = sync_context["mutation"] + result = ChoreEntitySyncResult() + + if mutation == "deleted": + await self.ui_manager.async_reconcile_chore_shards_for_users( + sync_context["affected_user_ids"] + ) + result.orphaned_assignee_entities_removed = ( + await remove_orphaned_assignee_chore_entities( + self.hass, + self.config_entry.entry_id, + self.assignees_data, + self.chores_data, + ) + ) + result.orphaned_shared_sensors_removed = ( + await remove_orphaned_shared_chore_sensors( + self.hass, + self.config_entry.entry_id, + self.chores_data, + ) + ) + self.async_update_listeners() + self.hass.async_create_task( + self.ui_manager.async_finalize_chore_shards_for_users( + sync_context["affected_user_ids"], + registry_only=True, + ) + ) + return result + + rename_sensitive_update = sync_context["rename_sensitive_update"] + assignments_added = sync_context["assignments_added"] + + if rename_sensitive_update: + remove_entities_by_item_id(self.hass, self.config_entry.entry_id, chore_id) + + target_assignee_ids: list[str] | None + replace_existing = False + + if mutation == "created" or rename_sensitive_update: + target_assignee_ids = None + replace_existing = rename_sensitive_update + else: + target_assignee_ids = assignments_added + + current_chore = self.chores_data.get(chore_id) + + if current_chore is not None: + should_create_all = ( + mutation == "created" + or rename_sensitive_update + or sync_context["shared_state_changed"] + ) + creation_target_ids = None if should_create_all else target_assignee_ids + + result.sensors_created = create_chore_entities( + self, + chore_id, + assignee_ids=creation_target_ids, + replace_existing=replace_existing, + ) + result.buttons_created = create_chore_button_entities( + self, + chore_id, + assignee_ids=creation_target_ids, + replace_existing=replace_existing, + ) + + result.orphaned_assignee_entities_removed = ( + await remove_orphaned_assignee_chore_entities( + self.hass, + self.config_entry.entry_id, + self.assignees_data, + self.chores_data, + ) + ) + result.orphaned_shared_sensors_removed = ( + await remove_orphaned_shared_chore_sensors( + self.hass, + self.config_entry.entry_id, + self.chores_data, + ) + ) + await self.ui_manager.async_reconcile_chore_shards_for_users( + sync_context["affected_user_ids"] + ) + self.async_update_listeners() + self.hass.async_create_task( + self.ui_manager.async_finalize_chore_shards_for_users( + sync_context["affected_user_ids"], + registry_only=True, + ) + ) + + const.LOGGER.debug( + "Chore runtime sync completed for %s (%s): sensors=%s buttons=%s removed_assignee=%s removed_shared=%s", + chore_id, + mutation, + result.sensors_created, + result.buttons_created, + result.orphaned_assignee_entities_removed, + result.orphaned_shared_sensors_removed, + ) + return result + + @staticmethod + def build_chore_entity_sync_context( + chore_id: str, + *, + mutation: Literal["created", "updated", "deleted"], + previous_chore: ChoreData | None = None, + current_chore: ChoreData | None = None, + ) -> ChoreEntitySyncContext: + """Build a chore runtime entity sync context from chore snapshots.""" + + def is_shared_chore(chore: ChoreData | None) -> bool: + if not chore: + return False + return chore.get(const.DATA_CHORE_COMPLETION_CRITERIA) in ( + const.COMPLETION_CRITERIA_SHARED, + const.COMPLETION_CRITERIA_SHARED_FIRST, + ) + + previous_assignees = set( + previous_chore.get(const.DATA_CHORE_ASSIGNED_USER_IDS, []) + if previous_chore + else [] + ) + current_assignees = set( + current_chore.get(const.DATA_CHORE_ASSIGNED_USER_IDS, []) + if current_chore + else [] + ) + previous_shared = is_shared_chore(previous_chore) + current_shared = is_shared_chore(current_chore) + + return { + "chore_id": chore_id, + "mutation": mutation, + "assignments_added": sorted(current_assignees - previous_assignees), + "assignments_removed": sorted(previous_assignees - current_assignees), + "affected_user_ids": sorted(previous_assignees | current_assignees), + "rename_sensitive_update": ( + previous_chore is not None + and current_chore is not None + and previous_chore.get(const.DATA_CHORE_NAME) + != current_chore.get(const.DATA_CHORE_NAME) + ), + "shared_state_changed": previous_shared != current_shared, + } + # ------------------------------------------------------------------------------------- # Properties for Easy Access # ------------------------------------------------------------------------------------- diff --git a/custom_components/choreops/dashboards/dashboard_registry.json b/custom_components/choreops/dashboards/dashboard_registry.json index 1a56066..6e00ba8 100644 --- a/custom_components/choreops/dashboards/dashboard_registry.json +++ b/custom_components/choreops/dashboards/dashboard_registry.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "release_version": "1.0.4", + "release_version": "1.0.5", "repo": "ccpk1/ChoreOps-Dashboards", "title": "ChoreOps Dashboard Registry", "description": "Registry manifest for ChoreOps dashboard templates", diff --git a/custom_components/choreops/dashboards/preferences/admin-peruser-kidschores-classic.md b/custom_components/choreops/dashboards/preferences/admin-peruser-kidschores-classic.md index be26557..b5829a6 100644 --- a/custom_components/choreops/dashboards/preferences/admin-peruser-kidschores-classic.md +++ b/custom_components/choreops/dashboards/preferences/admin-peruser-kidschores-classic.md @@ -2,6 +2,12 @@ This template has configurable `pref_*` values in approval action layout. +## Scale note + +- This is an inline-only classic admin template. +- It is intended for smaller households, roughly in the same `20-40` chores-per-user range as the other inline-only classic layouts. +- It is not a shard-aware high-density admin template. + - `pref_points_precision` (default: `fixed_0`) - Controls how point values are formatted in classic admin summary and chore-value displays. - `fixed_0` shows a rounded whole-number display for compact layouts. diff --git a/custom_components/choreops/dashboards/preferences/admin-shared-kidschores-classic.md b/custom_components/choreops/dashboards/preferences/admin-shared-kidschores-classic.md index 149956b..80ab86e 100644 --- a/custom_components/choreops/dashboards/preferences/admin-shared-kidschores-classic.md +++ b/custom_components/choreops/dashboards/preferences/admin-shared-kidschores-classic.md @@ -2,6 +2,12 @@ This template has configurable `pref_*` values in approval action layout. +## Scale note + +- This is an inline-only classic admin template. +- It is intended for smaller households, roughly in the same `20-40` chores-per-user range as the other inline-only classic layouts. +- It is not a shard-aware high-density admin template. + - `pref_points_precision` (default: `fixed_0`) - Controls how point values are formatted in classic admin summary and chore-value displays. - `fixed_0` shows a rounded whole-number display for compact layouts. diff --git a/custom_components/choreops/dashboards/preferences/user-chores-essential-v1.md b/custom_components/choreops/dashboards/preferences/user-chores-essential-v1.md index 5b1bcd8..89aa4dd 100644 --- a/custom_components/choreops/dashboards/preferences/user-chores-essential-v1.md +++ b/custom_components/choreops/dashboards/preferences/user-chores-essential-v1.md @@ -2,6 +2,12 @@ `user-chores-essential-v1` is a lightweight, chore-focused layout that keeps the strong grouping and filtering behavior from the earlier essentials path while using compact chore rows. +## Scale note + +- This is an inline-only small-household template. +- It is intended for roughly `20-25` chores per user, with `~25` as the practical upper bound for reliable rendering. +- It is not a shard-aware high-density template and should not be used as the scale target for larger chore lists. + ## Quick overview - Lightweight by design: focuses on welcome + chores without adding extra dashboard complexity. diff --git a/custom_components/choreops/dashboards/preferences/user-chores-lite-v1.md b/custom_components/choreops/dashboards/preferences/user-chores-lite-v1.md index bc65d4b..7e63042 100644 --- a/custom_components/choreops/dashboards/preferences/user-chores-lite-v1.md +++ b/custom_components/choreops/dashboards/preferences/user-chores-lite-v1.md @@ -2,6 +2,12 @@ `user-chores-lite-v1` is a dynamic lightweight profile intended for older devices and lower-capability frontend environments. It keeps the ChoreOps dashboard-helper sorting and grouping intelligence while using native Home Assistant cards for the visible UI. +## Scale note + +- This template supports both inline and shard-backed chore transport. +- It remains lightweight in the frontend, so it may be a better fit than the richer templates when chore counts grow. +- It is still intended as a compact profile rather than the most feature-rich dashboard, but it can be used beyond the `20-40` inline-only range when the integration switches to shard-backed chore helpers. + ## Quick overview - Dynamic by design: chores and rewards are still generated from the dashboard helper at runtime. diff --git a/custom_components/choreops/dashboards/preferences/user-kidschores-classic-v1.md b/custom_components/choreops/dashboards/preferences/user-kidschores-classic-v1.md index 25d71ce..683ad56 100644 --- a/custom_components/choreops/dashboards/preferences/user-kidschores-classic-v1.md +++ b/custom_components/choreops/dashboards/preferences/user-kidschores-classic-v1.md @@ -2,6 +2,12 @@ `user-kidschores-classic-v1` is the classic kid-focused user layout. It keeps the original multi-card structure with configurable `pref_*` values. +## Scale note + +- This is an inline-only small-household template. +- It is intended for roughly `20-40` chores per user. +- It is not a shard-aware high-density template and should not be used as the scale target for larger chore lists. + - `pref_points_precision` (default: `fixed_0`) - Controls how point values are formatted across the classic dashboard point displays. - Applies to the welcome card, chore point values, reward costs, and showcase totals. diff --git a/custom_components/choreops/dashboards/templates/admin-peruser-v1.yaml b/custom_components/choreops/dashboards/templates/admin-peruser-v1.yaml index f51655d..b0c91a2 100644 --- a/custom_components/choreops/dashboards/templates/admin-peruser-v1.yaml +++ b/custom_components/choreops/dashboards/templates/admin-peruser-v1.yaml @@ -137,7 +137,16 @@ views: {%- set pending_approvals = helper_attrs.pending_approvals | default({}, true) -%} {%- set pending_chore_data = pending_approvals.get('chores', []) | default([], true) -%} {%- set pending_reward_data = pending_approvals.get('rewards', []) | default([], true) -%} - {%- set helper_chore_items = helper_attrs.chores | default([], true) -%} + {%- set helper_chore_items_ns = namespace(items=helper_attrs.chores | default([], true)) -%} + {%- set helper_chore_helper_eids = dashboard_helpers.get('chore_helper_eids', []) if dashboard_helpers is mapping else [] -%} + {%- if helper_chore_helper_eids is iterable and helper_chore_helper_eids is not string -%} + {%- for chore_helper_eid in helper_chore_helper_eids | list -%} + {%- if chore_helper_eid not in ['', None, 'None'] and states(chore_helper_eid) not in ['unknown', 'unavailable'] -%} + {%- set helper_chore_items_ns.items = helper_chore_items_ns.items + (state_attr(chore_helper_eid, 'chores') | default([], true)) -%} + {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- set helper_chore_items = helper_chore_items_ns.items -%} {%- set helper_reward_items = helper_attrs.rewards | default([], true) -%} {%- set chore_count = pending_chore_data | count -%} {%- set reward_count = pending_reward_data | count -%} @@ -729,7 +738,17 @@ views: {%- endfor -%} {%- set has_header_collapsed_override = shared_admin_ui_root.current is mapping and 'header-collapse' in shared_admin_ui_root.current -%} {%- set selected_user_display = selected_user_name if has_selected_user else '' -%} - {%- set selected_helper_chores = state_attr(selected_dashboard_helper, 'chores') | default([], true) if selected_dashboard_helper not in ['', None, 'None'] and states(selected_dashboard_helper) not in ['unknown', 'unavailable'] else [] -%} + {%- set selected_dashboard_helpers = state_attr(selected_dashboard_helper, 'dashboard_helpers') or {} if selected_dashboard_helper not in ['', None, 'None'] and states(selected_dashboard_helper) not in ['unknown', 'unavailable'] else {} -%} + {%- set selected_helper_chores_ns = namespace(items=state_attr(selected_dashboard_helper, 'chores') | default([], true) if selected_dashboard_helper not in ['', None, 'None'] and states(selected_dashboard_helper) not in ['unknown', 'unavailable'] else []) -%} + {%- set selected_chore_helper_eids = selected_dashboard_helpers.get('chore_helper_eids', []) if selected_dashboard_helpers is mapping else [] -%} + {%- if selected_chore_helper_eids is iterable and selected_chore_helper_eids is not string -%} + {%- for chore_helper_eid in selected_chore_helper_eids | list -%} + {%- if chore_helper_eid not in ['', None, 'None'] and states(chore_helper_eid) not in ['unknown', 'unavailable'] -%} + {%- set selected_helper_chores_ns.items = selected_helper_chores_ns.items + (state_attr(chore_helper_eid, 'chores') | default([], true)) -%} + {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- set selected_helper_chores = selected_helper_chores_ns.items -%} {%- set overdue_chore_count = selected_helper_chores | selectattr('state', 'eq', 'overdue') | list | count -%} {%- set show_reset_all_overdue_card = has_selected_user and overdue_chore_count > 0 and integration_entry_id not in ['', None, 'None'] -%} {%- set header_collapsed = shared_admin_ui_root.current['header-collapse'] if has_header_collapsed_override else pref_selector_default_header_collapsed -%} @@ -1017,7 +1036,16 @@ views: {%- set has_chore_selector = chore_select_entity not in ['', None, 'None'] -%} {%- set has_selected_chore = has_selected_dashboard_helper and has_chore_selector and selected_chore_name not in ['None', 'none', '', 'unknown', 'unavailable'] -%} {%- set selected_chore_display = selected_chore_name if has_selected_chore else '' -%} - {%- set helper_chore_items = state_attr(selected_dashboard_helper, 'chores') | default([], true) if has_selected_dashboard_helper else [] -%} + {%- set helper_chore_items_ns = namespace(items=state_attr(selected_dashboard_helper, 'chores') | default([], true) if has_selected_dashboard_helper else []) -%} + {%- set selected_chore_helper_eids = selected_dashboard_helpers.get('chore_helper_eids', []) if has_selected_dashboard_helper and selected_dashboard_helpers is mapping else [] -%} + {%- if selected_chore_helper_eids is iterable and selected_chore_helper_eids is not string -%} + {%- for chore_helper_eid in selected_chore_helper_eids | list -%} + {%- if chore_helper_eid not in ['', None, 'None'] and states(chore_helper_eid) not in ['unknown', 'unavailable'] -%} + {%- set helper_chore_items_ns.items = helper_chore_items_ns.items + (state_attr(chore_helper_eid, 'chores') | default([], true)) -%} + {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- set helper_chore_items = helper_chore_items_ns.items -%} {%- set overdue_chore_items = helper_chore_items | selectattr('state', 'eq', 'overdue') | list if has_selected_dashboard_helper else [] -%} {%- set overdue_chore_count = overdue_chore_items | count -%} {%- set selected_chore_lookup_name = selected_chore_name | string | trim if has_selected_chore else '' -%} diff --git a/custom_components/choreops/dashboards/templates/admin-shared-v1.yaml b/custom_components/choreops/dashboards/templates/admin-shared-v1.yaml index b2f130d..44495db 100644 --- a/custom_components/choreops/dashboards/templates/admin-shared-v1.yaml +++ b/custom_components/choreops/dashboards/templates/admin-shared-v1.yaml @@ -121,7 +121,16 @@ views: {%- set pending_approvals = helper_attrs.pending_approvals | default({}, true) -%} {%- set pending_chore_data = pending_approvals.get('chores', []) | default([], true) -%} {%- set pending_reward_data = pending_approvals.get('rewards', []) | default([], true) -%} - {%- set helper_chore_items = helper_attrs.chores | default([], true) -%} + {%- set helper_chore_items_ns = namespace(items=helper_attrs.chores | default([], true)) -%} + {%- set helper_chore_helper_eids = dashboard_helpers.get('chore_helper_eids', []) if dashboard_helpers is mapping else [] -%} + {%- if helper_chore_helper_eids is iterable and helper_chore_helper_eids is not string -%} + {%- for chore_helper_eid in helper_chore_helper_eids | list -%} + {%- if chore_helper_eid not in ['', None, 'None'] and states(chore_helper_eid) not in ['unknown', 'unavailable'] -%} + {%- set helper_chore_items_ns.items = helper_chore_items_ns.items + (state_attr(chore_helper_eid, 'chores') | default([], true)) -%} + {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- set helper_chore_items = helper_chore_items_ns.items -%} {%- set helper_reward_items = helper_attrs.rewards | default([], true) -%} {%- set chore_count = pending_chore_data | count -%} {%- set reward_count = pending_reward_data | count -%} @@ -703,7 +712,17 @@ views: {%- endfor -%} {%- set has_header_collapsed_override = shared_admin_ui_root.current is mapping and 'header-collapse' in shared_admin_ui_root.current -%} {%- set selected_user_display = selected_user_name if has_selected_user else '' -%} - {%- set selected_helper_chores = state_attr(selected_dashboard_helper, 'chores') | default([], true) if selected_dashboard_helper not in ['', None, 'None'] and states(selected_dashboard_helper) not in ['unknown', 'unavailable'] else [] -%} + {%- set selected_dashboard_helpers = state_attr(selected_dashboard_helper, 'dashboard_helpers') or {} if selected_dashboard_helper not in ['', None, 'None'] and states(selected_dashboard_helper) not in ['unknown', 'unavailable'] else {} -%} + {%- set selected_helper_chores_ns = namespace(items=state_attr(selected_dashboard_helper, 'chores') | default([], true) if selected_dashboard_helper not in ['', None, 'None'] and states(selected_dashboard_helper) not in ['unknown', 'unavailable'] else []) -%} + {%- set selected_chore_helper_eids = selected_dashboard_helpers.get('chore_helper_eids', []) if selected_dashboard_helpers is mapping else [] -%} + {%- if selected_chore_helper_eids is iterable and selected_chore_helper_eids is not string -%} + {%- for chore_helper_eid in selected_chore_helper_eids | list -%} + {%- if chore_helper_eid not in ['', None, 'None'] and states(chore_helper_eid) not in ['unknown', 'unavailable'] -%} + {%- set selected_helper_chores_ns.items = selected_helper_chores_ns.items + (state_attr(chore_helper_eid, 'chores') | default([], true)) -%} + {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- set selected_helper_chores = selected_helper_chores_ns.items -%} {%- set overdue_chore_count = selected_helper_chores | selectattr('state', 'eq', 'overdue') | list | count -%} {%- set show_reset_all_overdue_card = has_selected_user and overdue_chore_count > 0 and integration_entry_id not in ['', None, 'None'] -%} {%- set ns = namespace(chips=[]) -%} @@ -1085,7 +1104,16 @@ views: {%- set has_chore_selector = chore_select_entity not in ['', None, 'None'] -%} {%- set has_selected_chore = has_selected_dashboard_helper and has_chore_selector and selected_chore_name not in ['None', 'none', '', 'unknown', 'unavailable'] -%} {%- set selected_chore_display = selected_chore_name if has_selected_chore else '' -%} - {%- set helper_chore_items = state_attr(selected_dashboard_helper, 'chores') | default([], true) if has_selected_dashboard_helper else [] -%} + {%- set helper_chore_items_ns = namespace(items=state_attr(selected_dashboard_helper, 'chores') | default([], true) if has_selected_dashboard_helper else []) -%} + {%- set selected_chore_helper_eids = selected_dashboard_helpers.get('chore_helper_eids', []) if has_selected_dashboard_helper and selected_dashboard_helpers is mapping else [] -%} + {%- if selected_chore_helper_eids is iterable and selected_chore_helper_eids is not string -%} + {%- for chore_helper_eid in selected_chore_helper_eids | list -%} + {%- if chore_helper_eid not in ['', None, 'None'] and states(chore_helper_eid) not in ['unknown', 'unavailable'] -%} + {%- set helper_chore_items_ns.items = helper_chore_items_ns.items + (state_attr(chore_helper_eid, 'chores') | default([], true)) -%} + {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- set helper_chore_items = helper_chore_items_ns.items -%} {%- set overdue_chore_items = helper_chore_items | selectattr('state', 'eq', 'overdue') | list if has_selected_dashboard_helper else [] -%} {%- set overdue_chore_count = overdue_chore_items | count -%} {%- set selected_chore_lookup_name = selected_chore_name | string | trim if has_selected_chore else '' -%} diff --git a/custom_components/choreops/dashboards/templates/shared/button_card_template_chore_row_kids_v1.yaml b/custom_components/choreops/dashboards/templates/shared/button_card_template_chore_row_kids_v1.yaml index 925f502..4dd0726 100644 --- a/custom_components/choreops/dashboards/templates/shared/button_card_template_chore_row_kids_v1.yaml +++ b/custom_components/choreops/dashboards/templates/shared/button_card_template_chore_row_kids_v1.yaml @@ -184,6 +184,6 @@ chore_row_kids_v1: }, }, }, - "kid_status": '[[[ const labelSource = (typeof variables !== ''undefined'' && variables && typeof variables.ui_labels === ''object'') ? variables.ui_labels : ((typeof variables !== ''undefined'' && variables && typeof variables.state_map === ''object'') ? variables.state_map : null); const i18n = (key, fallback) => { const value = labelSource && typeof labelSource[key] === ''string'' ? labelSource[key].trim() : ''''; return value.length > 0 ? value : ((typeof fallback === ''string'' && fallback.startsWith(''err-'')) ? fallback : `err-${key}`); }; const rawState = entity.state; const state = rawState === ''approved_in_part'' ? ''completed_in_part'' : rawState; const label = i18n(state, `err-${state}`); const dateLabel = i18n(''date'', ((typeof variables !== ''undefined'' && variables && typeof variables.date_label === ''string'' && variables.date_label.trim().length > 0) ? variables.date_label : ''err-date'')); const streak = Number(entity.attributes.chore_current_streak || 0); const dueValue = entity.attributes.due_date || entity.attributes.due_window_start; const formatDateWithOrdinal = (value) => { if (!value) return ''''; const dt = new Date(value); if (Number.isNaN(dt.getTime())) return ''''; const day = dt.getDate(); const mod10 = day % 10; const mod100 = day % 100; let suffix = ''th''; if (mod10 === 1 && mod100 !== 11) suffix = ''st''; else if (mod10 === 2 && mod100 !== 12) suffix = ''nd''; else if (mod10 === 3 && mod100 !== 13) suffix = ''rd''; const month = dt.toLocaleString(undefined, { month: ''short'' }); const time = dt.toLocaleString(undefined, { hour: ''numeric'', minute: ''2-digit'' }); return `${month} ${day}${suffix}, ${time}`; }; const dueText = formatDateWithOrdinal(dueValue); const duePart = dueText ? `${dateLabel}: ${dueText}` : ''''; const streakPart = streak > 0 ? `${streak}` : ''''; const extraLine = (duePart || streakPart) ? `
${duePart}${streakPart}` : ''''; return `${String(label).toUpperCase()}${extraLine}`; ]]]', + "kid_status": '[[[ const getLabels = () => { const directLabels = (typeof variables !== ''undefined'' && variables && typeof variables.ui_labels === ''object'') ? variables.ui_labels : null; if (directLabels) return directLabels; const sensorId = (typeof variables !== ''undefined'' && variables && typeof variables.translation_sensor_eid === ''string'') ? variables.translation_sensor_eid.trim() : ''''; const sensorState = sensorId && typeof hass !== ''undefined'' && hass && hass.states ? hass.states[sensorId] : null; const sensorLabels = sensorState && sensorState.attributes && typeof sensorState.attributes.ui_translations === ''object'' ? sensorState.attributes.ui_translations : null; if (sensorLabels) return sensorLabels; return (typeof variables !== ''undefined'' && variables && typeof variables.state_map === ''object'') ? variables.state_map : null; }; const labelSource = getLabels(); const i18n = (key, fallback) => { const value = labelSource && typeof labelSource[key] === ''string'' ? labelSource[key].trim() : ''''; return value.length > 0 ? value : ((typeof fallback === ''string'' && fallback.startsWith(''err-'')) ? fallback : `err-${key}`); }; const rawState = entity.state; const state = rawState === ''approved_in_part'' ? ''completed_in_part'' : rawState; const label = i18n(state, `err-${state}`); const dateLabel = i18n(''date'', ((typeof variables !== ''undefined'' && variables && typeof variables.date_label === ''string'' && variables.date_label.trim().length > 0) ? variables.date_label : ''err-date'')); const streak = Number(entity.attributes.chore_current_streak || 0); const dueValue = entity.attributes.due_date || entity.attributes.due_window_start; const formatDateWithOrdinal = (value) => { if (!value) return ''''; const dt = new Date(value); if (Number.isNaN(dt.getTime())) return ''''; const day = dt.getDate(); const mod10 = day % 10; const mod100 = day % 100; let suffix = ''th''; if (mod10 === 1 && mod100 !== 11) suffix = ''st''; else if (mod10 === 2 && mod100 !== 12) suffix = ''nd''; else if (mod10 === 3 && mod100 !== 13) suffix = ''rd''; const month = dt.toLocaleString(undefined, { month: ''short'' }); const time = dt.toLocaleString(undefined, { hour: ''numeric'', minute: ''2-digit'' }); return `${month} ${day}${suffix}, ${time}`; }; const dueText = formatDateWithOrdinal(dueValue); const duePart = dueText ? `${dateLabel}: ${dueText}` : ''''; const streakPart = streak > 0 ? `${streak}` : ''''; const extraLine = (duePart || streakPart) ? `
${duePart}${streakPart}` : ''''; return `${String(label).toUpperCase()}${extraLine}`; ]]]', }, } diff --git a/custom_components/choreops/dashboards/templates/shared/button_card_template_chore_row_v1.yaml b/custom_components/choreops/dashboards/templates/shared/button_card_template_chore_row_v1.yaml index 88db7c7..fdf9b1f 100644 --- a/custom_components/choreops/dashboards/templates/shared/button_card_template_chore_row_v1.yaml +++ b/custom_components/choreops/dashboards/templates/shared/button_card_template_chore_row_v1.yaml @@ -146,9 +146,9 @@ chore_row_v1: }, "custom_fields": { - "due": '[[[ const i18n = (key, fallback) => { const labels = (typeof variables !== ''undefined'' && variables && typeof variables.ui_labels === ''object'') ? variables.ui_labels : null; const value = labels && typeof labels[key] === ''string'' ? labels[key].trim() : ''''; return value.length > 0 ? value : ((typeof fallback === ''string'' && fallback.startsWith(''err-'')) ? fallback : `err-${key}`); }; const statusMap = { pending: i18n(''pending'', ''err-pending''), due: i18n(''due'', ''err-due''), claimed: i18n(''claimed'', ''err-claimed''), claimed_in_part: i18n(''claimed_in_part'', ''err-claimed_in_part''), completed: i18n(''completed'', ''err-completed''), completed_in_part: i18n(''completed_in_part'', ''err-completed_in_part''), overdue: i18n(''overdue'', ''err-overdue''), waiting: i18n(''waiting'', ''err-waiting''), missed: i18n(''missed'', ''err-missed''), completed_by_other: i18n(''completed_by_other'', ''err-completed_by_other''), not_my_turn: i18n(''not_my_turn'', ''err-not_my_turn''), already_approved: i18n(''completed'', ''err-completed''), pending_claim: i18n(''claimed'', ''err-claimed''), missed_locked: i18n(''missed_locked'', ''err-missed_locked''), blocked_completed_by_other: i18n(''completed_by_other'', ''err-completed_by_other''), blocked_already_approved: i18n(''completed'', ''err-completed''), blocked_pending_claim: i18n(''claimed'', ''err-claimed''), blocked_waiting_window: i18n(''waiting'', ''err-waiting''), blocked_not_my_turn: i18n(''not_my_turn'', ''err-not_my_turn''), blocked_missed_locked: i18n(''missed_locked'', ''err-missed_locked''), steal_available: i18n(''steal_available'', ''err-steal_available'') }; const rawState = entity.state; let status = (rawState === ''approved'' || rawState === ''already_approved'') ? ''completed'' : (rawState === ''approved_in_part'' ? ''completed_in_part'' : rawState); const claimMode = entity.attributes.claim_mode || null; if (claimMode === ''steal_available'') status = ''steal_available''; else if (entity.attributes.can_claim === false && claimMode && ![''completed'', ''completed_in_part''].includes(status)) status = claimMode; const formatDateWithOrdinal = (value) => { if (!value) return ''--''; const dt = new Date(value); if (Number.isNaN(dt.getTime())) return ''--''; const day = dt.getDate(); const mod10 = day % 10; const mod100 = day % 100; let suffix = ''th''; if (mod10 === 1 && mod100 !== 11) suffix = ''st''; else if (mod10 === 2 && mod100 !== 12) suffix = ''nd''; else if (mod10 === 3 && mod100 !== 13) suffix = ''rd''; const month = dt.toLocaleString(undefined, { month: ''short'' }); const time = dt.toLocaleString(undefined, { hour: ''numeric'', minute: ''2-digit'' }); return `${month} ${day}${suffix}, ${time}`; }; const normalizeText = (value) => { if (typeof value !== ''string'') return ''''; const text = value.trim(); return text.length > 0 ? text : ''''; }; const formatFreqText = (value) => value.split('' '').filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('' ''); const value = entity.attributes.due_date || entity.attributes.due_window_start; const dateText = formatDateWithOrdinal(value); const statusLabel = statusMap[status] || status; const dueLabel = i18n(''date'', ''err-date''); const freqRaw = normalizeText(entity.attributes.recurring_frequency) || normalizeText(entity.attributes.frequency) || normalizeText(entity.attributes.recurrence) || normalizeText(entity.attributes.recurrence_text); const freqKey = freqRaw.toLowerCase().replace(/ /g, ''_''); const freqLocalizedMap = { daily: i18n(''daily'', ''err-daily''), daily_multi: i18n(''daily_multi'', ''err-daily_multi''), weekly: i18n(''weekly'', ''err-weekly''), biweekly: i18n(''biweekly'', ''err-biweekly''), monthly: i18n(''monthly'', ''err-monthly''), quarterly: i18n(''quarterly'', ''err-quarterly''), yearly: i18n(''yearly'', ''err-yearly''), custom: i18n(''custom'', ''err-custom''), custom_from_complete: i18n(''custom_from_complete'', ''err-custom_from_complete''), custom_1_week: i18n(''custom_1_week'', ''err-custom_1_week''), custom_1_month: i18n(''custom_1_month'', ''err-custom_1_month''), custom_1_quarter: i18n(''custom_1_quarter'', ''err-custom_1_quarter''), custom_1_year: i18n(''custom_1_year'', ''err-custom_1_year'') }; const isKnownNone = [''none'', ''unknown'', ''unavailable'', ''null''].includes(freqKey); const hasFreq = freqKey.length > 0 && !isKnownNone; const freqText = freqLocalizedMap[freqKey] || formatFreqText(freqRaw.toLowerCase().replace(/_/g, '' '')); const rText = hasFreq ? ` • ${freqText}` : ''''; return `${String(statusLabel).toUpperCase()} • ${dueLabel}: ${dateText}${rText}`; ]]]', + "due": '[[[ const getLabels = () => { const directLabels = (typeof variables !== ''undefined'' && variables && typeof variables.ui_labels === ''object'') ? variables.ui_labels : null; if (directLabels) return directLabels; const sensorId = (typeof variables !== ''undefined'' && variables && typeof variables.translation_sensor_eid === ''string'') ? variables.translation_sensor_eid.trim() : ''''; const sensorState = sensorId && typeof hass !== ''undefined'' && hass && hass.states ? hass.states[sensorId] : null; const sensorLabels = sensorState && sensorState.attributes && typeof sensorState.attributes.ui_translations === ''object'' ? sensorState.attributes.ui_translations : null; return sensorLabels; }; const i18n = (key, fallback) => { const labels = getLabels(); const value = labels && typeof labels[key] === ''string'' ? labels[key].trim() : ''''; return value.length > 0 ? value : ((typeof fallback === ''string'' && fallback.startsWith(''err-'')) ? fallback : `err-${key}`); }; const statusMap = { pending: i18n(''pending'', ''err-pending''), due: i18n(''due'', ''err-due''), claimed: i18n(''claimed'', ''err-claimed''), claimed_in_part: i18n(''claimed_in_part'', ''err-claimed_in_part''), completed: i18n(''completed'', ''err-completed''), completed_in_part: i18n(''completed_in_part'', ''err-completed_in_part''), overdue: i18n(''overdue'', ''err-overdue''), waiting: i18n(''waiting'', ''err-waiting''), missed: i18n(''missed'', ''err-missed''), completed_by_other: i18n(''completed_by_other'', ''err-completed_by_other''), not_my_turn: i18n(''not_my_turn'', ''err-not_my_turn''), already_approved: i18n(''completed'', ''err-completed''), pending_claim: i18n(''claimed'', ''err-claimed''), missed_locked: i18n(''missed_locked'', ''err-missed_locked''), blocked_completed_by_other: i18n(''completed_by_other'', ''err-completed_by_other''), blocked_already_approved: i18n(''completed'', ''err-completed''), blocked_pending_claim: i18n(''claimed'', ''err-claimed''), blocked_waiting_window: i18n(''waiting'', ''err-waiting''), blocked_not_my_turn: i18n(''not_my_turn'', ''err-not_my_turn''), blocked_missed_locked: i18n(''missed_locked'', ''err-missed_locked''), steal_available: i18n(''steal_available'', ''err-steal_available'') }; const rawState = entity.state; let status = (rawState === ''approved'' || rawState === ''already_approved'') ? ''completed'' : (rawState === ''approved_in_part'' ? ''completed_in_part'' : rawState); const claimMode = entity.attributes.claim_mode || null; if (claimMode === ''steal_available'') status = ''steal_available''; else if (entity.attributes.can_claim === false && claimMode && ![''completed'', ''completed_in_part''].includes(status)) status = claimMode; const formatDateWithOrdinal = (value) => { if (!value) return ''--''; const dt = new Date(value); if (Number.isNaN(dt.getTime())) return ''--''; const day = dt.getDate(); const mod10 = day % 10; const mod100 = day % 100; let suffix = ''th''; if (mod10 === 1 && mod100 !== 11) suffix = ''st''; else if (mod10 === 2 && mod100 !== 12) suffix = ''nd''; else if (mod10 === 3 && mod100 !== 13) suffix = ''rd''; const month = dt.toLocaleString(undefined, { month: ''short'' }); const time = dt.toLocaleString(undefined, { hour: ''numeric'', minute: ''2-digit'' }); return `${month} ${day}${suffix}, ${time}`; }; const normalizeText = (value) => { if (typeof value !== ''string'') return ''''; const text = value.trim(); return text.length > 0 ? text : ''''; }; const formatFreqText = (value) => value.split('' '').filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('' ''); const value = entity.attributes.due_date || entity.attributes.due_window_start; const dateText = formatDateWithOrdinal(value); const statusLabel = statusMap[status] || status; const dueLabel = i18n(''date'', ''err-date''); const freqRaw = normalizeText(entity.attributes.recurring_frequency) || normalizeText(entity.attributes.frequency) || normalizeText(entity.attributes.recurrence) || normalizeText(entity.attributes.recurrence_text); const freqKey = freqRaw.toLowerCase().replace(/ /g, ''_''); const freqLocalizedMap = { daily: i18n(''daily'', ''err-daily''), daily_multi: i18n(''daily_multi'', ''err-daily_multi''), weekly: i18n(''weekly'', ''err-weekly''), biweekly: i18n(''biweekly'', ''err-biweekly''), monthly: i18n(''monthly'', ''err-monthly''), quarterly: i18n(''quarterly'', ''err-quarterly''), yearly: i18n(''yearly'', ''err-yearly''), custom: i18n(''custom'', ''err-custom''), custom_from_complete: i18n(''custom_from_complete'', ''err-custom_from_complete''), custom_1_week: i18n(''custom_1_week'', ''err-custom_1_week''), custom_1_month: i18n(''custom_1_month'', ''err-custom_1_month''), custom_1_quarter: i18n(''custom_1_quarter'', ''err-custom_1_quarter''), custom_1_year: i18n(''custom_1_year'', ''err-custom_1_year'') }; const isKnownNone = [''none'', ''unknown'', ''unavailable'', ''null''].includes(freqKey); const hasFreq = freqKey.length > 0 && !isKnownNone; const freqText = freqLocalizedMap[freqKey] || formatFreqText(freqRaw.toLowerCase().replace(/_/g, '' '')); const rText = hasFreq ? ` • ${freqText}` : ''''; return `${String(statusLabel).toUpperCase()} • ${dueLabel}: ${dateText}${rText}`; ]]]', "description": "[[[ const prefEnabled = (typeof variables !== 'undefined' && variables && variables.pref_show_chore_description === false) ? false : true; const value = typeof entity.attributes.description === 'string' ? entity.attributes.description.trim() : ''; if (!prefEnabled || value.length === 0) return ''; const escaped = value.replace(/&/g, '&').replace(//g, '>'); return `${escaped}`; ]]]", - "history": '[[[ const i18n = (key, fallback) => { const labels = (typeof variables !== ''undefined'' && variables && typeof variables.ui_labels === ''object'') ? variables.ui_labels : null; const value = labels && typeof labels[key] === ''string'' ? labels[key].trim() : ''''; return value.length > 0 ? value : ((typeof fallback === ''string'' && fallback.startsWith(''err-'')) ? fallback : `err-${key}`); }; const map = { completed_by_other: i18n(''completed_by_other'', ''err-completed_by_other''), not_my_turn: i18n(''not_my_turn'', ''err-not_my_turn''), already_approved: i18n(''completed'', ''err-completed''), pending_claim: i18n(''claimed'', ''err-claimed''), waiting: i18n(''waiting'', ''err-waiting''), missed_locked: i18n(''missed'', ''err-missed''), blocked_completed_by_other: i18n(''completed_by_other'', ''err-completed_by_other''), blocked_already_approved: i18n(''completed'', ''err-completed''), blocked_pending_claim: i18n(''claimed'', ''err-claimed''), blocked_waiting_window: i18n(''waiting'', ''err-waiting''), blocked_not_my_turn: i18n(''not_my_turn'', ''err-not_my_turn''), blocked_missed_locked: i18n(''missed'', ''err-missed'') }; const toNameList = (raw) => { if (Array.isArray(raw)) { return [...new Set(raw.map((item) => String(item == null ? '''' : item)).map((item) => item.trim()).filter(Boolean))]; } return []; }; const formatPoints = (rawValue) => { const numericValue = Number(rawValue); if (!Number.isFinite(numericValue)) return null; const roundedValue = Math.round(numericValue * 100) / 100; const precision = (typeof variables !== ''undefined'' && variables && typeof variables.pref_points_precision === ''string'') ? variables.pref_points_precision.trim().toLowerCase() : ''fixed_0''; if (precision === ''adaptive'') { if (Math.abs(roundedValue - Math.round(roundedValue)) < 0.001) return String(Math.round(roundedValue)); if (Math.abs(roundedValue - Math.round(roundedValue * 10) / 10) < 0.001) return roundedValue.toFixed(1); return roundedValue.toFixed(2); } if (precision === ''fixed_1'') return roundedValue.toFixed(1); if (precision === ''fixed_2'') return roundedValue.toFixed(2); return String(Math.round(roundedValue)); }; const rawState = entity.state; const state = rawState === ''approved_in_part'' ? ''completed_in_part'' : rawState; const rawGlobalState = entity.attributes.global_state; const globalState = typeof rawGlobalState === ''string'' ? rawGlobalState : state; const claimMode = entity.attributes.claim_mode || null; const claimAccent = (typeof variables !== ''undefined'' && variables && typeof variables.pref_claim_accent === ''string'' && variables.pref_claim_accent.trim().length > 0) ? variables.pref_claim_accent.trim() : ''#a957fa''; const overdueAccent = (typeof variables !== ''undefined'' && variables && typeof variables.pref_overdue_accent === ''string'' && variables.pref_overdue_accent.trim().length > 0) ? variables.pref_overdue_accent.trim() : ''#ff4444''; const stealAccent = (typeof variables !== ''undefined'' && variables && typeof variables.pref_steal_accent === ''string'' && variables.pref_steal_accent.trim().length > 0) ? variables.pref_steal_accent.trim() : ''#F2C94C''; const type = entity.attributes.completion_criteria || ''''; const assigned = toNameList(entity.attributes.assigned_user_names); const completed = toNameList(entity.attributes.completed_by); const claimedRaw = entity.attributes.claimed_by; const claimed = Array.isArray(claimedRaw) ? [...new Set(claimedRaw.map((item) => String(item == null ? '''' : item)).map((item) => item.trim()).filter(Boolean))] : (claimedRaw ? [String(claimedRaw).trim()].filter(Boolean) : []); const progressedBy = [...new Set([...claimed, ...completed])]; const progressedByAssigned = assigned.filter((name) => progressedBy.includes(name)); const missingByAssigned = assigned.filter((name) => !progressedBy.includes(name)); const isRotationType = type.includes(''rotation''); const isSharedAll = type === ''shared_all''; const isSingleCompletion = type === ''shared_first'' || isRotationType; const isSharedType = isSharedAll || isSingleCompletion; const isGlobalFullyCompleted = [''completed'', ''already_approved'', ''completed_by_other''].includes(globalState); const missedLabel = i18n(''missed'', ''err-missed''); const stealLabel = i18n(''steal_window_open'', ''err-steal_window_open''); const currentTurnLabel = i18n(''current_turn'', ''err-current_turn''); const currentlyLabel = i18n(''currently'', ''err-currently''); const availableInLabel = i18n(''available_in'', ''err-available_in''); const overdueInLabel = i18n(''overdue_in'', ''err-overdue_in''); const progressLabel = i18n(''progress'', ''err-progress''); const completedLabel = i18n(''completed'', ''err-completed''); const byLabel = i18n(''by'', ''err-by''); const lastLabel = i18n(''last'', ''err-last''); let context = ''''; if (claimMode === ''steal_available'') { const turnName = entity.attributes.turn_user_name; context = turnName ? `${stealLabel} • ${currentTurnLabel}: ${turnName}` : `${stealLabel}`; } else if (isSharedType && globalState === ''overdue'') { if (isRotationType) { const turnName = entity.attributes.turn_user_name; context = turnName ? `${missedLabel}: ${turnName}` : `${missedLabel}`; } else { context = missingByAssigned.length > 0 ? `${missedLabel}: ${missingByAssigned.join('', '')}` : `${missedLabel}`; } } else if (state === ''not_my_turn'' && entity.attributes.turn_user_name) { context = `${currentlyLabel}: ${entity.attributes.turn_user_name}`; } else if (isSharedAll) { const totalCount = assigned.length > 0 ? assigned.length : 1; const progressCount = progressedByAssigned.length; const hasProgressSignal = progressCount > 0 || [''claimed'', ''claimed_in_part'', ''approved_in_part'', ''completed'', ''completed_in_part'', ''already_approved'', ''completed_by_other''].includes(globalState); if (hasProgressSignal) { const label = isGlobalFullyCompleted ? completedLabel : progressLabel; if (progressedByAssigned.length > 0) { context = isGlobalFullyCompleted ? `${label}: ${progressCount}/${totalCount} • ${progressedByAssigned.join('', '')}` : `${label}: ${progressCount}/${totalCount} • ${byLabel} ${progressedByAssigned.join('', '')}`; } else { context = `${label}: ${progressCount}/${totalCount}`; } } } else if (isSingleCompletion) { const actorName = progressedByAssigned[0] || progressedBy[0] || ''''; if (actorName) { context = `${completedLabel}: ${actorName}`; } } else if (state === ''waiting'') { const waitText = entity.attributes.time_until_due || ''--''; context = `${availableInLabel}: ${waitText}`; } else if (state === ''due'') { const overdueInText = entity.attributes.time_until_overdue || ''--''; context = `${overdueInLabel}: ${overdueInText}`; } else if (entity.attributes.can_claim === false && claimMode) { if (!isSharedType && [''missed_locked'', ''blocked_missed_locked''].includes(claimMode)) { context = ''''; } else { context = map[claimMode] || claimMode; } } const parts = []; if (context) parts.push(context); const rawLastCompleted = entity.attributes.last_completed; const lastCompletedText = typeof rawLastCompleted === ''string'' ? rawLastCompleted.trim().toLowerCase() : ''''; const hasLastCompleted = !!rawLastCompleted && !['''', ''never'', ''unknown'', ''unavailable'', ''none'', ''null''].includes(lastCompletedText); if (hasLastCompleted) { const lastDate = new Date(rawLastCompleted); if (!Number.isNaN(lastDate.getTime())) { const lastText = lastDate.toLocaleDateString(undefined, {month:''short'', day:''numeric''}); parts.push(`${lastLabel}: ${lastText}`); } } const gamificationEnabled = typeof variables !== ''undefined'' && variables && variables.gamification_enabled === true; const formattedPoints = formatPoints(entity.attributes.default_points); const pointsLabel = (typeof variables !== ''undefined'' && variables && typeof variables.points_label === ''string'' && variables.points_label.trim().length > 0) ? variables.points_label.trim() : ''Points''; if (gamificationEnabled && formattedPoints !== null) { parts.push(`${pointsLabel}: ${formattedPoints}`); } return parts.join('' • ''); ]]]', + "history": '[[[ const getLabels = () => { const directLabels = (typeof variables !== ''undefined'' && variables && typeof variables.ui_labels === ''object'') ? variables.ui_labels : null; if (directLabels) return directLabels; const sensorId = (typeof variables !== ''undefined'' && variables && typeof variables.translation_sensor_eid === ''string'') ? variables.translation_sensor_eid.trim() : ''''; const sensorState = sensorId && typeof hass !== ''undefined'' && hass && hass.states ? hass.states[sensorId] : null; const sensorLabels = sensorState && sensorState.attributes && typeof sensorState.attributes.ui_translations === ''object'' ? sensorState.attributes.ui_translations : null; return sensorLabels; }; const i18n = (key, fallback) => { const labels = getLabels(); const value = labels && typeof labels[key] === ''string'' ? labels[key].trim() : ''''; return value.length > 0 ? value : ((typeof fallback === ''string'' && fallback.startsWith(''err-'')) ? fallback : `err-${key}`); }; const map = { completed_by_other: i18n(''completed_by_other'', ''err-completed_by_other''), not_my_turn: i18n(''not_my_turn'', ''err-not_my_turn''), already_approved: i18n(''completed'', ''err-completed''), pending_claim: i18n(''claimed'', ''err-claimed''), waiting: i18n(''waiting'', ''err-waiting''), missed_locked: i18n(''missed'', ''err-missed''), blocked_completed_by_other: i18n(''completed_by_other'', ''err-completed_by_other''), blocked_already_approved: i18n(''completed'', ''err-completed''), blocked_pending_claim: i18n(''claimed'', ''err-claimed''), blocked_waiting_window: i18n(''waiting'', ''err-waiting''), blocked_not_my_turn: i18n(''not_my_turn'', ''err-not_my_turn''), blocked_missed_locked: i18n(''missed'', ''err-missed'') }; const toNameList = (raw) => { if (Array.isArray(raw)) { return [...new Set(raw.map((item) => String(item == null ? '''' : item)).map((item) => item.trim()).filter(Boolean))]; } return []; }; const formatPoints = (rawValue) => { const numericValue = Number(rawValue); if (!Number.isFinite(numericValue)) return null; const roundedValue = Math.round(numericValue * 100) / 100; const precision = (typeof variables !== ''undefined'' && variables && typeof variables.pref_points_precision === ''string'') ? variables.pref_points_precision.trim().toLowerCase() : ''fixed_0''; if (precision === ''adaptive'') { if (Math.abs(roundedValue - Math.round(roundedValue)) < 0.001) return String(Math.round(roundedValue)); if (Math.abs(roundedValue - Math.round(roundedValue * 10) / 10) < 0.001) return roundedValue.toFixed(1); return roundedValue.toFixed(2); } if (precision === ''fixed_1'') return roundedValue.toFixed(1); if (precision === ''fixed_2'') return roundedValue.toFixed(2); return String(Math.round(roundedValue)); }; const rawState = entity.state; const state = rawState === ''approved_in_part'' ? ''completed_in_part'' : rawState; const rawGlobalState = entity.attributes.global_state; const globalState = typeof rawGlobalState === ''string'' ? rawGlobalState : state; const claimMode = entity.attributes.claim_mode || null; const claimAccent = (typeof variables !== ''undefined'' && variables && typeof variables.pref_claim_accent === ''string'' && variables.pref_claim_accent.trim().length > 0) ? variables.pref_claim_accent.trim() : ''#a957fa''; const overdueAccent = (typeof variables !== ''undefined'' && variables && typeof variables.pref_overdue_accent === ''string'' && variables.pref_overdue_accent.trim().length > 0) ? variables.pref_overdue_accent.trim() : ''#ff4444''; const stealAccent = (typeof variables !== ''undefined'' && variables && typeof variables.pref_steal_accent === ''string'' && variables.pref_steal_accent.trim().length > 0) ? variables.pref_steal_accent.trim() : ''#F2C94C''; const type = entity.attributes.completion_criteria || ''''; const assigned = toNameList(entity.attributes.assigned_user_names); const completed = toNameList(entity.attributes.completed_by); const claimedRaw = entity.attributes.claimed_by; const claimed = Array.isArray(claimedRaw) ? [...new Set(claimedRaw.map((item) => String(item == null ? '''' : item)).map((item) => item.trim()).filter(Boolean))] : (claimedRaw ? [String(claimedRaw).trim()].filter(Boolean) : []); const progressedBy = [...new Set([...claimed, ...completed])]; const progressedByAssigned = assigned.filter((name) => progressedBy.includes(name)); const missingByAssigned = assigned.filter((name) => !progressedBy.includes(name)); const isRotationType = type.includes(''rotation''); const isSharedAll = type === ''shared_all''; const isSingleCompletion = type === ''shared_first'' || isRotationType; const isSharedType = isSharedAll || isSingleCompletion; const isGlobalFullyCompleted = [''completed'', ''already_approved'', ''completed_by_other''].includes(globalState); const missedLabel = i18n(''missed'', ''err-missed''); const stealLabel = i18n(''steal_window_open'', ''err-steal_window_open''); const currentTurnLabel = i18n(''current_turn'', ''err-current_turn''); const currentlyLabel = i18n(''currently'', ''err-currently''); const availableInLabel = i18n(''available_in'', ''err-available_in''); const overdueInLabel = i18n(''overdue_in'', ''err-overdue_in''); const progressLabel = i18n(''progress'', ''err-progress''); const completedLabel = i18n(''completed'', ''err-completed''); const byLabel = i18n(''by'', ''err-by''); const lastLabel = i18n(''last'', ''err-last''); let context = ''''; if (claimMode === ''steal_available'') { const turnName = entity.attributes.turn_user_name; context = turnName ? `${stealLabel} • ${currentTurnLabel}: ${turnName}` : `${stealLabel}`; } else if (isSharedType && globalState === ''overdue'') { if (isRotationType) { const turnName = entity.attributes.turn_user_name; context = turnName ? `${missedLabel}: ${turnName}` : `${missedLabel}`; } else { context = missingByAssigned.length > 0 ? `${missedLabel}: ${missingByAssigned.join('', '')}` : `${missedLabel}`; } } else if (state === ''not_my_turn'' && entity.attributes.turn_user_name) { context = `${currentlyLabel}: ${entity.attributes.turn_user_name}`; } else if (isSharedAll) { const totalCount = assigned.length > 0 ? assigned.length : 1; const progressCount = progressedByAssigned.length; const hasProgressSignal = progressCount > 0 || [''claimed'', ''claimed_in_part'', ''approved_in_part'', ''completed'', ''completed_in_part'', ''already_approved'', ''completed_by_other''].includes(globalState); if (hasProgressSignal) { const label = isGlobalFullyCompleted ? completedLabel : progressLabel; if (progressedByAssigned.length > 0) { context = isGlobalFullyCompleted ? `${label}: ${progressCount}/${totalCount} • ${progressedByAssigned.join('', '')}` : `${label}: ${progressCount}/${totalCount} • ${byLabel} ${progressedByAssigned.join('', '')}`; } else { context = `${label}: ${progressCount}/${totalCount}`; } } } else if (isSingleCompletion) { const actorName = progressedByAssigned[0] || progressedBy[0] || ''''; if (actorName) { context = `${completedLabel}: ${actorName}`; } } else if (state === ''waiting'') { const waitText = entity.attributes.time_until_due || ''--''; context = `${availableInLabel}: ${waitText}`; } else if (state === ''due'') { const overdueInText = entity.attributes.time_until_overdue || ''--''; context = `${overdueInLabel}: ${overdueInText}`; } else if (entity.attributes.can_claim === false && claimMode) { if (!isSharedType && [''missed_locked'', ''blocked_missed_locked''].includes(claimMode)) { context = ''''; } else { context = map[claimMode] || claimMode; } } const parts = []; if (context) parts.push(context); const rawLastCompleted = entity.attributes.last_completed; const lastCompletedText = typeof rawLastCompleted === ''string'' ? rawLastCompleted.trim().toLowerCase() : ''''; const hasLastCompleted = !!rawLastCompleted && !['''', ''never'', ''unknown'', ''unavailable'', ''none'', ''null''].includes(lastCompletedText); if (hasLastCompleted) { const lastDate = new Date(rawLastCompleted); if (!Number.isNaN(lastDate.getTime())) { const lastText = lastDate.toLocaleDateString(undefined, {month:''short'', day:''numeric''}); parts.push(`${lastLabel}: ${lastText}`); } } const gamificationEnabled = typeof variables !== ''undefined'' && variables && variables.gamification_enabled === true; const formattedPoints = formatPoints(entity.attributes.default_points); const pointsLabel = (typeof variables !== ''undefined'' && variables && typeof variables.points_label === ''string'' && variables.points_label.trim().length > 0) ? variables.points_label.trim() : ''Points''; if (gamificationEnabled && formattedPoints !== null) { parts.push(`${pointsLabel}: ${formattedPoints}`); } return parts.join('' • ''); ]]]', "points": "[[[ return ''; ]]]", "progress": '[[[ const type = entity.attributes.completion_criteria || ''''; if (type !== ''shared_all'') return ''''; const toNameList = (raw) => { if (Array.isArray(raw)) { return [...new Set(raw.map((item) => String(item == null ? '''' : item)).map((item) => item.trim()).filter(Boolean))]; } return []; }; const assigned = toNameList(entity.attributes.assigned_user_names); const completed = toNameList(entity.attributes.completed_by); const claimedRaw = entity.attributes.claimed_by; const claimed = Array.isArray(claimedRaw) ? [...new Set(claimedRaw.map((item) => String(item == null ? '''' : item)).map((item) => item.trim()).filter(Boolean))] : (claimedRaw ? [String(claimedRaw).trim()].filter(Boolean) : []); const progressedBy = [...new Set([...claimed, ...completed])]; const progressedByAssigned = assigned.filter((name) => progressedBy.includes(name)); const total = assigned.length; if (total <= 0) return ''''; const done = Math.min(progressedByAssigned.length, total); const pct = Math.max(0, Math.min(100, Math.round((done / total) * 100))); const globalState = entity.attributes.global_state || ''''; const claimAccent = (typeof variables !== ''undefined'' && variables && typeof variables.pref_claim_accent === ''string'' && variables.pref_claim_accent.trim().length > 0) ? variables.pref_claim_accent.trim() : ''#a957fa''; const isGlobalFullyCompleted = [''completed'', ''already_approved'', ''completed_by_other''].includes(globalState); const fillColor = [''claimed'', ''claimed_in_part''].includes(globalState) ? claimAccent : (globalState === ''approved_in_part'' ? ''var(--primary-color)'' : (isGlobalFullyCompleted ? ''var(--success-color)'' : ''var(--primary-color)'')); return `
` + `
` + `
` + `
` + `${done}/${total}` + `
`; ]]]', "btn_undo": diff --git a/custom_components/choreops/dashboards/templates/shared/chore_engine/context_v1.yaml b/custom_components/choreops/dashboards/templates/shared/chore_engine/context_v1.yaml index afc7abe..7dee039 100644 --- a/custom_components/choreops/dashboards/templates/shared/chore_engine/context_v1.yaml +++ b/custom_components/choreops/dashboards/templates/shared/chore_engine/context_v1.yaml @@ -115,6 +115,7 @@ today_buttons=[], this_week_buttons=[], other_buttons=[], + label_group_candidates=[], label_button_groups=[], label_order=[], group_cards=[] @@ -124,8 +125,16 @@ {%- set core_sensors = state_attr(dashboard_helper, 'core_sensors') or {} -%} {%- set gamification_enabled = state_attr(dashboard_helper, 'gamification_enabled') | default(false, true) -%} {%- set points_sensor = core_sensors.get('points_eid') -%} - {%- set translation_sensor = (state_attr(dashboard_helper, 'dashboard_helpers') or {}).get('translation_sensor_eid') -%} + {%- set dashboard_helpers = state_attr(dashboard_helper, 'dashboard_helpers') or {} -%} + {%- set translation_sensor = dashboard_helpers.get('translation_sensor_eid') -%} + {%- set chore_helper_eids = dashboard_helpers.get('chore_helper_eids', []) if dashboard_helpers is mapping else [] -%} + {%- if chore_helper_eids is not iterable or chore_helper_eids is string -%} + {%- set chore_helper_eids = [] -%} + {%- else -%} + {%- set chore_helper_eids = chore_helper_eids | list -%} + {%- endif -%} {%- set ui = state_attr(translation_sensor, 'ui_translations') if translation_sensor else {} -%} + {%- set ui_labels = ui if ui is mapping else {} -%} {%- set points_label = (state_attr(points_sensor, 'unit_of_measurement') if points_sensor else None) or 'Points' -%} {%- set points_icon = (state_attr(points_sensor, 'icon') if points_sensor else None) or 'mdi:star-circle' -%} {%- set ui_control = state_attr(dashboard_helper, 'ui_control') | default({}, true) -%} @@ -225,5 +234,16 @@ {%- set chore_row_template_name = 'chore_row_kids_v1' if effective_row_variant == 'kids' else 'chore_row_v1' -%} {%- set integration_entry_id = state_attr(dashboard_helper, 'integration_entry_id') -%} {%- set dashboard_helper_user_id = state_attr(dashboard_helper, 'user_id') -%} - {%- set chore_list = state_attr(dashboard_helper, 'chores') | default([], true) -%} - {%- set chores_by_label_raw = state_attr(dashboard_helper, 'chores_by_label') | default({}, true) -%} + {%- set ns_chore_merge = namespace(rows=state_attr(dashboard_helper, 'chores') | default([], true)) -%} + {%- if chore_helper_eids | length > 0 -%} + {%- set ns_chore_merge.rows = [] -%} + {%- for helper_eid in chore_helper_eids -%} + {%- if helper_eid not in ['', none, 'None'] and states(helper_eid) not in ['unknown', 'unavailable'] -%} + {%- set helper_rows = state_attr(helper_eid, 'chores') | default([], true) -%} + {%- if helper_rows is iterable and helper_rows is not string -%} + {%- set ns_chore_merge.rows = ns_chore_merge.rows + (helper_rows | list) -%} + {%- endif -%} + {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- set chore_list = ns_chore_merge.rows if ns_chore_merge.rows is iterable and ns_chore_merge.rows is not string else [] -%} diff --git a/custom_components/choreops/dashboards/templates/shared/chore_engine/group_render_v1.yaml b/custom_components/choreops/dashboards/templates/shared/chore_engine/group_render_v1.yaml index 89b9e40..6e1f45e 100644 --- a/custom_components/choreops/dashboards/templates/shared/chore_engine/group_render_v1.yaml +++ b/custom_components/choreops/dashboards/templates/shared/chore_engine/group_render_v1.yaml @@ -50,6 +50,7 @@ 'template': chore_row_template_name, 'entity': chore_sensor_id, 'variables': { + 'translation_sensor_eid': translation_sensor, 'pref_show_chore_description': pref_show_chore_description, 'pref_claim_accent': pref_claim_accent, 'pref_due_accent': pref_due_accent, @@ -57,46 +58,7 @@ 'pref_steal_accent': pref_steal_accent, 'gamification_enabled': gamification_enabled, 'points_label': points_label, - 'points_icon': points_icon, - 'ui_labels': { - 'pending': ui.get('pending', 'err-pending'), - 'due': ui.get('due', 'err-due'), - 'claimed': ui.get('claimed', 'err-claimed'), - 'claimed_in_part': ui.get('claimed_in_part', 'err-claimed_in_part'), - 'completed': ui.get('completed', 'err-completed'), - 'completed_in_part': ui.get('completed_in_part', 'err-completed_in_part'), - 'overdue': ui.get('overdue', 'err-overdue'), - 'waiting': ui.get('waiting', 'err-waiting'), - 'missed': ui.get('missed', 'err-missed'), - 'completed_by_other': ui.get('completed_by_other', 'err-completed_by_other'), - 'not_my_turn': ui.get('not_my_turn', 'err-not_my_turn'), - 'missed_locked': ui.get('missed_locked', 'err-missed_locked'), - 'steal_available': ui.get('steal_available', 'err-steal_available'), - 'date': ui.get('date', 'err-date'), - 'due_date': ui.get('due_date', 'err-due_date'), - 'steal_window_open': ui.get('steal_window_open', 'err-steal_window_open'), - 'current_turn': ui.get('current_turn', 'err-current_turn'), - 'currently': ui.get('currently', 'err-currently'), - 'available_in': ui.get('available_in', 'err-available_in'), - 'overdue_in': ui.get('overdue_in', 'err-overdue_in'), - 'progress': ui.get('progress', 'err-progress'), - 'by': ui.get('by', 'err-by'), - 'last': ui.get('last', 'err-last'), - 'never': ui.get('never', 'err-never'), - 'daily': ui.get('daily', 'err-daily'), - 'daily_multi': ui.get('daily_multi', 'err-daily_multi'), - 'weekly': ui.get('weekly', 'err-weekly'), - 'biweekly': ui.get('biweekly', 'err-biweekly'), - 'monthly': ui.get('monthly', 'err-monthly'), - 'quarterly': ui.get('quarterly', 'err-quarterly'), - 'yearly': ui.get('yearly', 'err-yearly'), - 'custom': ui.get('custom', 'err-custom'), - 'custom_from_complete': ui.get('custom_from_complete', 'err-custom_from_complete'), - 'custom_1_week': ui.get('custom_1_week', 'err-custom_1_week'), - 'custom_1_month': ui.get('custom_1_month', 'err-custom_1_month'), - 'custom_1_quarter': ui.get('custom_1_quarter', 'err-custom_1_quarter'), - 'custom_1_year': ui.get('custom_1_year', 'err-custom_1_year') - } + 'points_icon': points_icon } } ] -%} diff --git a/custom_components/choreops/dashboards/templates/shared/chore_engine/prepare_groups_v1.yaml b/custom_components/choreops/dashboards/templates/shared/chore_engine/prepare_groups_v1.yaml index cf0879f..019a95b 100644 --- a/custom_components/choreops/dashboards/templates/shared/chore_engine/prepare_groups_v1.yaml +++ b/custom_components/choreops/dashboards/templates/shared/chore_engine/prepare_groups_v1.yaml @@ -29,7 +29,7 @@ {#-- Step 4: Skip chores with labels when label grouping is enabled (they're processed separately) --#} {%- elif effective_use_label_grouping and (chore_labels | default([], true) | list | length > 0) -%} - {#-- Labels take priority; processed in label grouping section below --#} + {%- set ns.label_group_candidates = ns.label_group_candidates + [{'eid': chore_sensor_id, 'labels': chore_labels}] -%} {#-- Step 5: Handle recurring filters BEFORE primary_group logic (override primary_group assignment) --#} {#-- If a chore is in 'today' group without specific date, it's daily recurring --#} @@ -64,23 +64,36 @@ {%- endif -%} {%- endfor -%} -{#-- Build Label Groups from chores_by_label --#} +{#-- Build Label Groups from merged chore rows collected once per card --#} {%- if effective_use_label_grouping -%} - {#-- Determine label order: use pref_label_display_order if provided, else all labels alphabetically --#} + {%- set ns.discovered_labels = [] -%} + {%- for chore in ns.label_group_candidates -%} + {%- for label in chore.labels | default([], true) -%} + {%- if label not in ns.discovered_labels and label not in pref_exclude_label_list -%} + {%- set ns.discovered_labels = ns.discovered_labels + [label] -%} + {%- endif -%} + {%- endfor -%} + {%- endfor -%} + + {#-- Determine label order: use pref_label_display_order first, then append remaining labels alphabetically --#} {%- if pref_label_display_order | length > 0 -%} - {%- set ns.label_order = pref_label_display_order -%} + {%- set ns.label_order = pref_label_display_order | reject('in', pref_exclude_label_list) | list -%} + {%- for label in ns.discovered_labels | sort -%} + {%- if label not in ns.label_order -%} + {%- set ns.label_order = ns.label_order + [label] -%} + {%- endif -%} + {%- endfor -%} {%- else -%} - {%- set ns.label_order = chores_by_label_raw.keys() | list | sort -%} + {%- set ns.label_order = ns.discovered_labels | sort -%} {%- endif -%} {#-- Build label button groups in order --#} {%- for label in ns.label_order -%} - {%- if label not in pref_exclude_label_list and label in chores_by_label_raw -%} - {%- set label_eids = chores_by_label_raw[label] | default([], true) -%} + {%- if label not in pref_exclude_label_list -%} {%- set ns.temp_label_buttons = [] -%} - {%- for eid in label_eids -%} - {%- if eid not in (ns.overdue_buttons | list) -%} - {%- set ns.temp_label_buttons = ns.temp_label_buttons + [eid] -%} + {%- for chore in ns.label_group_candidates -%} + {%- if label in (chore.labels | default([], true)) and chore.eid not in ns.temp_label_buttons -%} + {%- set ns.temp_label_buttons = ns.temp_label_buttons + [chore.eid] -%} {%- endif -%} {%- endfor -%} {%- if ns.temp_label_buttons | length > 0 -%} @@ -88,25 +101,6 @@ {%- endif -%} {%- endif -%} {%- endfor -%} - - {#-- Add any remaining labels not in pref_label_display_order (alphabetically sorted) --#} - {%- if pref_label_display_order | length > 0 -%} - {%- set remaining_labels = chores_by_label_raw.keys() | list | sort | reject('in', pref_label_display_order) | list -%} - {%- for label in remaining_labels -%} - {%- if label not in pref_exclude_label_list -%} - {%- set label_eids = chores_by_label_raw[label] | default([], true) -%} - {%- set ns.temp_label_buttons = [] -%} - {%- for eid in label_eids -%} - {%- if eid not in (ns.overdue_buttons | list) -%} - {%- set ns.temp_label_buttons = ns.temp_label_buttons + [eid] -%} - {%- endif -%} - {%- endfor -%} - {%- if ns.temp_label_buttons | length > 0 -%} - {%- set ns.label_button_groups = ns.label_button_groups + [{'id': none, 'name': label, 'buttons': ns.temp_label_buttons, 'icon': 'mdi:label'}] -%} - {%- endif -%} - {%- endif -%} - {%- endfor -%} - {%- endif -%} {%- endif -%} {#-- Build the button groups --#} diff --git a/custom_components/choreops/dashboards/templates/user-chores-essential-v1.yaml b/custom_components/choreops/dashboards/templates/user-chores-essential-v1.yaml index 12f1707..f492b43 100644 --- a/custom_components/choreops/dashboards/templates/user-chores-essential-v1.yaml +++ b/custom_components/choreops/dashboards/templates/user-chores-essential-v1.yaml @@ -280,6 +280,7 @@ views: today_buttons=[], this_week_buttons=[], other_buttons=[], + label_group_candidates=[], label_button_groups=[], label_order=[], group_cards=[] @@ -288,12 +289,12 @@ views: {#-- 4. Collect Data --#} {%- set core_sensors = state_attr(dashboard_helper, 'core_sensors') or {} -%} {%- set points_sensor = core_sensors.get('points_eid') -%} - {%- set translation_sensor = (state_attr(dashboard_helper, 'dashboard_helpers') or {}).get('translation_sensor_eid') -%} + {%- set dashboard_helpers = state_attr(dashboard_helper, 'dashboard_helpers') or {} -%} + {%- set translation_sensor = dashboard_helpers.get('translation_sensor_eid') if dashboard_helpers is mapping else none -%} {%- set ui = state_attr(translation_sensor, 'ui_translations') if translation_sensor else {} -%} {%- set points_label = (state_attr(points_sensor, 'unit_of_measurement') if points_sensor else None) or 'Points' -%} {%- set points_icon = (state_attr(points_sensor, 'icon') if points_sensor else None) or 'mdi:star-circle' -%} {%- set chore_list = state_attr(dashboard_helper, 'chores') | default([], true) -%} - {%- set chores_by_label_raw = state_attr(dashboard_helper, 'chores_by_label') | default({}, true) -%} {#-- 5. Build Display --#} {%- for chore in chore_list -%} @@ -326,7 +327,7 @@ views: {#-- Step 4: Skip chores with labels when label grouping is enabled (they're processed separately) --#} {%- elif pref_use_label_grouping and (chore_labels | default([], true) | list | length > 0) -%} - {#-- Labels take priority; processed in label grouping section below --#} + {%- set ns.label_group_candidates = ns.label_group_candidates + [{'eid': chore_sensor_id, 'labels': chore_labels}] -%} {#-- Step 5: Handle recurring filters BEFORE primary_group logic (override primary_group assignment) --#} {#-- If a chore is in 'today' group without specific date, it's daily recurring --#} @@ -361,23 +362,36 @@ views: {%- endif -%} {%- endfor -%} - {#-- Build Label Groups from chores_by_label --#} + {#-- Build Label Groups from merged chore rows --#} {%- if pref_use_label_grouping -%} + {%- set ns.discovered_labels = [] -%} + {%- for chore in ns.label_group_candidates -%} + {%- for label in chore.labels | default([], true) -%} + {%- if label not in ns.discovered_labels and label not in pref_exclude_label_list -%} + {%- set ns.discovered_labels = ns.discovered_labels + [label] -%} + {%- endif -%} + {%- endfor -%} + {%- endfor -%} + {#-- Determine label order: use pref_label_display_order if provided, else all labels alphabetically --#} {%- if pref_label_display_order | length > 0 -%} - {%- set ns.label_order = pref_label_display_order -%} + {%- set ns.label_order = pref_label_display_order | reject('in', pref_exclude_label_list) | list -%} + {%- for label in ns.discovered_labels | sort -%} + {%- if label not in ns.label_order -%} + {%- set ns.label_order = ns.label_order + [label] -%} + {%- endif -%} + {%- endfor -%} {%- else -%} - {%- set ns.label_order = chores_by_label_raw.keys() | list | sort -%} + {%- set ns.label_order = ns.discovered_labels | sort -%} {%- endif -%} {#-- Build label button groups in order --#} {%- for label in ns.label_order -%} - {%- if label not in pref_exclude_label_list and label in chores_by_label_raw -%} - {%- set label_eids = chores_by_label_raw[label] | default([], true) -%} + {%- if label not in pref_exclude_label_list -%} {%- set ns.temp_label_buttons = [] -%} - {%- for eid in label_eids -%} - {%- if eid not in (ns.overdue_buttons | list) -%} - {%- set ns.temp_label_buttons = ns.temp_label_buttons + [eid] -%} + {%- for chore in ns.label_group_candidates -%} + {%- if label in (chore.labels | default([], true)) and chore.eid not in (ns.overdue_buttons | list) and chore.eid not in ns.temp_label_buttons -%} + {%- set ns.temp_label_buttons = ns.temp_label_buttons + [chore.eid] -%} {%- endif -%} {%- endfor -%} {%- if ns.temp_label_buttons | length > 0 -%} @@ -385,25 +399,6 @@ views: {%- endif -%} {%- endif -%} {%- endfor -%} - - {#-- Add any remaining labels not in pref_label_display_order (alphabetically sorted) --#} - {%- if pref_label_display_order | length > 0 -%} - {%- set remaining_labels = chores_by_label_raw.keys() | list | sort | reject('in', pref_label_display_order) | list -%} - {%- for label in remaining_labels -%} - {%- if label not in pref_exclude_label_list -%} - {%- set label_eids = chores_by_label_raw[label] | default([], true) -%} - {%- set ns.temp_label_buttons = [] -%} - {%- for eid in label_eids -%} - {%- if eid not in (ns.overdue_buttons | list) -%} - {%- set ns.temp_label_buttons = ns.temp_label_buttons + [eid] -%} - {%- endif -%} - {%- endfor -%} - {%- if ns.temp_label_buttons | length > 0 -%} - {%- set ns.label_button_groups = ns.label_button_groups + [{'id': 'none', 'name': label, 'buttons': ns.temp_label_buttons, 'icon': 'mdi:label'}] -%} - {%- endif -%} - {%- endif -%} - {%- endfor -%} - {%- endif -%} {%- endif -%} {#-- Build the button groups --#} diff --git a/custom_components/choreops/dashboards/templates/user-chores-lite-v1.yaml b/custom_components/choreops/dashboards/templates/user-chores-lite-v1.yaml index a52a578..d89e217 100644 --- a/custom_components/choreops/dashboards/templates/user-chores-lite-v1.yaml +++ b/custom_components/choreops/dashboards/templates/user-chores-lite-v1.yaml @@ -229,16 +229,27 @@ views: today_buttons=[], this_week_buttons=[], other_buttons=[], + label_group_candidates=[], label_button_groups=[], label_order=[], visible_count=0 ) -%} {#-- 4. Collect Data --#} - {%- set translation_sensor = (state_attr(dashboard_helper, 'dashboard_helpers') or {}).get('translation_sensor_eid') -%} + {%- set dashboard_helpers = state_attr(dashboard_helper, 'dashboard_helpers') or {} -%} + {%- set translation_sensor = dashboard_helpers.get('translation_sensor_eid') if dashboard_helpers is mapping else none -%} {%- set ui = state_attr(translation_sensor, 'ui_translations') if translation_sensor else {} -%} - {%- set chore_list = state_attr(dashboard_helper, 'chores') | default([], true) -%} - {%- set chores_by_label_raw = state_attr(dashboard_helper, 'chores_by_label') | default({}, true) -%} + {%- set chore_helper_eids = dashboard_helpers.get('chore_helper_eids', []) if dashboard_helpers is mapping else [] -%} + {%- if chore_helper_eids is not iterable or chore_helper_eids is string -%} + {%- set chore_helper_eids = [] -%} + {%- else -%} + {%- set chore_helper_eids = chore_helper_eids | list -%} + {%- endif -%} + {%- set ns_chores = namespace(list=(state_attr(dashboard_helper, 'chores') | default([], true))) -%} + {%- for chore_helper_eid in chore_helper_eids -%} + {%- set ns_chores.list = ns_chores.list + (state_attr(chore_helper_eid, 'chores') | default([], true)) -%} + {%- endfor -%} + {%- set chore_list = ns_chores.list -%} {#-- 5. Build Display --#} {%- for chore in chore_list -%} @@ -258,6 +269,7 @@ views: {%- set ns.overdue_buttons = ns.overdue_buttons + [chore_sensor_id] -%} {%- set ns.visible_count = ns.visible_count + 1 -%} {%- elif pref_use_label_grouping and (chore_labels | default([], true) | list | length > 0) -%} + {%- set ns.label_group_candidates = ns.label_group_candidates + [{'eid': chore_sensor_id, 'labels': chore_labels}] -%} {%- elif chore_primary_group == 'today' and not pref_include_daily_recurring_in_today -%} {%- set ns.other_buttons = ns.other_buttons + [chore_sensor_id] -%} {%- set ns.visible_count = ns.visible_count + 1 -%} @@ -281,18 +293,30 @@ views: {%- endfor -%} {%- if pref_use_label_grouping -%} + {%- set ns.discovered_labels = [] -%} + {%- for chore in ns.label_group_candidates -%} + {%- for label in chore.labels | default([], true) -%} + {%- if label not in ns.discovered_labels and label not in pref_exclude_label_list -%} + {%- set ns.discovered_labels = ns.discovered_labels + [label] -%} + {%- endif -%} + {%- endfor -%} + {%- endfor -%} {%- if pref_label_display_order | length > 0 -%} - {%- set ns.label_order = pref_label_display_order -%} + {%- set ns.label_order = pref_label_display_order | reject('in', pref_exclude_label_list) | list -%} + {%- for label in ns.discovered_labels | sort -%} + {%- if label not in ns.label_order -%} + {%- set ns.label_order = ns.label_order + [label] -%} + {%- endif -%} + {%- endfor -%} {%- else -%} - {%- set ns.label_order = chores_by_label_raw.keys() | list | sort -%} + {%- set ns.label_order = ns.discovered_labels | sort -%} {%- endif -%} {%- for label in ns.label_order -%} - {%- if label not in pref_exclude_label_list and label in chores_by_label_raw -%} - {%- set label_eids = chores_by_label_raw[label] | default([], true) -%} + {%- if label not in pref_exclude_label_list -%} {%- set ns.temp_label_buttons = [] -%} - {%- for eid in label_eids -%} - {%- if eid not in (ns.overdue_buttons | list) -%} - {%- set ns.temp_label_buttons = ns.temp_label_buttons + [eid] -%} + {%- for chore in ns.label_group_candidates -%} + {%- if label in (chore.labels | default([], true)) and chore.eid not in (ns.overdue_buttons | list) and chore.eid not in ns.temp_label_buttons -%} + {%- set ns.temp_label_buttons = ns.temp_label_buttons + [chore.eid] -%} {%- endif -%} {%- endfor -%} {%- if ns.temp_label_buttons | length > 0 -%} diff --git a/custom_components/choreops/dashboards/templates/user-kidschores-classic-v1.yaml b/custom_components/choreops/dashboards/templates/user-kidschores-classic-v1.yaml index d26f39a..54599e5 100644 --- a/custom_components/choreops/dashboards/templates/user-kidschores-classic-v1.yaml +++ b/custom_components/choreops/dashboards/templates/user-kidschores-classic-v1.yaml @@ -212,6 +212,7 @@ views: today_buttons=[], this_week_buttons=[], other_buttons=[], + label_group_candidates=[], label_button_groups=[], label_order=[], group_cards=[] @@ -220,12 +221,12 @@ views: {#-- 4. Collect Data --#} {%- set core_sensors = state_attr(dashboard_helper, 'core_sensors') or {} -%} {%- set points_sensor = core_sensors.get('points_eid') -%} - {%- set translation_sensor = (state_attr(dashboard_helper, 'dashboard_helpers') or {}).get('translation_sensor_eid') -%} + {%- set dashboard_helpers = state_attr(dashboard_helper, 'dashboard_helpers') or {} -%} + {%- set translation_sensor = dashboard_helpers.get('translation_sensor_eid') if dashboard_helpers is mapping else none -%} {%- set ui = state_attr(translation_sensor, 'ui_translations') if translation_sensor else {} -%} {%- set points_label = (state_attr(points_sensor, 'unit_of_measurement') if points_sensor else None) or 'Points' -%} {%- set points_icon = (state_attr(points_sensor, 'icon') if points_sensor else None) or 'mdi:star-circle' -%} {%- set chore_list = state_attr(dashboard_helper, 'chores') | default([], true) -%} - {%- set chores_by_label_raw = state_attr(dashboard_helper, 'chores_by_label') | default({}, true) -%} {#-- 5. Build Display --#} {%- for chore in chore_list -%} @@ -258,7 +259,7 @@ views: {#-- Step 4: Skip chores with labels when label grouping is enabled (they're processed separately) --#} {%- elif pref_use_label_grouping and (chore_labels | default([], true) | list | length > 0) -%} - {#-- Labels take priority; processed in label grouping section below --#} + {%- set ns.label_group_candidates = ns.label_group_candidates + [{'eid': chore_sensor_id, 'labels': chore_labels}] -%} {#-- Step 5: Handle recurring filters BEFORE primary_group logic (override primary_group assignment) --#} {#-- If a chore is in 'today' group without specific date, it's daily recurring --#} @@ -293,23 +294,36 @@ views: {%- endif -%} {%- endfor -%} - {#-- Build Label Groups from chores_by_label --#} + {#-- Build Label Groups from merged chore rows --#} {%- if pref_use_label_grouping -%} + {%- set ns.discovered_labels = [] -%} + {%- for chore in ns.label_group_candidates -%} + {%- for label in chore.labels | default([], true) -%} + {%- if label not in ns.discovered_labels and label not in pref_exclude_label_list -%} + {%- set ns.discovered_labels = ns.discovered_labels + [label] -%} + {%- endif -%} + {%- endfor -%} + {%- endfor -%} + {#-- Determine label order: use pref_label_display_order if provided, else all labels alphabetically --#} {%- if pref_label_display_order | length > 0 -%} - {%- set ns.label_order = pref_label_display_order -%} + {%- set ns.label_order = pref_label_display_order | reject('in', pref_exclude_label_list) | list -%} + {%- for label in ns.discovered_labels | sort -%} + {%- if label not in ns.label_order -%} + {%- set ns.label_order = ns.label_order + [label] -%} + {%- endif -%} + {%- endfor -%} {%- else -%} - {%- set ns.label_order = chores_by_label_raw.keys() | list | sort -%} + {%- set ns.label_order = ns.discovered_labels | sort -%} {%- endif -%} {#-- Build label button groups in order --#} {%- for label in ns.label_order -%} - {%- if label not in pref_exclude_label_list and label in chores_by_label_raw -%} - {%- set label_eids = chores_by_label_raw[label] | default([], true) -%} + {%- if label not in pref_exclude_label_list -%} {%- set ns.temp_label_buttons = [] -%} - {%- for eid in label_eids -%} - {%- if eid not in (ns.overdue_buttons | list) -%} - {%- set ns.temp_label_buttons = ns.temp_label_buttons + [eid] -%} + {%- for chore in ns.label_group_candidates -%} + {%- if label in (chore.labels | default([], true)) and chore.eid not in (ns.overdue_buttons | list) and chore.eid not in ns.temp_label_buttons -%} + {%- set ns.temp_label_buttons = ns.temp_label_buttons + [chore.eid] -%} {%- endif -%} {%- endfor -%} {%- if ns.temp_label_buttons | length > 0 -%} @@ -317,25 +331,6 @@ views: {%- endif -%} {%- endif -%} {%- endfor -%} - - {#-- Add any remaining labels not in pref_label_display_order (alphabetically sorted) --#} - {%- if pref_label_display_order | length > 0 -%} - {%- set remaining_labels = chores_by_label_raw.keys() | list | sort | reject('in', pref_label_display_order) | list -%} - {%- for label in remaining_labels -%} - {%- if label not in pref_exclude_label_list -%} - {%- set label_eids = chores_by_label_raw[label] | default([], true) -%} - {%- set ns.temp_label_buttons = [] -%} - {%- for eid in label_eids -%} - {%- if eid not in (ns.overdue_buttons | list) -%} - {%- set ns.temp_label_buttons = ns.temp_label_buttons + [eid] -%} - {%- endif -%} - {%- endfor -%} - {%- if ns.temp_label_buttons | length > 0 -%} - {%- set ns.label_button_groups = ns.label_button_groups + [{'id': 'none', 'name': label, 'buttons': ns.temp_label_buttons, 'icon': 'mdi:label'}] -%} - {%- endif -%} - {%- endif -%} - {%- endfor -%} - {%- endif -%} {%- endif -%} {#-- Build the button groups --#} diff --git a/custom_components/choreops/dashboards/translations/ca_dashboard.json b/custom_components/choreops/dashboards/translations/ca_dashboard.json index 3dfc81a..9b9a456 100644 --- a/custom_components/choreops/dashboards/translations/ca_dashboard.json +++ b/custom_components/choreops/dashboards/translations/ca_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Va anar endarrerit", "month_end": "Final de mes", "quarter_end": "Final de trimestre", + "quick_chore_editor_chore_name": "Nom de la tasca", + "quick_chore_editor_chore_name_required": "El nom de la tasca és obligatori", + "quick_chore_editor_create": "Crea", + "quick_chore_editor_create_action": "Crea una tasca", + "quick_chore_editor_created": "Tasca creada", + "quick_chore_editor_description": "Descripció", + "quick_chore_editor_edit": "Edita", + "quick_chore_editor_frequency": "Freqüència", + "quick_chore_editor_icon": "Icona", + "quick_chore_editor_missing_helper": "No s'ha resolt cap ajudant del tauler de control del sistema ChoreOps.", + "quick_chore_editor_points": "Punts", + "quick_chore_editor_points_invalid": "Els punts han de ser un nombre superior o igual a 0", + "quick_chore_editor_request_failed": "La sol·licitud de tasca ha fallat", + "quick_chore_editor_update_action": "Tasca d'actualització", + "quick_chore_editor_updated": "Tasca actualitzada", "year_end": "Final d'any", "independent": "Independent", "never_overdue": "Mai endarrerit", diff --git a/custom_components/choreops/dashboards/translations/da_dashboard.json b/custom_components/choreops/dashboards/translations/da_dashboard.json index 60e17cd..33d9f62 100644 --- a/custom_components/choreops/dashboards/translations/da_dashboard.json +++ b/custom_components/choreops/dashboards/translations/da_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Gik for sent", "month_end": "Månedsafslutning", "quarter_end": "Kvartalsafslutning", + "quick_chore_editor_chore_name": "Navn på opgave", + "quick_chore_editor_chore_name_required": "Navn på pligt er påkrævet", + "quick_chore_editor_create": "Skabe", + "quick_chore_editor_create_action": "Opret en pligt", + "quick_chore_editor_created": "Opgave oprettet", + "quick_chore_editor_description": "Beskrivelse", + "quick_chore_editor_edit": "Redigere", + "quick_chore_editor_frequency": "Frekvens", + "quick_chore_editor_icon": "Ikon", + "quick_chore_editor_missing_helper": "Ingen ChoreOps-systemdashboardhjælper løst", + "quick_chore_editor_points": "Point", + "quick_chore_editor_points_invalid": "Point skal være et tal større end eller lig med 0", + "quick_chore_editor_request_failed": "Anmodning om huslige pligter mislykkedes", + "quick_chore_editor_update_action": "Opdater opgave", + "quick_chore_editor_updated": "Opgave opdateret", "year_end": "Årsafslutning", "independent": "Uafhængig", "never_overdue": "Aldrig for sent", diff --git a/custom_components/choreops/dashboards/translations/de_dashboard.json b/custom_components/choreops/dashboards/translations/de_dashboard.json index 4b310b3..d8ea507 100644 --- a/custom_components/choreops/dashboards/translations/de_dashboard.json +++ b/custom_components/choreops/dashboards/translations/de_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Wurde überfällig", "month_end": "Monatsende", "quarter_end": "Quartalsende", + "quick_chore_editor_chore_name": "Aufgabenname", + "quick_chore_editor_chore_name_required": "Die Angabe des Aufgabennamens ist erforderlich.", + "quick_chore_editor_create": "Erstellen", + "quick_chore_editor_create_action": "Aufgabe erstellen", + "quick_chore_editor_created": "Aufgabe erstellt", + "quick_chore_editor_description": "Beschreibung", + "quick_chore_editor_edit": "Bearbeiten", + "quick_chore_editor_frequency": "Frequenz", + "quick_chore_editor_icon": "Symbol", + "quick_chore_editor_missing_helper": "Kein ChoreOps-System-Dashboard-Helfer aufgelöst", + "quick_chore_editor_points": "Punkte", + "quick_chore_editor_points_invalid": "Die Punktzahl muss eine Zahl größer oder gleich 0 sein.", + "quick_chore_editor_request_failed": "Aufgabenanfrage fehlgeschlagen", + "quick_chore_editor_update_action": "Aktualisierungsaufgabe", + "quick_chore_editor_updated": "Aufgabe aktualisiert", "year_end": "Jahresende", "independent": "Unabhängig", "never_overdue": "Nie überfällig", diff --git a/custom_components/choreops/dashboards/translations/en_dashboard.json b/custom_components/choreops/dashboards/translations/en_dashboard.json index a5e479f..33efd83 100644 --- a/custom_components/choreops/dashboards/translations/en_dashboard.json +++ b/custom_components/choreops/dashboards/translations/en_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Went Overdue", "month_end": "Month-End", "quarter_end": "Quarter-End", + "quick_chore_editor_chore_name": "Chore name", + "quick_chore_editor_chore_name_required": "Chore name is required", + "quick_chore_editor_create": "Create", + "quick_chore_editor_create_action": "Create chore", + "quick_chore_editor_created": "Chore created", + "quick_chore_editor_description": "Description", + "quick_chore_editor_edit": "Edit", + "quick_chore_editor_frequency": "Frequency", + "quick_chore_editor_icon": "Icon", + "quick_chore_editor_missing_helper": "No ChoreOps system dashboard helper resolved", + "quick_chore_editor_points": "Points", + "quick_chore_editor_points_invalid": "Points must be a number greater than or equal to 0", + "quick_chore_editor_request_failed": "Chore request failed", + "quick_chore_editor_update_action": "Update chore", + "quick_chore_editor_updated": "Chore updated", "year_end": "Year-End", "independent": "Independent", "never_overdue": "Never overdue", diff --git a/custom_components/choreops/dashboards/translations/es_dashboard.json b/custom_components/choreops/dashboards/translations/es_dashboard.json index a49f395..6d83258 100644 --- a/custom_components/choreops/dashboards/translations/es_dashboard.json +++ b/custom_components/choreops/dashboards/translations/es_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Se atrasó", "month_end": "Fin de mes", "quarter_end": "Fin de trimestre", + "quick_chore_editor_chore_name": "Nombre de la tarea", + "quick_chore_editor_chore_name_required": "Se requiere el nombre de la tarea.", + "quick_chore_editor_create": "Crear", + "quick_chore_editor_create_action": "Crear tarea", + "quick_chore_editor_created": "Tarea creada", + "quick_chore_editor_description": "Descripción", + "quick_chore_editor_edit": "Editar", + "quick_chore_editor_frequency": "Frecuencia", + "quick_chore_editor_icon": "Icono", + "quick_chore_editor_missing_helper": "No se ha resuelto el problema del panel de control del sistema ChoreOps.", + "quick_chore_editor_points": "Puntos", + "quick_chore_editor_points_invalid": "Los puntos deben ser un número mayor o igual a 0.", + "quick_chore_editor_request_failed": "La solicitud de tarea falló.", + "quick_chore_editor_update_action": "Tarea de actualización", + "quick_chore_editor_updated": "Tareas actualizadas", "year_end": "Fin de año", "independent": "Independiente", "never_overdue": "Nunca vencido", diff --git a/custom_components/choreops/dashboards/translations/fi_dashboard.json b/custom_components/choreops/dashboards/translations/fi_dashboard.json index ff47b68..419f099 100644 --- a/custom_components/choreops/dashboards/translations/fi_dashboard.json +++ b/custom_components/choreops/dashboards/translations/fi_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Meni myöhässä", "month_end": "Kuukauden lopussa", "quarter_end": "Neljänneksen loppu", + "quick_chore_editor_chore_name": "Työn nimi", + "quick_chore_editor_chore_name_required": "Työn nimi on pakollinen", + "quick_chore_editor_create": "Luoda", + "quick_chore_editor_create_action": "Luo tehtävä", + "quick_chore_editor_created": "Työtehtävä luotu", + "quick_chore_editor_description": "Kuvaus", + "quick_chore_editor_edit": "Muokata", + "quick_chore_editor_frequency": "Taajuus", + "quick_chore_editor_icon": "Kuvake", + "quick_chore_editor_missing_helper": "ChoreOps-järjestelmän kojelaudan apuohjelmaa ei ole ratkaistu", + "quick_chore_editor_points": "Pisteet", + "quick_chore_editor_points_invalid": "Pisteiden on oltava suurempi tai yhtä suuri kuin 0", + "quick_chore_editor_request_failed": "Työpyyntö epäonnistui", + "quick_chore_editor_update_action": "Päivitä tehtävä", + "quick_chore_editor_updated": "Kotitehtävä päivitetty", "year_end": "Vuoden lopussa", "independent": "Itsenäinen", "never_overdue": "Ei koskaan myöhässä", diff --git a/custom_components/choreops/dashboards/translations/fr_dashboard.json b/custom_components/choreops/dashboards/translations/fr_dashboard.json index 0cb84ef..4741bcb 100644 --- a/custom_components/choreops/dashboards/translations/fr_dashboard.json +++ b/custom_components/choreops/dashboards/translations/fr_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Délais dépassé", "month_end": "Fin de mois", "quarter_end": "Fin de trimestre", + "quick_chore_editor_chore_name": "Nom de la tâche", + "quick_chore_editor_chore_name_required": "Le nom de la tâche est requis", + "quick_chore_editor_create": "Créer", + "quick_chore_editor_create_action": "Créer une tâche", + "quick_chore_editor_created": "Tâche créée", + "quick_chore_editor_description": "Description", + "quick_chore_editor_edit": "Modifier", + "quick_chore_editor_frequency": "Fréquence", + "quick_chore_editor_icon": "Icône", + "quick_chore_editor_missing_helper": "Aucun assistant de tableau de bord du système ChoreOps résolu", + "quick_chore_editor_points": "Points", + "quick_chore_editor_points_invalid": "Le nombre de points doit être supérieur ou égal à 0.", + "quick_chore_editor_request_failed": "La demande de tâche a échoué", + "quick_chore_editor_update_action": "Mise à jour des tâches", + "quick_chore_editor_updated": "Tâche mise à jour", "year_end": "Fin d'année", "independent": "Indépendant", "never_overdue": "Jamais en retard", diff --git a/custom_components/choreops/dashboards/translations/it_dashboard.json b/custom_components/choreops/dashboards/translations/it_dashboard.json new file mode 100644 index 0000000..821241b --- /dev/null +++ b/custom_components/choreops/dashboards/translations/it_dashboard.json @@ -0,0 +1,281 @@ +{ + "achievement": "Risultato", + "achievements": "Risultati conseguiti", + "active": "Attivo", + "all_time": "Di tutti i tempi", + "all_earned_badges": "Tutti i distintivi guadagnati", + "applicable_days": "Giorni applicabili", + "approval_reset_type": "Completamenti consentiti e ripristino", + "at_due_date_multi": "Parti multipli alla data di scadenza", + "at_due_date_once": "Una volta alla data di scadenza", + "at_midnight_multi": "Molteplici a mezzanotte", + "at_midnight_once": "Una volta a mezzanotte", + "upon_completion": "Al completamento", + "shared_all": "Condiviso (Tutti)", + "shared_first": "Condiviso (Primo)", + "completion_criteria": "Tipo di completamento", + "applied": "Applicato", + "approval_center": "Centro di approvazione", + "auto_approve": "Approvazione automatica", + "auto_approve_pending": "Approvazione automatica in sospeso", + "approvals": "Approvazioni", + "approved": "Approvato", + "completed_in_part": "Completato (parzialmente)", + "assigned_to": "Assegnato a", + "award_count": "Conteggio premi", + "award_status": "Stato del premio", + "awards": "Categorie", + "available": "Disponibile", + "badge": "Distintivo", + "bonus": "Bonus", + "bonuses_applied": "Bonus applicati", + "challenge": "Sfida", + "challenges": "Sfide", + "chore_admin_actions": "Azioni amministrative relative alle attività", + "chore_claimed": "Compito richiesto", + "chore": "Lavoretto", + "chore_approvals": "Approvazione delle faccende domestiche", + "chore_detail": "Dettagli della mansione", + "chores": "Lavoretti", + "chores_completed": "Lavori completati", + "chores_details": "Dettagli delle faccende domestiche", + "chores_per_day": "Lavori domestici al giorno", + "biweekly": "Bisettimanale", + "claimed": "Reclamato", + "claimed_in_part": "Richiesto (parziale)", + "claims": "Affermazioni", + "claim_lock": "Blocco di rivendicazione", + "completed_by_other": "Completato da Altri", + "clear_date": "Data chiara", + "clear_applied": "Cancella applicato", + "clear_pending": "In attesa di chiarimenti", + "completed": "Completato", + "configure": "Specificare", + "cost": "Costo", + "criteria": "criteri", + "cumulative": "Cumulativo", + "quests": "Domande", + "ranks": "Classifiche", + "custom": "Costume", + "custom_1_month": "Personalizzato (1 mese)", + "custom_1_quarter": "Personalizzato (1 quarto)", + "custom_1_week": "Personalizzato (1 settimana)", + "custom_1_year": "Personalizzato (1 anno)", + "current_status": "Stato attuale", + "custom_from_complete": "Personalizzato (Riprogrammazione dalla data di completamento)", + "daily": "Quotidiano", + "daily_multi": "Giornalieramente (più volte al giorno)", + "mon_short": "Lun", + "tue_short": "Martedì", + "wed_short": "Mercoledì", + "thu_short": "Gio", + "fri_short": "Ven", + "sat_short": "Sab", + "sun_short": "Sole", + "demoted": "Declassato", + "due": "Dovuto", + "date": "Data", + "waiting": "In attesa", + "available_in": "Disponibile in", + "overdue_in": "In ritardo", + "steal_window_open": "Aprire la finestra di furto", + "current_turn": "turno attuale", + "not_my_turn": "Non è il mio turno", + "already_approved": "Già approvato", + "all_caught_up": "Tutto a posto!", + "pending_claim": "Richiesta in sospeso", + "steal_available": "Rubare", + "missed_locked": "Mancato (Bloccato)", + "completed_by": "Completato da", + "currently": "Attualmente", + "last": "Scorso", + "never": "Mai", + "done": "Fatto", + "by": "di", + "missed": "Mancato", + "due_date": "Scadenza", + "due_window": "finestra di scadenza", + "due_this_morning": "Da consegnare stamattina", + "due_this_week": "Da consegnare questa settimana", + "due_today": "Scadenza oggi", + "earned": "Guadagnato", + "earned_all_time": "Guadagnato in assoluto", + "earned_by": "Guadagnato da", + "earned_count": "Conteggio guadagnato", + "enable_label_grouping": "Abilita gruppi di etichette", + "end_date": "Data di fine", + "false": "Falso", + "future_goal": "Obiettivo futuro", + "global_status": "Stato globale", + "goal": "Obiettivo", + "grace_period": "Periodo di grazia", + "highest_badge_earned": "Distintivo più alto conseguito", + "highest_rank": "Grado più alto", + "hide_overviews": "Meno panoramiche", + "hide_overdue_group": "Nascondi il gruppo in ritardo", + "hide_penalties": "Nascondi le sanzioni", + "hide_this_week_group": "Nascondi il gruppo di questa settimana", + "hide_next_higher_badge": "Nascondi il badge di livello superiore successivo", + "hide_next_lower_badge": "Nascondi il badge successivo in basso", + "hide_blocked": "Nascondi bloccato", + "hold_pending": "Sospensione in corso", + "in_progress": "In corso", + "interval": "Intervallo", + "if_missed": "Se dimenticato", + "last_awarded": "Ultimo premio assegnato", + "last_earned": "Ultimo guadagnato", + "locked": "Chiuso", + "hide_completed": "Più completati", + "longest_streak": "serie più lunga", + "maintenance": "Manutenzione", + "most_completions": "Maggior numero di completamenti", + "multiplier": "Moltiplicatore", + "need": "Bisogno", + "no": "NO", + "monthly": "Mensile", + "kids": "Bambini", + "quarterly": "Trimestrale", + "yearly": "Annuale", + "no_achievements_found": "Nessun obiettivo trovato", + "no_badges_found": "Nessun badge cumulativo trovato", + "cumulative_badge_setup_prompt": "Configura dei badge cumulativi per tenere traccia dei traguardi raggiunti!", + "no_periodic_badges_found": "Nessun distintivo periodico trovato", + "periodic_badge_setup_prompt": "Imposta dei badge periodici per monitorare gli obiettivi giornalieri, settimanali o mensili!", + "no_challenges_found": "Nessuna sfida rilevata", + "no_chore_selected": "Nessuna attività selezionata o stato non disponibile", + "no_date_set": "Nessuna data impostata", + "no_due_date": "Nessuna data di scadenza", + "no_overdue_chores": "Nessun ritardo", + "no_pending_approvals": "Nessuna approvazione in sospeso", + "no_rewards": "Nessun premio disponibile.", + "none": "Nessuno", + "ok": "OK", + "overview": "Panoramica", + "overdue": "In ritardo", + "overdue_handling": "In ritardo", + "overdue_chores": "Lavori in sospeso", + "approver_dashboard_for": "Dashboard per", + "penalties_applied": "Sanzioni applicate", + "penalty": "Pena", + "pending": "In attesa di", + "pending_claim_action": "Richiesta in sospeso", + "pending_approvals": "Approvazioni in sospeso", + "management": "Gestione", + "manage_for": "Gestire per", + "choose_user_for_admin_actions": "Selezionare un utente per la gestione.", + "choose_chore_for_admin_actions": "Seleziona un'attività da gestire per", + "chore_selector_unavailable": "Selettore di attività non disponibile per questo profilo.", + "clear_selection": "Cancella selezione", + "link_to_choreops_integration_configuration": "Collegamento alla configurazione dell'integrazione con ChoreOps.", + "documentation_and_help": "Documentazione e assistenza", + "documentation_and_help_description": "Consulta la wiki per guide di configurazione e avanzate.", + "support_the_project": "Sostieni il progetto", + "support_the_project_description": "Il modo migliore per mostrare questo lavoro è significativo e garantire che l'attenzione rimanga focalizzata sulla prossima versione.", + "select_chore_to_manage": "Seleziona un'attività da gestire", + "select_user_first": "Seleziona prima un utente", + "select_user_to_manage_chores_first": "Seleziona un utente qui sopra per gestire le attività.", + "selected_user": "Utente selezionato", + "viewing_selected_chore": "Gestione delle attività selezionate per", + "viewing_selected_user": "Visualizzazione dell'utente selezionato", + "periodic": "Periodico", + "points_details": "Dettagli sui punti", + "points_per_day": "Punti al giorno", + "per_day_goal": "Obiettivo giornaliero", + "plus_1_day": "+1 giorno", + "plus_1_week": "+1 settimana", + "plus_next_due": "+Prossima scadenza", + "pluses_and_minuses": "Pro e contro", + "progress": "Progressi", + "recurrence": "Ricorrenza", + "related_chore": "Compito correlato", + "requested": "richiesto", + "requirement_met": "Requisito soddisfatto!", + "reset_all": "Reimposta tutto ciò che è stato perso o in ritardo", + "reset_overdue_status": "Reimposta lo stato di scadenza", + "reward": "Ricompensa", + "reward_approvals": "Approvazioni dei premi", + "reward_requested": "Ricompensa richiesta", + "rewards": "Premi", + "rotation_simple": "Rotazione semplice", + "rotation_smart": "Rotazione intelligente", + "rules": "Regole", + "review_chores_and_rewards_waiting_for_approval": "Rivedi le attività e i premi in attesa di approvazione.", + "select_chore_edit": "Seleziona l'attività per modificare la data di scadenza", + "select_new_date": "Seleziona nuova data e ora", + "seven_day_activity_log": "Registro delle attività di 7 giorni", + "showcase": "Vetrina", + "show_blocked": "Mostra bloccato", + "show_completed": "Spettacolo completato", + "show_overviews": "Mostra le panoramiche", + "show_overdue_group": "Mostra il gruppo in ritardo", + "show_penalties": "Mostra le sanzioni", + "show_this_week_group": "Mostra questo gruppo della settimana", + "sort_mode_by_state_and_date": "Accorto", + "sort_mode_date_asc": "Data di scadenza più prossima", + "sort_mode_date_desc": "Ultimo termine", + "sort_mode_default": "Predefinito", + "sort_mode_name_asc": "Nome AZ", + "sort_mode_name_desc": "Nome ZA", + "start_achievement_prompt": "Inizia un obiettivo per monitorare i tuoi progressi!", + "start_challenge_prompt": "Inizia una sfida per superare i tuoi limiti!", + "start_date": "Data di lancio", + "status": "Stato", + "standard": "Standard", + "streak": "Strisciante", + "disable_label_grouping": "Disabilita i gruppi di etichette", + "show_next_higher_badge": "Mostra il badge di livello superiore successivo", + "show_next_lower_badge": "Mostra il badge inferiore successivo", + "system_administration": "Amministrazione di sistema", + "tap_to_change": "(Tocca per modificare / Tieni premuto per impostare)", + "threshold": "Soglia", + "today_group_mode_off": "Oggi: Riposo", + "today_group_mode_today": "Oggi", + "today_group_mode_today_morning": "Oggi +AM", + "this_month": "Questo mese", + "this_week": "Questa settimana", + "times": "Volte", + "to_unlock": "Per sbloccare", + "todays_completed": "Completato oggi", + "total_bonus": "Bonus totale", + "total_chores": "Compiti totali", + "total_completed": "Numero totale di faccende completate", + "total_earned": "Guadagno totale", + "total": "Totale", + "total_penalty": "Penalità totale", + "true": "VERO", + "upcoming_bonus": "Dopo", + "value": "Valore", + "window": "Finestra", + "weekly": "Settimanale", + "weekly_completed": "Completamento settimanale", + "weekly_performance": "Prestazioni settimanali", + "week_end": "Fine settimana", + "welcome": "Benvenuto", + "went_overdue": "È scaduto", + "month_end": "Fine del mese", + "quarter_end": "Fine del trimestre", + "quick_chore_editor_chore_name": "Nome del lavoro", + "quick_chore_editor_chore_name_required": "Il nome dell'attività è obbligatorio", + "quick_chore_editor_create": "Creare", + "quick_chore_editor_create_action": "Creare un compito", + "quick_chore_editor_created": "Compito creato", + "quick_chore_editor_description": "Descrizione", + "quick_chore_editor_edit": "Modificare", + "quick_chore_editor_frequency": "Frequenza", + "quick_chore_editor_icon": "Icona", + "quick_chore_editor_missing_helper": "Nessun helper del dashboard di sistema ChoreOps risolto", + "quick_chore_editor_points": "Punti", + "quick_chore_editor_points_invalid": "I punti devono essere un numero maggiore o uguale a 0", + "quick_chore_editor_request_failed": "Richiesta di lavoro fallita", + "quick_chore_editor_update_action": "Aggiornamento dell'attività", + "quick_chore_editor_updated": "Attività aggiornata", + "year_end": "Fine anno", + "independent": "Indipendente", + "never_overdue": "Mai in ritardo", + "at_due_date": "Alla data di scadenza", + "at_due_date_allow_steal": "Consentire il furto", + "at_due_date_clear_and_mark_missed": "Cancella e segna mancato", + "at_due_date_clear_at_approval_reset": "Cancella al ripristino", + "at_due_date_clear_immediate_on_late": "Sgomberare immediatamente in caso di ritardo", + "at_due_date_mark_missed_and_lock": "Mark ha mancato e bloccato" +} diff --git a/custom_components/choreops/dashboards/translations/nb_dashboard.json b/custom_components/choreops/dashboards/translations/nb_dashboard.json index b743ed8..f5b54d1 100644 --- a/custom_components/choreops/dashboards/translations/nb_dashboard.json +++ b/custom_components/choreops/dashboards/translations/nb_dashboard.json @@ -1,5 +1,5 @@ { - "achievement": "Oppnåelse", + "achievement": "Prestasjon", "achievements": "prestasjoner", "active": "Aktiv", "all_time": "Hele tiden", @@ -21,78 +21,78 @@ "approvals": "Godkjenninger", "approved": "Godkjent", "completed_in_part": "Fullført (delvis)", - "assigned_to": "Tildelt til", + "assigned_to": "Tildelt", "award_count": "Antall priser", "award_status": "Prisstatus", "awards": "Priser", "available": "Tilgjengelig", - "badge": "Skilt", + "badge": "Merke", "bonus": "Bonus", "bonuses_applied": "Bonuser brukt", "challenge": "Utfordring", "challenges": "Utfordringer", - "chore_admin_actions": "Handlinger for husarbeidsadministrator", - "chore_claimed": "Husarbeid gjort krav på", - "chore": "Husarbeid", + "chore_admin_actions": "Handlinger for administrator", + "chore_claimed": "Gjøremål reservert", + "chore": "Gjøremål", "chore_approvals": "Godkjenninger av husarbeid", "chore_detail": "Detaljer om husarbeid", "chores": "Gjøremål", "chores_completed": "Gjøremål fullført", - "chores_details": "Detaljer om husarbeid", + "chores_details": "Detaljer om gjøremål", "chores_per_day": "Gjøremål per dag", "biweekly": "Annenhver uke", - "claimed": "Krevd", - "claimed_in_part": "Krav påtatt (delvis)", - "claims": "Krav", - "claim_lock": "Kravlås", + "claimed": "Reservert", + "claimed_in_part": "Reservert (Delvis)", + "claims": "Reservasjoner", + "claim_lock": "Låst Reservasjon", "completed_by_other": "Fullført av Annen", "clear_date": "Fjern dato", "clear_applied": "Fjern brukt", "clear_pending": "Fjern ventende", "completed": "Fullført", "configure": "Konfigurer", - "cost": "Koste", + "cost": "Koster", "criteria": "Kriterier", "cumulative": "Kumulativ", "quests": "Oppdrag", "ranks": "Ranger", - "custom": "Skikk", + "custom": "Tilpasset", "custom_1_month": "Tilpasset (1 måned)", "custom_1_quarter": "Tilpasset (1 kvartal)", "custom_1_week": "Tilpasset (1 uke)", "custom_1_year": "Tilpasset (1 år)", "current_status": "Nåværende status", - "custom_from_complete": "Tilpasset (Omplanlegg fra ferdigstillelsesdato)", + "custom_from_complete": "Egendefinert (Ny frist fra fullføringsdato)", "daily": "Daglig", "daily_multi": "Daglig (flere ganger per dag)", - "mon_short": "man", - "tue_short": "tirsdag", + "mon_short": "Man", + "tue_short": "Tir", "wed_short": "Ons", - "thu_short": "Torsdag", + "thu_short": "Tor", "fri_short": "Fre", "sat_short": "Lør", - "sun_short": "Sol", + "sun_short": "Søn", "demoted": "Degradert", "due": "Forfaller", "date": "Dato", "waiting": "Venter", - "available_in": "Tilgjengelig i", + "available_in": "Tilgjengelig om", "overdue_in": "Forfalt om", "steal_window_open": "Stjel opp vinduet", - "current_turn": "Nåværende sving", + "current_turn": "Nåværende tur", "not_my_turn": "Ikke min tur", "already_approved": "Allerede godkjent", - "all_caught_up": "Alt er oppdatert!", + "all_caught_up": "Alt er gjort!", "pending_claim": "Ventende krav", - "steal_available": "Stjele", + "steal_available": "Stjel", "missed_locked": "Mistet (Låst)", "completed_by": "Fullført av", "currently": "For tiden", - "last": "Siste", + "last": "Sist", "never": "Aldri", "done": "ferdig", "by": "ved", - "missed": "Gå glipp av", + "missed": "Glemt", "due_date": "Forfallsdato", "due_window": "Forfallsvindu", "due_this_morning": "Forfaller i morgentimene", @@ -157,7 +157,7 @@ "approver_dashboard_for": "Dashbord for", "penalties_applied": "Straffer som ble pålagt", "penalty": "Straff", - "pending": "I påvente av", + "pending": "Venter", "pending_claim_action": "Ventende krav", "pending_approvals": "Venter på godkjenninger", "management": "Ledelse", @@ -190,7 +190,7 @@ "related_chore": "Relatert gjøremål", "requested": "Forespurt", "requirement_met": "Krav oppfylt!", - "reset_all": "Tilbakestill alle tapte og forfalte", + "reset_all": "Nullstill alle glemte og forfalte", "reset_overdue_status": "Tilbakestill forfalt status", "reward": "Belønne", "reward_approvals": "Godkjenninger av belønninger", @@ -250,10 +250,25 @@ "weekly_completed": "Ukentlig fullført", "weekly_performance": "Ukentlig ytelse", "week_end": "Helg", - "welcome": "Velkomst", + "welcome": "Velkommen", "went_overdue": "Gikk for sent", "month_end": "Månedsslutt", "quarter_end": "Kvartslutt", + "quick_chore_editor_chore_name": "Navn på gjøremål", + "quick_chore_editor_chore_name_required": "Navn på gjøremål er obligatorisk", + "quick_chore_editor_create": "Skape", + "quick_chore_editor_create_action": "Opprett gjøremål", + "quick_chore_editor_created": "Gjøremål opprettet", + "quick_chore_editor_description": "Beskrivelse", + "quick_chore_editor_edit": "Redigere", + "quick_chore_editor_frequency": "Hyppighet", + "quick_chore_editor_icon": "Ikon", + "quick_chore_editor_missing_helper": "Ingen ChoreOps-systemdashbordhjelper løst", + "quick_chore_editor_points": "Poeng", + "quick_chore_editor_points_invalid": "Poeng må være et tall større enn eller lik 0", + "quick_chore_editor_request_failed": "Forespørsel om gjøremål mislyktes", + "quick_chore_editor_update_action": "Oppdater gjøremål", + "quick_chore_editor_updated": "Gjøremål oppdatert", "year_end": "Årsslutt", "independent": "Selvstendig", "never_overdue": "Aldri for sent", diff --git a/custom_components/choreops/dashboards/translations/nl_dashboard.json b/custom_components/choreops/dashboards/translations/nl_dashboard.json index 2160438..bc44039 100644 --- a/custom_components/choreops/dashboards/translations/nl_dashboard.json +++ b/custom_components/choreops/dashboards/translations/nl_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Te laat", "month_end": "Einde van de maand", "quarter_end": "einde kwartaal", + "quick_chore_editor_chore_name": "Naam van de klus", + "quick_chore_editor_chore_name_required": "De naam van de taak moet worden vermeld.", + "quick_chore_editor_create": "Creëren", + "quick_chore_editor_create_action": "Maak een klus", + "quick_chore_editor_created": "Taak gecreëerd", + "quick_chore_editor_description": "Beschrijving", + "quick_chore_editor_edit": "Bewerking", + "quick_chore_editor_frequency": "Frequentie", + "quick_chore_editor_icon": "Icon", + "quick_chore_editor_missing_helper": "Geen ChoreOps-systeemdashboardhelper opgelost", + "quick_chore_editor_points": "Punten", + "quick_chore_editor_points_invalid": "Het aantal punten moet een getal zijn dat groter is dan of gelijk aan 0.", + "quick_chore_editor_request_failed": "Taakverzoek mislukt", + "quick_chore_editor_update_action": "Update taak", + "quick_chore_editor_updated": "Taak bijgewerkt", "year_end": "Jaareinde", "independent": "Onafhankelijk", "never_overdue": "Nooit te laat", diff --git a/custom_components/choreops/dashboards/translations/pl_dashboard.json b/custom_components/choreops/dashboards/translations/pl_dashboard.json index 5f5cd2b..8155db7 100644 --- a/custom_components/choreops/dashboards/translations/pl_dashboard.json +++ b/custom_components/choreops/dashboards/translations/pl_dashboard.json @@ -55,7 +55,7 @@ "criteria": "Kryteria", "cumulative": "Łączny", "quests": "Zadania", - "ranks": "Rangi", + "ranks": "Odznaki", "custom": "Własny interwał", "custom_1_month": "Własny interwał (1 miesiąc)", "custom_1_quarter": "Własny interwał (1 kwartał)", @@ -203,7 +203,7 @@ "select_chore_edit": "Wybierz zadanie, aby edytować datę wykonania", "select_new_date": "Wybierz nową datę i godzinę", "seven_day_activity_log": "7-dniowy dziennik aktywności", - "showcase": "Wyróżnione", + "showcase": "statystyki", "show_blocked": "Pokaż zablokowane", "show_completed": "Pokaż ukończone", "show_overviews": "Pokaż podsumowania", @@ -239,7 +239,7 @@ "total_bonus": "Całkowity bonus", "total_chores": "Całkowita liczba obowiązków", "total_completed": "Całkowita liczba wykonanych prac domowych", - "total_earned": "Całkowita kwota zarobiona", + "total_earned": "Łącznie zdobyto", "total": "Całkowity", "total_penalty": "Całkowita kara", "true": "PRAWDA", @@ -250,10 +250,25 @@ "weekly_completed": "Ukończone tygodniowo", "weekly_performance": "Wydajność tygodniowa", "week_end": "Weekend", - "welcome": "Powitanie", + "welcome": "Witaj", "went_overdue": "Poszło po terminie", "month_end": "Koniec miesiąca", "quarter_end": "Koniec kwartału", + "quick_chore_editor_chore_name": "Nazwa zadania", + "quick_chore_editor_chore_name_required": "Nazwa zadania jest wymagana", + "quick_chore_editor_create": "Tworzyć", + "quick_chore_editor_create_action": "Utwórz zadanie", + "quick_chore_editor_created": "Utworzono zadanie", + "quick_chore_editor_description": "Opis", + "quick_chore_editor_edit": "Redagować", + "quick_chore_editor_frequency": "Częstotliwość", + "quick_chore_editor_icon": "Ikona", + "quick_chore_editor_missing_helper": "Brak pomocy w panelu informacyjnym systemu ChoreOps", + "quick_chore_editor_points": "Zwrotnica", + "quick_chore_editor_points_invalid": "Punkty muszą być liczbą większą lub równą 0", + "quick_chore_editor_request_failed": "Prośba o wykonanie zadania nie powiodła się", + "quick_chore_editor_update_action": "Aktualizacja zadania", + "quick_chore_editor_updated": "Zaktualizowano zadanie", "year_end": "Koniec roku", "independent": "Niezależny", "never_overdue": "Nigdy nie spóźniony", diff --git a/custom_components/choreops/dashboards/translations/pt_dashboard.json b/custom_components/choreops/dashboards/translations/pt_dashboard.json index 0cdb146..7aa9451 100644 --- a/custom_components/choreops/dashboards/translations/pt_dashboard.json +++ b/custom_components/choreops/dashboards/translations/pt_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Atrasou", "month_end": "Fim do mês", "quarter_end": "Fim do trimestre", + "quick_chore_editor_chore_name": "Nome da tarefa", + "quick_chore_editor_chore_name_required": "É necessário especificar o nome da tarefa.", + "quick_chore_editor_create": "Criar", + "quick_chore_editor_create_action": "Criar tarefa", + "quick_chore_editor_created": "Tarefa criada", + "quick_chore_editor_description": "Descrição", + "quick_chore_editor_edit": "Editar", + "quick_chore_editor_frequency": "Frequência", + "quick_chore_editor_icon": "Ícone", + "quick_chore_editor_missing_helper": "Problema com o assistente do painel de controlo do sistema ChoreOps resolvido", + "quick_chore_editor_points": "Pontos", + "quick_chore_editor_points_invalid": "A pontuação deve ser um número maior ou igual a 0.", + "quick_chore_editor_request_failed": "A solicitação de tarefa falhou", + "quick_chore_editor_update_action": "Atualizar tarefa", + "quick_chore_editor_updated": "Tarefa atualizada", "year_end": "Fim de ano", "independent": "Independente", "never_overdue": "Nunca atrasado", diff --git a/custom_components/choreops/dashboards/translations/sk_dashboard.json b/custom_components/choreops/dashboards/translations/sk_dashboard.json index 048e105..25d478a 100644 --- a/custom_components/choreops/dashboards/translations/sk_dashboard.json +++ b/custom_components/choreops/dashboards/translations/sk_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Meškalo", "month_end": "Koniec mesiaca", "quarter_end": "Koniec štvrťroka", + "quick_chore_editor_chore_name": "Názov domácej úlohy", + "quick_chore_editor_chore_name_required": "Názov úlohy je povinný", + "quick_chore_editor_create": "Vytvoriť", + "quick_chore_editor_create_action": "Vytvoriť domácu úlohu", + "quick_chore_editor_created": "Úloha vytvorená", + "quick_chore_editor_description": "Popis", + "quick_chore_editor_edit": "Upraviť", + "quick_chore_editor_frequency": "Frekvencia", + "quick_chore_editor_icon": "Ikona", + "quick_chore_editor_missing_helper": "Pomocník pre dashboard systému ChoreOps nebol vyriešený", + "quick_chore_editor_points": "Body", + "quick_chore_editor_points_invalid": "Body musia byť číslo väčšie alebo rovné 0", + "quick_chore_editor_request_failed": "Žiadosť o domácu úlohu zlyhala", + "quick_chore_editor_update_action": "Aktualizácia úlohy", + "quick_chore_editor_updated": "Úloha aktualizovaná", "year_end": "Koniec roka", "independent": "Nezávislý", "never_overdue": "Nikdy po termíne", diff --git a/custom_components/choreops/dashboards/translations/sl_dashboard.json b/custom_components/choreops/dashboards/translations/sl_dashboard.json index d113b7a..766ad8c 100644 --- a/custom_components/choreops/dashboards/translations/sl_dashboard.json +++ b/custom_components/choreops/dashboards/translations/sl_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Zamujalo", "month_end": "Konec meseca", "quarter_end": "Konec četrtine", + "quick_chore_editor_chore_name": "Ime opravila", + "quick_chore_editor_chore_name_required": "Ime opravila je obvezno", + "quick_chore_editor_create": "Ustvari", + "quick_chore_editor_create_action": "Ustvari opravilo", + "quick_chore_editor_created": "Naloga ustvarjena", + "quick_chore_editor_description": "Opis", + "quick_chore_editor_edit": "Uredi", + "quick_chore_editor_frequency": "Pogostost", + "quick_chore_editor_icon": "Ikona", + "quick_chore_editor_missing_helper": "Pomočnik za nadzorno ploščo sistema ChoreOps ni bil odpravljen", + "quick_chore_editor_points": "Točke", + "quick_chore_editor_points_invalid": "Točke morajo biti število večje ali enako 0", + "quick_chore_editor_request_failed": "Zahteva za opravilo ni uspela", + "quick_chore_editor_update_action": "Posodobitev opravila", + "quick_chore_editor_updated": "Opravilo posodobljeno", "year_end": "Konec leta", "independent": "Neodvisen", "never_overdue": "Nikoli zamudno", diff --git a/custom_components/choreops/dashboards/translations/sv_dashboard.json b/custom_components/choreops/dashboards/translations/sv_dashboard.json index 35c2cf5..eadf63c 100644 --- a/custom_components/choreops/dashboards/translations/sv_dashboard.json +++ b/custom_components/choreops/dashboards/translations/sv_dashboard.json @@ -254,6 +254,21 @@ "went_overdue": "Gick försenad", "month_end": "Månadsslut", "quarter_end": "Kvartsslut", + "quick_chore_editor_chore_name": "Namn på syssla", + "quick_chore_editor_chore_name_required": "Sysslan måste namnges", + "quick_chore_editor_create": "Skapa", + "quick_chore_editor_create_action": "Skapa syssla", + "quick_chore_editor_created": "Syssla skapad", + "quick_chore_editor_description": "Beskrivning", + "quick_chore_editor_edit": "Redigera", + "quick_chore_editor_frequency": "Frekvens", + "quick_chore_editor_icon": "Ikon", + "quick_chore_editor_missing_helper": "Ingen hjälp för ChoreOps-systemets instrumentpanel löst", + "quick_chore_editor_points": "Poäng", + "quick_chore_editor_points_invalid": "Poängen måste vara ett tal större än eller lika med 0", + "quick_chore_editor_request_failed": "Begäran om syssla misslyckades", + "quick_chore_editor_update_action": "Uppdatera syssla", + "quick_chore_editor_updated": "Syssla uppdaterad", "year_end": "Årsslut", "independent": "Oberoende", "never_overdue": "Aldrig försenad", diff --git a/custom_components/choreops/helpers/entity_helpers.py b/custom_components/choreops/helpers/entity_helpers.py index 4cc07ac..1995b21 100644 --- a/custom_components/choreops/helpers/entity_helpers.py +++ b/custom_components/choreops/helpers/entity_helpers.py @@ -718,7 +718,7 @@ async def remove_orphaned_shared_chore_sensors( Count of removed entities. """ prefix = f"{entry_id}_" - suffix = const.DATA_GLOBAL_STATE_SUFFIX + suffix = const.SENSOR_KC_UID_SUFFIX_SHARED_CHORE_GLOBAL_STATE_SENSOR def is_valid(unique_id: str) -> bool: chore_id = unique_id[len(prefix) : -len(suffix)] @@ -726,7 +726,10 @@ def is_valid(unique_id: str) -> bool: return bool( chore_info and chore_info.get(const.DATA_CHORE_COMPLETION_CRITERIA) - == const.COMPLETION_CRITERIA_SHARED + in ( + const.COMPLETION_CRITERIA_SHARED, + const.COMPLETION_CRITERIA_SHARED_FIRST, + ) ) return await remove_entities_by_validator( diff --git a/custom_components/choreops/helpers/flow_helpers.py b/custom_components/choreops/helpers/flow_helpers.py index 1db194d..82937cc 100644 --- a/custom_components/choreops/helpers/flow_helpers.py +++ b/custom_components/choreops/helpers/flow_helpers.py @@ -801,6 +801,8 @@ def build_chore_schema( assignees_dict: dict[str, str], default: dict[str, Any] | None = None, frequency_options: list[str] | None = None, + *, + preserve_optional_on_omit: bool = False, ) -> vol.Schema: """Build a schema for chores, referencing existing assignees by name. @@ -816,22 +818,34 @@ def build_chore_schema( frequency_options: List of frequency options to show. Defaults to const.CHORE_FREQUENCY_OPTIONS (all frequencies). Config flow should pass const.CHORE_FREQUENCY_OPTIONS_CONFIG_FLOW to exclude DAILY_MULTI. + preserve_optional_on_omit: When True, optional fields use suggested values + for display but do not inject backend defaults into sparse edit payloads. """ default = default or {} frequency_options = frequency_options or const.CHORE_FREQUENCY_OPTIONS assignee_choices = {k: k for k in assignees_dict} + def _optional_field( + field_key: str, + default_value: Any | None = None, + *, + has_default: bool = True, + ) -> Any: + if preserve_optional_on_omit or not has_default: + return vol.Optional(field_key) + return vol.Optional(field_key, default=default_value) + # Build schema fields in approved UX order grouped by sections root_form_fields: dict[Any, Any] = { vol.Required(const.CFOF_CHORES_INPUT_NAME, default=const.SENTINEL_EMPTY): str, - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_DESCRIPTION, - default=const.SENTINEL_EMPTY, + const.SENTINEL_EMPTY, ): str, - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_ICON, - default=const.SENTINEL_EMPTY, + const.SENTINEL_EMPTY, ): selector.IconSelector(), vol.Required( const.CFOF_CHORES_INPUT_DEFAULT_POINTS, @@ -891,20 +905,18 @@ def build_chore_schema( translation_key=const.TRANS_KEY_FLOW_HELPERS_RECURRING_FREQUENCY, ) ), - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_DUE_DATE, - default=default.get(const.CFOF_CHORES_INPUT_DUE_DATE), + default.get(const.CFOF_CHORES_INPUT_DUE_DATE), ): vol.Any(None, selector.DateTimeSelector()), # Keep clear_due_date directly under due_date for consistent UX. - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_CLEAR_DUE_DATE, - default=False, + False, ): selector.BooleanSelector(), - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_APPLICABLE_DAYS, - # Explicitly check for None to preserve empty list [] (when user clears all days) - # Storage may have null when per_assignee_applicable_days is source of truth - default=( + ( default.get(const.CFOF_CHORES_INPUT_APPLICABLE_DAYS) if default.get(const.CFOF_CHORES_INPUT_APPLICABLE_DAYS) is not None else const.DEFAULT_APPLICABLE_DAYS @@ -919,9 +931,9 @@ def build_chore_schema( translation_key=const.TRANS_KEY_FLOW_HELPERS_APPLICABLE_DAYS, ) ), - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_DUE_WINDOW_OFFSET, - default=default.get( + default.get( const.CFOF_CHORES_INPUT_DUE_WINDOW_OFFSET, const.DEFAULT_DUE_WINDOW_OFFSET, ), @@ -938,11 +950,9 @@ def build_chore_schema( ), ), ): selector.BooleanSelector(), - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_CUSTOM_INTERVAL, - default=( - custom_interval_default if custom_interval_default is not None else 1 - ), + custom_interval_default if custom_interval_default is not None else 1, ): vol.Any( None, vol.All( @@ -955,9 +965,9 @@ def build_chore_schema( vol.Range(min=1), ), ), - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_CUSTOM_INTERVAL_UNIT, - default=( + ( custom_interval_unit_default if custom_interval_unit_default is not None else const.TIME_UNIT_DAYS @@ -1030,18 +1040,18 @@ def build_chore_schema( mode=selector.SelectSelectorMode.DROPDOWN, ) ), - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_DUE_REMINDER_OFFSET, - default=default.get( + default.get( const.CFOF_CHORES_INPUT_DUE_REMINDER_OFFSET, const.DEFAULT_DUE_REMINDER_OFFSET, ), ): selector.TextSelector( selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT) ), - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_NOTIFICATIONS, - default=_build_notification_defaults(default), + _build_notification_defaults(default), ): selector.SelectSelector( selector.SelectSelectorConfig( options=[ @@ -1060,9 +1070,9 @@ def build_chore_schema( const.CFOF_CHORES_INPUT_SHOW_ON_CALENDAR, default=default.get(const.CFOF_CHORES_INPUT_SHOW_ON_CALENDAR, True), ): selector.BooleanSelector(), - vol.Optional( + _optional_field( const.CFOF_CHORES_INPUT_LABELS, - default=[], + [], ): selector.LabelSelector(selector.LabelSelectorConfig(multiple=True)), } diff --git a/custom_components/choreops/managers/chore_manager.py b/custom_components/choreops/managers/chore_manager.py index 4b514f4..20d2463 100644 --- a/custom_components/choreops/managers/chore_manager.py +++ b/custom_components/choreops/managers/chore_manager.py @@ -21,7 +21,7 @@ from contextlib import AsyncExitStack from dataclasses import dataclass from datetime import datetime, timedelta -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any, Literal, cast from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.util import dt as dt_util @@ -61,6 +61,7 @@ from ..type_defs import ( AssigneeChoreDataEntry, ChoreData, + ChoreEntitySyncContext, GlobalChoreStateContext, ResetApplyContext, ResetBoundaryCategory, @@ -3192,6 +3193,26 @@ def create_chore( return chore_data + def build_entity_sync_context( + self, + chore_id: str, + *, + mutation: Literal["created", "updated", "deleted"], + previous_chore: ChoreData | None = None, + current_chore: ChoreData | None = None, + ) -> ChoreEntitySyncContext: + """Build the runtime entity-sync context for a chore mutation. + + This keeps caller-side mutation classification rooted in the chore domain + while delegating final orchestration to the coordinator runtime sync layer. + """ + return self._coordinator.build_chore_entity_sync_context( + chore_id, + mutation=mutation, + previous_chore=previous_chore, + current_chore=current_chore, + ) + def update_chore( self, chore_id: str, updates: dict[str, Any], *, immediate_persist: bool = False ) -> dict[str, Any]: diff --git a/custom_components/choreops/managers/ui_manager.py b/custom_components/choreops/managers/ui_manager.py index 362d364..9fbcb49 100644 --- a/custom_components/choreops/managers/ui_manager.py +++ b/custom_components/choreops/managers/ui_manager.py @@ -11,9 +11,11 @@ from __future__ import annotations +import asyncio from copy import deepcopy +from dataclasses import dataclass from datetime import datetime, timedelta -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util @@ -29,6 +31,18 @@ from ..coordinator import ChoreOpsDataCoordinator +@dataclass(slots=True) +class HelperShardRuntimePlan: + """Runtime shard allocation for one user and payload family.""" + + family: str + mode: Literal["inline", "sharded"] + shard_item_ids: list[list[str]] + expected_shard_count: int + last_accepted_serialized_size: int + last_reconciliation_outcome: str + + class UIManager(BaseManager): """Manager for UI features including translation sensors and datetime helpers. @@ -58,6 +72,11 @@ def __init__( self._pending_chore_changed: bool = True self._pending_reward_changed: bool = True + # Runtime-owned shard plans keyed by payload family then user ID. + self._helper_shard_plans: dict[str, dict[str, HelperShardRuntimePlan]] = { + const.HELPER_SHARD_FAMILY_CHORES: {} + } + async def async_setup(self) -> None: """Set up the UI manager. @@ -88,6 +107,8 @@ async def async_setup(self) -> None: # Listen for midnight rollover to bump datetime helpers self.listen(const.SIGNAL_SUFFIX_MIDNIGHT_ROLLOVER, self._on_midnight_rollover) + await self.async_prepare_startup_chore_shard_plans() + const.LOGGER.debug("UIManager setup complete for entry %s", self.entry_id) async def _on_midnight_rollover(self, payload: dict[str, Any]) -> None: @@ -119,6 +140,11 @@ def _on_user_deleted(self, payload: dict[str, Any]) -> None: # Don't need payload data - just check if any languages are now unused self.remove_unused_translation_sensors() + user_id = payload.get(const.DATA_USER_ID) + if isinstance(user_id, str) and user_id: + self.clear_helper_shard_plan(user_id, const.HELPER_SHARD_FAMILY_CHORES) + self._remove_chore_shard_entities_for_user(user_id) + def _on_chore_changed(self, payload: dict[str, Any]) -> None: """Handle chore state change - mark pending approvals as changed. @@ -206,6 +232,199 @@ def register_translation_sensor_callback( """ self._sensor_add_entities_callback = async_add_entities + async def async_prepare_startup_chore_shard_plans(self) -> None: + """Build startup chore shard plans from current coordinator data.""" + await self.async_reconcile_chore_shards_for_users( + list(self.coordinator.assignees_data), + registry_only=True, + ) + + async def async_reconcile_chore_shards_for_users( + self, + user_ids: list[str], + *, + registry_only: bool = False, + ) -> None: + """Rebuild chore shard plans and reconcile shard helper entities.""" + if not user_ids: + return + + from ..sensor import AssigneeDashboardChoreShardSensor, build_chore_shard_plan + + entity_registry = er.async_get(self.hass) + created_sensors: list[AssigneeDashboardChoreShardSensor] = [] + + for user_id in sorted(set(user_ids)): + user_data = self.coordinator.assignees_data.get(user_id) + if not isinstance(user_data, dict): + self.clear_helper_shard_plan(user_id, const.HELPER_SHARD_FAMILY_CHORES) + self._remove_chore_shard_entities_for_user(user_id) + continue + + user_name = user_data.get(const.DATA_USER_NAME) + if not isinstance(user_name, str) or not user_name: + continue + + previous_plan = self.get_helper_shard_plan( + user_id, const.HELPER_SHARD_FAMILY_CHORES + ) + plan = build_chore_shard_plan( + self.hass, + self.coordinator, + self.coordinator.config_entry, + user_id, + user_name, + previous_plan=previous_plan, + ) + self.set_helper_shard_plan(user_id, const.HELPER_SHARD_FAMILY_CHORES, plan) + + existing_entries = self._get_existing_chore_shard_entries(user_id) + live_shard_indexes = self._get_live_chore_shard_indexes(existing_entries) + expected_indexes = set(range(1, plan.expected_shard_count + 1)) + + removed_count = 0 + for shard_index, entity_entry in existing_entries.items(): + if shard_index in expected_indexes: + continue + entity_registry.async_remove(entity_entry.entity_id) + removed_count += 1 + + created_count = 0 + if not registry_only and self._sensor_add_entities_callback is not None: + for shard_index in sorted(expected_indexes): + if shard_index in live_shard_indexes: + continue + created_sensors.append( + AssigneeDashboardChoreShardSensor( + self.coordinator, + self.coordinator.config_entry, + user_id, + user_name, + shard_index, + ) + ) + created_count += 1 + + missing_count = max(plan.expected_shard_count - len(existing_entries), 0) + if registry_only and missing_count > 0: + plan.last_reconciliation_outcome = ( + f"planned_missing={missing_count} removed={removed_count}" + ) + else: + plan.last_reconciliation_outcome = ( + f"created={created_count} removed={removed_count}" + ) + + if created_sensors and self._sensor_add_entities_callback is not None: + self._sensor_add_entities_callback(created_sensors) + + async def async_finalize_chore_shards_for_users( + self, + user_ids: list[str], + *, + registry_only: bool = True, + ) -> None: + """Refresh shard plans from the settled registry and republish helper state.""" + if registry_only: + await asyncio.sleep(0) + await self.async_reconcile_chore_shards_for_users( + user_ids, + registry_only=registry_only, + ) + self.coordinator.async_update_listeners() + if not registry_only: + self.hass.async_create_task( + self.async_finalize_chore_shards_for_users( + user_ids, + registry_only=True, + ) + ) + + def clear_runtime_state(self) -> None: + """Clear runtime-only UI manager state on unload.""" + self._translation_sensors_created.clear() + self._helper_shard_plans = {const.HELPER_SHARD_FAMILY_CHORES: {}} + self._sensor_add_entities_callback = None + + def get_helper_shard_plan( + self, user_id: str, family: str + ) -> HelperShardRuntimePlan | None: + """Return the runtime shard plan for one user and family.""" + return self._helper_shard_plans.get(family, {}).get(user_id) + + def set_helper_shard_plan( + self, user_id: str, family: str, plan: HelperShardRuntimePlan + ) -> None: + """Store the runtime shard plan for one user and family.""" + self._helper_shard_plans.setdefault(family, {})[user_id] = plan + + def clear_helper_shard_plan(self, user_id: str, family: str) -> None: + """Drop one runtime shard plan entry if present.""" + family_plans = self._helper_shard_plans.get(family) + if family_plans is not None: + family_plans.pop(user_id, None) + + def get_chore_shard_helper_eids(self, user_id: str) -> list[str]: + """Return ordered chore shard helper entity IDs for one user.""" + plan = self.get_helper_shard_plan(user_id, const.HELPER_SHARD_FAMILY_CHORES) + if plan is None or plan.expected_shard_count == 0: + return [] + + return [ + entity_entry.entity_id + for shard_index in range(1, plan.expected_shard_count + 1) + if ( + entity_entry := self._get_existing_chore_shard_entries(user_id).get( + shard_index + ) + ) + ] + + def get_chore_shard_unique_id(self, user_id: str, shard_index: int) -> str: + """Return the stable unique ID for one chore shard helper.""" + return ( + f"{self.coordinator.config_entry.entry_id}_{user_id}_{shard_index}" + f"{const.SENSOR_KC_UID_SUFFIX_UI_DASHBOARD_CHORE_SHARD_HELPER}" + ) + + def _get_existing_chore_shard_entries( + self, user_id: str + ) -> dict[int, er.RegistryEntry]: + """Return existing chore shard registry entries keyed by shard index.""" + entity_registry = er.async_get(self.hass) + prefix = f"{self.coordinator.config_entry.entry_id}_{user_id}_" + suffix = const.SENSOR_KC_UID_SUFFIX_UI_DASHBOARD_CHORE_SHARD_HELPER + shard_entries: dict[int, er.RegistryEntry] = {} + + for entry in entity_registry.entities.values(): + unique_id = entry.unique_id + if not unique_id.startswith(prefix) or not unique_id.endswith(suffix): + continue + + shard_index = unique_id[len(prefix) : -len(suffix)] + if not shard_index.isdigit(): + continue + + shard_entries[int(shard_index)] = entry + + return shard_entries + + def _get_live_chore_shard_indexes( + self, existing_entries: dict[int, er.RegistryEntry] + ) -> set[int]: + """Return shard indexes that currently have live states in Home Assistant.""" + return { + shard_index + for shard_index, entity_entry in existing_entries.items() + if self.hass.states.get(entity_entry.entity_id) is not None + } + + def _remove_chore_shard_entities_for_user(self, user_id: str) -> None: + """Remove all chore shard helper entities for one user.""" + entity_registry = er.async_get(self.hass) + for entity_entry in self._get_existing_chore_shard_entries(user_id).values(): + entity_registry.async_remove(entity_entry.entity_id) + def mark_translation_sensor_created(self, lang_code: str) -> None: """Mark that a translation sensor for this language has been created. diff --git a/custom_components/choreops/manifest.json b/custom_components/choreops/manifest.json index 600a175..ba98462 100644 --- a/custom_components/choreops/manifest.json +++ b/custom_components/choreops/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/ccpk1/choreops/issues", "quality_scale": "platinum", "requirements": ["python-dateutil>=2.9.0"], - "version": "1.0.7" + "version": "1.0.8" } diff --git a/custom_components/choreops/options_flow.py b/custom_components/choreops/options_flow.py index c528538..438fe09 100644 --- a/custom_components/choreops/options_flow.py +++ b/custom_components/choreops/options_flow.py @@ -7,6 +7,7 @@ import asyncio import contextlib +from copy import deepcopy from datetime import datetime from typing import TYPE_CHECKING, Any, cast import uuid @@ -347,6 +348,24 @@ async def _async_route_to_daily_multi_helper( const.LOGGER.debug(log_message, *log_args) return await self.async_step_chores_daily_multi() + async def _async_sync_chore_entities( + self, + chore_id: str, + *, + mutation: str, + previous_chore: "ChoreData | None" = None, + current_chore: "ChoreData | None" = None, + ) -> None: + """Synchronize chore-linked runtime entities for options-flow CRUD.""" + coordinator = self._get_coordinator() + sync_context = coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation=mutation, + previous_chore=previous_chore, + current_chore=current_chore, + ) + await coordinator.async_sync_chore_entities(sync_context) + def _build_dashboard_template_preferences_markdown_lines( self, template_ids: list[str], @@ -1183,6 +1202,11 @@ async def async_step_add_chore(self, user_input=None): prebuilt=True, immediate_persist=True, ) + await self._async_sync_chore_entities( + internal_id, + mutation="created", + current_chore=deepcopy(coordinator.chores_data[internal_id]), + ) # CFE-2026-001 FIX: Single-assignee DAILY_MULTI without times # needs to route to times helper (main form doesn't have times field) @@ -1200,7 +1224,6 @@ async def async_step_add_chore(self, user_input=None): chore_name, internal_id, ) - self._mark_reload_needed() return await self.async_step_init() # Multiple assignees: create chore, then show per-assignee details helper @@ -1211,6 +1234,11 @@ async def async_step_add_chore(self, user_input=None): prebuilt=True, immediate_persist=True, ) + await self._async_sync_chore_entities( + internal_id, + mutation="created", + current_chore=deepcopy(coordinator.chores_data[internal_id]), + ) # Store chore data and template values for helper form self._chore_being_edited = dict(new_chore_data) @@ -1235,6 +1263,11 @@ async def async_step_add_chore(self, user_input=None): prebuilt=True, immediate_persist=True, ) + await self._async_sync_chore_entities( + internal_id, + mutation="created", + current_chore=deepcopy(coordinator.chores_data[internal_id]), + ) return await self._async_route_to_daily_multi_helper( new_chore_data, @@ -1251,6 +1284,11 @@ async def async_step_add_chore(self, user_input=None): prebuilt=True, immediate_persist=True, ) + await self._async_sync_chore_entities( + internal_id, + mutation="created", + current_chore=deepcopy(coordinator.chores_data[internal_id]), + ) const.LOGGER.debug( "Added Chore '%s' with ID: %s and Due Date %s", @@ -1258,7 +1296,6 @@ async def async_step_add_chore(self, user_input=None): internal_id, due_date_str, ) - self._mark_reload_needed() return await self.async_step_init() # Use flow_helpers.build_chore_schema, passing current assignees @@ -1296,6 +1333,7 @@ async def async_step_edit_chore(self, user_input=None): return self.async_abort(reason=const.TRANS_KEY_CFOF_INVALID_CHORE) chore_data = chores_dict[internal_id] + original_chore = deepcopy(chore_data) if user_input is not None: user_input = fh.normalize_chore_form_input(user_input) @@ -1332,7 +1370,10 @@ async def async_step_edit_chore(self, user_input=None): if errors: # Merge original chore data with user's attempted input merged_defaults = {**chore_data, **user_input} - schema = fh.build_chore_schema(assignees_dict) + schema = fh.build_chore_schema( + assignees_dict, + preserve_optional_on_omit=True, + ) schema = self.add_suggested_values_to_schema( schema, fh.build_chore_section_suggested_values(merged_defaults), @@ -1356,13 +1397,6 @@ async def async_step_edit_chore(self, user_input=None): existing_chore=chore_data, ) - # Check if assigned assignees changed (for reload decision) - old_assigned = set(chore_data.get(const.DATA_CHORE_ASSIGNED_USER_IDS, [])) - new_assigned = set( - transformed_data.get(const.DATA_CHORE_ASSIGNED_USER_IDS, []) - ) - assignments_changed = old_assigned != new_assigned - # Use Manager-owned CRUD (handles badge recalc and orphan cleanup) const.LOGGER.debug( "CHORE UPDATE: About to update chore %s with completion_criteria=%s", @@ -1382,11 +1416,12 @@ async def async_step_edit_chore(self, user_input=None): chore_data.get(const.DATA_CHORE_NAME), ) const.LOGGER.debug("Edited Chore '%s' with ID: %s", new_name, internal_id) - - # Only reload if assignments changed (entities added/removed) - if assignments_changed: - const.LOGGER.debug("Chore assignments changed, marking reload needed") - self._mark_reload_needed() + await self._async_sync_chore_entities( + str(internal_id), + mutation="updated", + previous_chore=original_chore, + current_chore=deepcopy(coordinator.chores_data[str(internal_id)]), + ) # For INDEPENDENT chores with assigned assignees, handle per-assignee date editing # Use merged_chore (post-update) for routing decisions @@ -1515,7 +1550,6 @@ async def async_step_edit_chore(self, user_input=None): new_name, ) - self._mark_reload_needed() return await self.async_step_init() # Multiple assignees: show unified per-assignee details step (PKAD-2026-001) @@ -1815,7 +1849,10 @@ async def async_step_edit_chore(self, user_input=None): suggested_values[const.CFOF_CHORES_INPUT_NOTIFICATIONS] = notifications_list # Build schema and apply suggested values - schema = fh.build_chore_schema(assignees_dict) + schema = fh.build_chore_schema( + assignees_dict, + preserve_optional_on_omit=True, + ) schema = self.add_suggested_values_to_schema( schema, fh.build_chore_section_suggested_values(suggested_values), @@ -1997,7 +2034,6 @@ async def async_step_edit_chore_per_user_dates( # Clear stored state self._chore_being_edited = None self._chore_template_date_raw = None - self._mark_reload_needed() return await self.async_step_init() # Build dynamic schema with assignee names as field keys (for readable labels) @@ -2321,7 +2357,6 @@ async def async_step_edit_chore_per_user_details( self._chore_template_date_raw = None self._chore_template_applicable_days = None self._chore_template_daily_multi_times = None - self._mark_reload_needed() return await self.async_step_init() # Build form schema @@ -2505,7 +2540,6 @@ async def async_step_chores_daily_multi( times_str, ) - self._mark_reload_needed() # Clear temp state self._chore_being_edited = None return await self.async_step_init() @@ -2552,6 +2586,10 @@ async def async_step_delete_chore(self, user_input=None): coordinator.chore_manager.delete_chore( str(internal_id), immediate_persist=True ) + await self._async_sync_chore_entities( + str(internal_id), + mutation="deleted", + ) const.LOGGER.debug( "Deleted Chore '%s' with ID: %s", chore_name, internal_id diff --git a/custom_components/choreops/sensor.py b/custom_components/choreops/sensor.py index d7fc2e0..8f34e48 100644 --- a/custom_components/choreops/sensor.py +++ b/custom_components/choreops/sensor.py @@ -57,8 +57,8 @@ 13. AssigneeBonusAppliedSensor - Bonus application count (data in dashboard helper) """ -from collections.abc import Callable from datetime import datetime, timedelta +import json from typing import Any, cast from homeassistant.components.sensor import SensorEntity, SensorStateClass @@ -86,6 +86,7 @@ should_create_workflow_buttons, ) from .helpers.translation_helpers import load_dashboard_translation +from .managers.ui_manager import HelperShardRuntimePlan from .sensor_legacy import ( AssigneeBonusAppliedSensor, AssigneeChoreCompletionDailySensor, @@ -552,6 +553,11 @@ async def async_setup_entry( # This enables creating new translation sensors when a assignee changes to a new language coordinator.ui_manager.register_translation_sensor_callback(async_add_entities) + await coordinator.ui_manager.async_reconcile_chore_shards_for_users( + list(coordinator.assignees_data), + registry_only=True, + ) + # Register callback for dynamic chore/reward sensor creation (services) register_chore_reward_callback(async_add_entities) @@ -563,6 +569,34 @@ async def async_setup_entry( entities.append(SystemDashboardHelperSensor(coordinator, entry)) + # Startup/reload shard helpers must be part of the platform's initial entity + # add so their states exist end to end before the main helper republishes + # deterministic chore_helper_eids. + for assignee_id, assignee_data in coordinator.assignees_data.items(): + assignee_name = get_item_name_or_log_error( + "assignee", assignee_id, assignee_data, const.DATA_USER_NAME + ) + if not assignee_name: + continue + + plan = coordinator.ui_manager.get_helper_shard_plan( + assignee_id, + const.HELPER_SHARD_FAMILY_CHORES, + ) + if plan is None or plan.mode != const.HELPER_SHARD_MODE_SHARDED: + continue + + for shard_index in range(1, plan.expected_shard_count + 1): + entities.append( + AssigneeDashboardChoreShardSensor( + coordinator, + entry, + assignee_id, + assignee_name, + shard_index, + ) + ) + # Dashboard helper sensors: Created last to ensure all referenced entities exist # This prevents entity ID lookup failures during initial setup for assignee_id, assignee_data in coordinator.assignees_data.items(): @@ -582,17 +616,24 @@ async def async_setup_entry( ) async_add_entities(entities) + coordinator.hass.async_create_task( + coordinator.ui_manager.async_finalize_chore_shards_for_users( + list(coordinator.assignees_data), + registry_only=True, + ) + ) + coordinator.async_update_listeners() # ------------------------------------------------------------------------------------------ # Module-level callback storage for dynamic entity creation # ------------------------------------------------------------------------------------------ -_async_add_entities_callback: Callable | None = None +_async_add_entities_callback: AddEntitiesCallback | None = None def register_chore_reward_callback( - async_add_entities: Callable, + async_add_entities: AddEntitiesCallback, ) -> None: """Register async_add_entities callback for dynamic chore/reward sensor creation. @@ -603,33 +644,67 @@ def register_chore_reward_callback( _async_add_entities_callback = async_add_entities -def create_chore_entities(coordinator: ChoreOpsDataCoordinator, chore_id: str) -> None: - """Create chore status sensor entities for a newly created chore. +def _sensor_entity_exists( + coordinator: ChoreOpsDataCoordinator, + unique_id: str, +) -> bool: + """Return whether a sensor entity with the given unique ID already exists.""" + entity_registry = async_get(coordinator.hass) + return ( + entity_registry.async_get_entity_id("sensor", const.DOMAIN, unique_id) + is not None + ) + - Called by create_chore service after adding chore to storage. - Creates AssigneeChoreStatusSensor for each assigned assignee. +def create_chore_entities( + coordinator: ChoreOpsDataCoordinator, + chore_id: str, + *, + assignee_ids: list[str] | None = None, + replace_existing: bool = False, +) -> int: + """Create missing chore-linked sensor entities for a chore. + + Args: + coordinator: Runtime coordinator. + chore_id: Internal ID of the chore. + assignee_ids: Optional subset of assignee IDs to create status sensors for. + When omitted, create sensors for all currently assigned assignees. + replace_existing: If True, do not filter existing registry entities. This is + intended for flows that have already removed the prior entity set. + + Returns: + Count of entities handed to Home Assistant for creation. """ if _async_add_entities_callback is None: const.LOGGER.warning("Cannot create chore entities: callback not registered") - return + return 0 chore_info = coordinator.chores_data.get(chore_id) if not chore_info: const.LOGGER.warning( "Cannot create chore entities: chore %s not found", chore_id ) - return + return 0 chore_name = get_item_name_or_log_error( "chore", chore_id, chore_info, const.DATA_CHORE_NAME ) if not chore_name: - return + return 0 assigned_assignees_ids = chore_info.get(const.DATA_CHORE_ASSIGNED_USER_IDS, []) - entities: list[AssigneeChoreStatusSensor] = [] + target_assignee_ids = assigned_assignees_ids + if assignee_ids is not None: + target_assignee_ids = [ + assignee_id + for assignee_id in assigned_assignees_ids + if assignee_id in assignee_ids + ] + + entities: list[SensorEntity] = [] - for assignee_id in assigned_assignees_ids: + for assignee_id in target_assignee_ids: assignee_data: AssigneeData = cast( "AssigneeData", coordinator.assignees_data.get(assignee_id) or {} ) @@ -639,6 +714,13 @@ def create_chore_entities(coordinator: ChoreOpsDataCoordinator, chore_id: str) - if not assignee_name: continue + unique_id = ( + f"{coordinator.config_entry.entry_id}_{assignee_id}_{chore_id}" + f"{const.SENSOR_KC_UID_SUFFIX_CHORE_STATUS_SENSOR}" + ) + if not replace_existing and _sensor_entity_exists(coordinator, unique_id): + continue + entities.append( AssigneeChoreStatusSensor( coordinator, @@ -650,12 +732,29 @@ def create_chore_entities(coordinator: ChoreOpsDataCoordinator, chore_id: str) - ) ) + if ChoreEngine.is_shared_chore(chore_info): + unique_id = ( + f"{coordinator.config_entry.entry_id}_{chore_id}" + f"{const.SENSOR_KC_UID_SUFFIX_SHARED_CHORE_GLOBAL_STATE_SENSOR}" + ) + if replace_existing or not _sensor_entity_exists(coordinator, unique_id): + entities.append( + SystemChoreSharedStateSensor( + coordinator, + coordinator.config_entry, + chore_id, + chore_name, + ) + ) + if entities: _async_add_entities_callback(entities) const.LOGGER.debug( - "Created %d chore status sensors for chore: %s", len(entities), chore_name + "Created %d chore-linked sensors for chore: %s", len(entities), chore_name ) + return len(entities) + def create_reward_entities( coordinator: ChoreOpsDataCoordinator, reward_id: str @@ -3958,6 +4057,241 @@ def icon(self) -> str | None: return None +def _serialized_payload_size(payload: dict[str, Any]) -> int: + """Return the exact serialized payload size in bytes.""" + return len( + json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + ) + + +def _build_chore_shard_payload( + chores_attr: list[dict[str, Any]], + shard_index: int, + shard_count: int, +) -> dict[str, Any]: + """Build the public payload for one chore shard helper.""" + return { + const.ATTR_PURPOSE: const.TRANS_KEY_PURPOSE_DASHBOARD_CHORE_SHARD_HELPER, + const.ATTR_SHARD_INDEX: shard_index, + const.ATTR_SHARD_COUNT: shard_count, + const.ATTR_HELPER_CONTRACT_VERSION: const.HELPER_CONTRACT_VERSION_V1, + "chores": AssigneeDashboardHelperSensor._sanitize_dashboard_chore_rows( + chores_attr + ), + } + + +def build_chore_shard_plan( + hass: HomeAssistant, + coordinator: ChoreOpsDataCoordinator, + entry: ChoreOpsConfigEntry, + assignee_id: str, + assignee_name: str, + *, + previous_plan: HelperShardRuntimePlan | None, +) -> HelperShardRuntimePlan: + """Build the current chore shard plan for one assignee.""" + from .managers.ui_manager import HelperShardRuntimePlan + + helper = AssigneeDashboardHelperSensor( + coordinator, + entry, + assignee_id, + assignee_name, + "", + ) + entity_registry = async_get(hass) + chores_attr = helper._build_chore_rows(entity_registry) + assignee_data = cast( + "dict[str, Any]", coordinator.assignees_data.get(assignee_id) or {} + ) + assignee_language = cast( + "str", + assignee_data.get( + const.DATA_USER_DASHBOARD_LANGUAGE, + const.DEFAULT_DASHBOARD_LANGUAGE, + ), + ) + translation_sensor_eid = coordinator.ui_manager.get_translation_sensor_eid( + assignee_language + ) + current_mode = ( + previous_plan.mode + if previous_plan is not None + else const.HELPER_SHARD_MODE_INLINE + ) + + inline_payload = helper._build_payload( + entity_registry, + chores_attr, + translation_sensor_eid=translation_sensor_eid, + chore_helper_eids=[], + shard_runtime={ + "family": const.HELPER_SHARD_FAMILY_CHORES, + "mode": const.HELPER_SHARD_MODE_INLINE, + "expected_shard_count": 0, + "last_accepted_serialized_size": 0, + "last_reconciliation_outcome": "planning", + }, + ) + inline_size = _serialized_payload_size(inline_payload) + + if current_mode == const.HELPER_SHARD_MODE_SHARDED: + if inline_size <= const.HELPER_SHARD_EXIT_BYTES: + return HelperShardRuntimePlan( + family=const.HELPER_SHARD_FAMILY_CHORES, + mode=const.HELPER_SHARD_MODE_INLINE, + shard_item_ids=[], + expected_shard_count=0, + last_accepted_serialized_size=inline_size, + last_reconciliation_outcome="planned_mode=inline", + ) + elif inline_size < const.HELPER_SHARD_ENTER_BYTES: + return HelperShardRuntimePlan( + family=const.HELPER_SHARD_FAMILY_CHORES, + mode=const.HELPER_SHARD_MODE_INLINE, + shard_item_ids=[], + expected_shard_count=0, + last_accepted_serialized_size=inline_size, + last_reconciliation_outcome="planned_mode=inline", + ) + + shard_groups: list[list[dict[str, Any]]] = [] + current_group: list[dict[str, Any]] = [] + + for chore in chores_attr: + candidate_group = [*current_group, chore] + candidate_payload = _build_chore_shard_payload( + candidate_group, + len(shard_groups) + 1, + len(shard_groups) + 1, + ) + if ( + current_group + and _serialized_payload_size(candidate_payload) + >= const.HELPER_SHARD_ENTER_BYTES + ): + shard_groups.append(current_group) + current_group = [chore] + else: + current_group = candidate_group + + if current_group: + shard_groups.append(current_group) + + placeholder_eids = [ + f"sensor.choreops_chore_shard_{index}" + for index in range(1, len(shard_groups) + 1) + ] + sharded_payload = helper._build_payload( + entity_registry, + [], + translation_sensor_eid=translation_sensor_eid, + chore_helper_eids=placeholder_eids, + shard_runtime={ + "family": const.HELPER_SHARD_FAMILY_CHORES, + "mode": const.HELPER_SHARD_MODE_SHARDED, + "expected_shard_count": len(shard_groups), + "last_accepted_serialized_size": 0, + "last_reconciliation_outcome": "planning", + }, + ) + + return HelperShardRuntimePlan( + family=const.HELPER_SHARD_FAMILY_CHORES, + mode=const.HELPER_SHARD_MODE_SHARDED, + shard_item_ids=[ + [cast("str", chore["_chore_id"]) for chore in shard_group] + for shard_group in shard_groups + ], + expected_shard_count=len(shard_groups), + last_accepted_serialized_size=_serialized_payload_size(sharded_payload), + last_reconciliation_outcome=( + f"planned_mode=sharded shard_count={len(shard_groups)}" + ), + ) + + +class AssigneeDashboardChoreShardSensor(ChoreOpsCoordinatorEntity, SensorEntity): + """Dedicated chore-only dashboard helper shard for high-density households.""" + + _attr_has_entity_name = True + _attr_translation_key = const.TRANS_KEY_SENSOR_DASHBOARD_CHORE_LIST_HELPER + + def __init__( + self, + coordinator: ChoreOpsDataCoordinator, + entry: ChoreOpsConfigEntry, + assignee_id: str, + assignee_name: str, + shard_index: int, + ) -> None: + """Initialize one chore shard helper sensor.""" + super().__init__(coordinator) + self._entry = entry + self._assignee_id = assignee_id + self._assignee_name = assignee_name + self._shard_index = shard_index + self._attr_unique_id = coordinator.ui_manager.get_chore_shard_unique_id( + assignee_id, shard_index + ) + self._attr_translation_placeholders = { + const.ATTR_SHARD_INDEX: str(shard_index), + const.TRANS_KEY_SENSOR_ATTR_ASSIGNEE_NAME: assignee_name, + } + self._attr_device_info = create_assignee_device_info_from_coordinator( + coordinator, assignee_id, assignee_name, entry + ) + + async def async_added_to_hass(self) -> None: + """Refresh main dashboard helpers after this chore list helper is registered.""" + await super().async_added_to_hass() + self.coordinator.hass.async_create_task( + self.coordinator.ui_manager.async_finalize_chore_shards_for_users( + [self._assignee_id], + registry_only=True, + ) + ) + + @property + def native_value(self) -> Any: + """Return a simple helper state for dashboard availability checks.""" + return "available" + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Return the minimal public chore shard payload.""" + plan = self.coordinator.ui_manager.get_helper_shard_plan( + self._assignee_id, + const.HELPER_SHARD_FAMILY_CHORES, + ) + if plan is None or self._shard_index > len(plan.shard_item_ids): + return _build_chore_shard_payload([], self._shard_index, 0) + + helper = AssigneeDashboardHelperSensor( + self.coordinator, + self._entry, + self._assignee_id, + self._assignee_name, + "", + ) + entity_registry = async_get(self.hass) + chores_attr = helper._build_chore_rows( + entity_registry, + set(plan.shard_item_ids[self._shard_index - 1]), + ) + return _build_chore_shard_payload( + chores_attr, + self._shard_index, + plan.expected_shard_count, + ) + + @property + def icon(self) -> str | None: + """Return None for icons.json fallback.""" + return None + + # ------------------------------------------------------------------------------------------ class SystemDashboardHelperSensor(ChoreOpsCoordinatorEntity, SensorEntity): """System-level helper sensor for shared admin dashboard state.""" @@ -4139,6 +4473,60 @@ def _get_translation_sensor_eid(self) -> str | None: # Look up entity ID from registry return self.coordinator.ui_manager.get_translation_sensor_eid(lang_code) + @staticmethod + def _sanitize_dashboard_chore_rows( + chores_attr: list[dict[str, Any]], + ) -> list[dict[str, Any]]: + """Strip internal-only metadata before exposing chore rows.""" + return [ + {key: value for key, value in chore.items() if key != "_chore_id"} + for chore in chores_attr + ] + + def _build_chore_rows( + self, + entity_registry, + included_chore_ids: set[str] | None = None, + ) -> list[dict[str, Any]]: + """Build chore rows for this helper, optionally restricted to specific IDs.""" + assignee_info: AssigneeData = cast( + "AssigneeData", self.coordinator.assignees_data.get(self._assignee_id, {}) + ) + chores_attr: list[dict[str, Any]] = [] + + for chore_id, chore_info in self.coordinator.chores_data.items(): + if included_chore_ids is not None and chore_id not in included_chore_ids: + continue + if self._assignee_id not in chore_info.get( + const.DATA_CHORE_ASSIGNED_USER_IDS, [] + ): + continue + + chore_eid = None + if entity_registry: + unique_id = ( + f"{self._entry.entry_id}_{self._assignee_id}_{chore_id}" + f"{const.SENSOR_KC_UID_SUFFIX_CHORE_STATUS_SENSOR}" + ) + chore_eid = entity_registry.async_get_entity_id( + "sensor", const.DOMAIN, unique_id + ) + + chore_attrs = self._calculate_chore_attributes( + chore_id, chore_info, assignee_info, chore_eid + ) + if chore_attrs: + chores_attr.append(chore_attrs) + + chores_attr.sort( + key=lambda chore: ( + chore.get(const.ATTR_CHORE_DUE_DATE) is None, + chore.get(const.ATTR_CHORE_DUE_DATE) or "", + chore.get(const.ATTR_EID) or "", + ) + ) + return chores_attr + def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator. @@ -4263,6 +4651,7 @@ def _calculate_chore_attributes( # Return the minimal fields needed for dashboard rendering return { + "_chore_id": chore_id, const.ATTR_EID: chore_eid, const.ATTR_NAME: chore_name, const.ATTR_STATE: state, @@ -4370,7 +4759,13 @@ def _build_core_sensors(self, entity_registry) -> dict[str, str | None]: return core_sensors - def _build_dashboard_helpers(self, entity_registry) -> dict[str, str | None]: + def _build_dashboard_helpers( + self, + entity_registry, + *, + translation_sensor_eid: str | None, + chore_helper_eids: list[str], + ) -> dict[str, Any]: """Build dashboard helper entity IDs for dashboard use. Looks up entity IDs from the registry by unique ID to ensure correct @@ -4392,7 +4787,9 @@ def _build_dashboard_helpers(self, entity_registry) -> dict[str, str | None]: # Select helper uses SUFFIX pattern: entry_id_assignee_id + SUFFIX select_unique_id = f"{self._entry.entry_id}_{self._assignee_id}{const.SELECT_KC_UID_SUFFIX_ASSIGNEE_DASHBOARD_HELPER_CHORES_SELECT}" - dashboard_helpers = {} + dashboard_helpers: dict[str, Any] = { + const.ATTR_CHORE_HELPER_EIDS: chore_helper_eids, + } # Look up datetime helper try: @@ -4412,160 +4809,24 @@ def _build_dashboard_helpers(self, entity_registry) -> dict[str, str | None]: except (KeyError, ValueError, AttributeError): dashboard_helpers["chore_select_eid"] = None - dashboard_helpers[const.ATTR_TRANSLATION_SENSOR_EID] = ( - self._get_translation_sensor_eid() - ) + dashboard_helpers[const.ATTR_TRANSLATION_SENSOR_EID] = translation_sensor_eid return dashboard_helpers - def _build_pending_approvals(self, entity_registry) -> dict: - """Build pending approvals data with button entity IDs. - - Returns: - dict: { - "chores": [ - { - "chore_id": "uuid", - "chore_name": "Take out Trash", - "timestamp": "2024-01-15T10:30:00+00:00", - "approve_button_eid": "button.kc_assignee_a_chore_1_approve", - "disapprove_button_eid": "button.kc_assignee_a_chore_1_disapprove" - } - ], - "rewards": [...] - } - """ - pending_chores = [] - pending_rewards = [] - - # Get all pending approvals from coordinator via manager query methods - pending_chore_approvals = ( - self.coordinator.chore_manager.get_pending_chore_approvals() - ) - pending_reward_approvals = ( - self.coordinator.reward_manager.get_pending_approvals() - ) - - # Filter for this assignee's pending chores - for approval in pending_chore_approvals: - if approval.get(const.DATA_USER_ID) != self._assignee_id: - continue - - chore_id = approval.get(const.DATA_CHORE_ID) - if not chore_id: - continue - chore_info: ChoreData = cast( - "ChoreData", self.coordinator.chores_data.get(chore_id, {}) - ) - chore_name = get_item_name_or_log_error( - "chore", chore_id, chore_info, const.DATA_CHORE_NAME - ) - if not chore_name: - continue - - # Build button unique IDs and lookup entity IDs - approve_uid = ( - f"{self._entry.entry_id}_{self._assignee_id}_{chore_id}" - f"{const.BUTTON_KC_UID_SUFFIX_APPROVE}" - ) - disapprove_uid = ( - f"{self._entry.entry_id}_{self._assignee_id}_{chore_id}" - f"{const.BUTTON_KC_UID_SUFFIX_DISAPPROVE}" - ) - - approve_eid = None - disapprove_eid = None - if entity_registry: - approve_eid = entity_registry.async_get_entity_id( - "button", const.DOMAIN, approve_uid - ) - disapprove_eid = entity_registry.async_get_entity_id( - "button", const.DOMAIN, disapprove_uid - ) - - pending_chores.append( - { - "chore_id": chore_id, - "chore_name": chore_name, - "timestamp": approval.get(const.DATA_CHORE_TIMESTAMP), - "approve_button_eid": approve_eid, - "disapprove_button_eid": disapprove_eid, - } - ) - - # Filter for this assignee's pending rewards - for approval in pending_reward_approvals: - if approval.get(const.DATA_USER_ID) != self._assignee_id: - continue - - reward_id = approval.get(const.DATA_REWARD_ID) - if not reward_id: - continue - reward_info: RewardData = cast( - "RewardData", self.coordinator.rewards_data.get(reward_id, {}) - ) - reward_name = get_item_name_or_log_error( - "reward", reward_id, reward_info, const.DATA_REWARD_NAME - ) - if not reward_name: - continue - - # Build button unique IDs and lookup entity IDs - approve_uid = ( - f"{self._entry.entry_id}_{self._assignee_id}_{reward_id}" - f"{const.BUTTON_KC_UID_SUFFIX_APPROVE_REWARD}" - ) - disapprove_uid = ( - f"{self._entry.entry_id}_{self._assignee_id}_{reward_id}" - f"{const.BUTTON_KC_UID_SUFFIX_DISAPPROVE_REWARD}" - ) - - approve_eid = None - disapprove_eid = None - if entity_registry: - approve_eid = entity_registry.async_get_entity_id( - "button", const.DOMAIN, approve_uid - ) - disapprove_eid = entity_registry.async_get_entity_id( - "button", const.DOMAIN, disapprove_uid - ) - - pending_rewards.append( - { - "reward_id": reward_id, - "reward_name": reward_name, - "timestamp": approval.get(const.DATA_REWARD_TIMESTAMP), - "approve_button_eid": approve_eid, - "disapprove_button_eid": disapprove_eid, - } - ) - - return {"chores": pending_chores, "rewards": pending_rewards} - - @property - def extra_state_attributes(self) -> dict: - """Return detailed aggregated structure as attributes. - - Format: - { - "chores": [ - {"eid": "sensor.assignee_a_chore_1", "name": "Take out Trash", "status": "overdue"}, - ... - ], - "rewards": [ - {"eid": "sensor.assignee_a_reward_1", "name": "Ice Cream", "cost": "10 Points"}, - ... - ], - } - """ + def _build_payload( + self, + entity_registry, + chores_attr: list[dict[str, Any]], + *, + translation_sensor_eid: str | None, + chore_helper_eids: list[str], + shard_runtime: dict[str, Any], + ) -> dict[str, Any]: + """Build the public dashboard helper payload from prepared inputs.""" assignee_info: AssigneeData = cast( "AssigneeData", self.coordinator.assignees_data.get(self._assignee_id, {}) ) - - try: - entity_registry = async_get(self.hass) - except (KeyError, ValueError, AttributeError): - entity_registry = None + hass = self.hass or self.coordinator.hass gamification_enabled = should_create_gamification_entities( self.coordinator, self._assignee_id @@ -4574,41 +4835,6 @@ def extra_state_attributes(self) -> dict: self.coordinator, self._assignee_id ) - chores_attr = [] - - for chore_id, chore_info in self.coordinator.chores_data.items(): - if self._assignee_id not in chore_info.get( - const.DATA_CHORE_ASSIGNED_USER_IDS, [] - ): - continue - - # Get the ChoreStatusSensor entity_id - chore_eid = None - if entity_registry: - unique_id = f"{self._entry.entry_id}_{self._assignee_id}_{chore_id}{const.SENSOR_KC_UID_SUFFIX_CHORE_STATUS_SENSOR}" - chore_eid = entity_registry.async_get_entity_id( - "sensor", const.DOMAIN, unique_id - ) - - # Use helper method to calculate all chore attributes - chore_attrs = self._calculate_chore_attributes( - chore_id, chore_info, assignee_info, chore_eid - ) - if chore_attrs: # Skip if name missing (data corruption) - chores_attr.append(chore_attrs) - - # Sort chores by due date (ascending, earliest first) - # Chores without due dates are placed at the end, sorted by entity_id - chores_attr.sort( - key=lambda c: ( - c.get(const.ATTR_CHORE_DUE_DATE) is None, # None values go last - c.get(const.ATTR_CHORE_DUE_DATE) - or "", # Sort by due_date (ISO format sorts correctly) - c.get(const.ATTR_EID) - or "", # Then by entity_id for chores without due dates - ) - ) - rewards_attr = [] if gamification_enabled: for reward_id, reward_info in self.coordinator.rewards_data.items(): @@ -4618,30 +4844,26 @@ def extra_state_attributes(self) -> dict: if not reward_name: continue - # Get the RewardStatusSensor entity_id reward_eid = None if entity_registry: - unique_id = f"{self._entry.entry_id}_{self._assignee_id}_{reward_id}{const.SENSOR_KC_UID_SUFFIX_REWARD_STATUS_SENSOR}" + unique_id = ( + f"{self._entry.entry_id}_{self._assignee_id}_{reward_id}" + f"{const.SENSOR_KC_UID_SUFFIX_REWARD_STATUS_SENSOR}" + ) reward_eid = entity_registry.async_get_entity_id( "sensor", const.DOMAIN, unique_id ) - # Get reward status from the sensor state reward_status = None - if reward_eid: - state_obj = self.hass.states.get(reward_eid) + if reward_eid and hass is not None: + state_obj = hass.states.get(reward_eid) if state_obj: reward_status = state_obj.state - # Get reward labels (always a list, even if empty) reward_labels = reward_info.get(const.DATA_REWARD_LABELS, []) if not isinstance(reward_labels, list): reward_labels = [] - # Get reward cost - reward_cost = reward_info.get(const.DATA_REWARD_COST, 0) - - # Get claims and approvals counts using get_period_total reward_data_entry = assignee_info.get( const.DATA_USER_REWARD_DATA, {} ).get(reward_id, {}) @@ -4668,19 +4890,16 @@ def extra_state_attributes(self) -> dict: const.ATTR_NAME: reward_name, const.ATTR_STATUS: reward_status, const.ATTR_LABELS: reward_labels, - const.ATTR_COST: reward_cost, + const.ATTR_COST: reward_info.get(const.DATA_REWARD_COST, 0), const.ATTR_CLAIMS: claims_count, const.ATTR_APPROVALS: approvals_count, } ) - # Sort rewards by name (alphabetically) - rewards_attr.sort(key=lambda r: str(r.get(const.ATTR_NAME, "")).lower()) + rewards_attr.sort( + key=lambda reward: str(reward.get(const.ATTR_NAME, "")).lower() + ) - # Badges assigned to this assignee - only build if gamification is enabled - # Badge applies if: no assignees assigned (applies to all) OR assignee is in assigned list - # Note: Cumulative badges return system-level badge sensor (no assignee-specific progress sensor) - # Other badge types return assignee-specific progress sensors badges_attr = [] if gamification_enabled: for badge_id, badge_info in self.coordinator.badges_data.items(): @@ -4694,24 +4913,22 @@ def extra_state_attributes(self) -> dict: if not badge_name: continue - # For cumulative badges, return the system-level badge sensor - # For other types, return the assignee-specific progress sensor badge_eid = None if entity_registry: if badge_type == const.BADGE_TYPE_CUMULATIVE: - # System badge sensor (no assignee_id in unique_id) - unique_id = f"{self._entry.entry_id}_{badge_id}{const.SENSOR_KC_UID_SUFFIX_BADGE_SENSOR}" - badge_eid = entity_registry.async_get_entity_id( - "sensor", const.DOMAIN, unique_id + unique_id = ( + f"{self._entry.entry_id}_{badge_id}" + f"{const.SENSOR_KC_UID_SUFFIX_BADGE_SENSOR}" ) else: - # Assignee-specific progress sensor - unique_id = f"{self._entry.entry_id}_{self._assignee_id}_{badge_id}{const.SENSOR_KC_UID_SUFFIX_BADGE_PROGRESS_SENSOR}" - badge_eid = entity_registry.async_get_entity_id( - "sensor", const.DOMAIN, unique_id + unique_id = ( + f"{self._entry.entry_id}_{self._assignee_id}_{badge_id}" + f"{const.SENSOR_KC_UID_SUFFIX_BADGE_PROGRESS_SENSOR}" ) + badge_eid = entity_registry.async_get_entity_id( + "sensor", const.DOMAIN, unique_id + ) - # Check if badge is earned (in badges_earned dict) badges_earned = assignee_info.get(const.DATA_USER_BADGES_EARNED, {}) is_earned = badge_id in badges_earned badge_earned = ( @@ -4730,41 +4947,27 @@ def extra_state_attributes(self) -> dict: period_key_mapping=period_key_mapping, ) - # Get badge status from assignee's badge progress (only for non-cumulative) - badge_status = const.SENTINEL_NONE + badge_payload: dict[str, Any] = { + const.ATTR_EID: badge_eid, + const.ATTR_NAME: badge_name, + const.ATTR_BADGE_TYPE: badge_type, + const.ATTR_BADGE_EARNED: is_earned, + const.ATTR_EARNED_COUNT: earned_count, + } if badge_type != const.BADGE_TYPE_CUMULATIVE: badge_progress = assignee_info.get( const.DATA_USER_BADGE_PROGRESS, {} ).get(badge_id, {}) - badge_status = badge_progress.get( - const.DATA_USER_BADGE_PROGRESS_STATUS, const.SENTINEL_NONE - ) - badges_attr.append( - { - const.ATTR_EID: badge_eid, - const.ATTR_NAME: badge_name, - const.ATTR_BADGE_TYPE: badge_type, - const.ATTR_STATUS: badge_status, - const.ATTR_BADGE_EARNED: is_earned, - const.ATTR_EARNED_COUNT: earned_count, - } - ) - else: - # Cumulative badge - no status - badges_attr.append( - { - const.ATTR_EID: badge_eid, - const.ATTR_NAME: badge_name, - const.ATTR_BADGE_TYPE: badge_type, - const.ATTR_BADGE_EARNED: is_earned, - const.ATTR_EARNED_COUNT: earned_count, - } + badge_payload[const.ATTR_STATUS] = badge_progress.get( + const.DATA_USER_BADGE_PROGRESS_STATUS, + const.SENTINEL_NONE, ) + badges_attr.append(badge_payload) - # Sort badges by name (alphabetically) - badges_attr.sort(key=lambda b: str(b.get(const.ATTR_NAME, "")).lower()) + badges_attr.sort( + key=lambda badge: str(badge.get(const.ATTR_NAME, "")).lower() + ) - # Bonuses for this assignee - only build if gamification is enabled bonuses_attr = [] if gamification_enabled: for bonus_id, bonus_info in self.coordinator.bonuses_data.items(): @@ -4773,43 +4976,42 @@ def extra_state_attributes(self) -> dict: ) if not bonus_name: continue - # Get ApproverBonusApplyButton entity_id + bonus_eid = None if entity_registry: - unique_id = f"{self._entry.entry_id}_{self._assignee_id}_{bonus_id}{const.BUTTON_KC_UID_SUFFIX_APPROVER_BONUS_APPLY}" + unique_id = ( + f"{self._entry.entry_id}_{self._assignee_id}_{bonus_id}" + f"{const.BUTTON_KC_UID_SUFFIX_APPROVER_BONUS_APPLY}" + ) bonus_eid = entity_registry.async_get_entity_id( "button", const.DOMAIN, unique_id ) - # Get bonus points - bonus_points = bonus_info.get(const.DATA_BONUS_POINTS, 0) - - # Get applied count for this bonus for this assignee - bonus_applies = assignee_info.get(const.DATA_USER_BONUS_APPLIES, {}) - bonus_entry = bonus_applies.get(bonus_id) + bonus_entry = assignee_info.get(const.DATA_USER_BONUS_APPLIES, {}).get( + bonus_id + ) + bonus_applied_count: int | float = 0 if bonus_entry: periods = bonus_entry.get(const.DATA_USER_BONUS_PERIODS, {}) - applied_count = self.coordinator.stats.get_period_total( + bonus_applied_count = self.coordinator.stats.get_period_total( periods, const.PERIOD_ALL_TIME, const.DATA_USER_BONUS_PERIOD_APPLIES, ) - else: - applied_count = 0 bonuses_attr.append( { const.ATTR_EID: bonus_eid, const.ATTR_NAME: bonus_name, - const.ATTR_POINTS: bonus_points, - const.ATTR_APPLIED: applied_count, + const.ATTR_POINTS: bonus_info.get(const.DATA_BONUS_POINTS, 0), + const.ATTR_APPLIED: bonus_applied_count, } ) - # Sort bonuses by name (alphabetically) - bonuses_attr.sort(key=lambda b: str(b.get(const.ATTR_NAME, "")).lower()) - # Bonuses for this assignee - # Penalties for this assignee - only build if gamification is enabled + bonuses_attr.sort( + key=lambda bonus: str(bonus.get(const.ATTR_NAME, "")).lower() + ) + penalties_attr = [] if gamification_enabled: for penalty_id, penalty_info in self.coordinator.penalties_data.items(): @@ -4818,43 +5020,44 @@ def extra_state_attributes(self) -> dict: ) if not penalty_name: continue - # Get ApproverPenaltyApplyButton entity_id + penalty_eid = None if entity_registry: - unique_id = f"{self._entry.entry_id}_{self._assignee_id}_{penalty_id}{const.BUTTON_KC_UID_SUFFIX_APPROVER_PENALTY_APPLY}" + unique_id = ( + f"{self._entry.entry_id}_{self._assignee_id}_{penalty_id}" + f"{const.BUTTON_KC_UID_SUFFIX_APPROVER_PENALTY_APPLY}" + ) penalty_eid = entity_registry.async_get_entity_id( "button", const.DOMAIN, unique_id ) - # Get penalty points (stored as positive, represents points removed) - penalty_points = penalty_info.get(const.DATA_PENALTY_POINTS, 0) - - # Get applied count for this penalty for this assignee - penalty_applies = assignee_info.get(const.DATA_USER_PENALTY_APPLIES, {}) - penalty_entry = penalty_applies.get(penalty_id) + penalty_entry = assignee_info.get( + const.DATA_USER_PENALTY_APPLIES, {} + ).get(penalty_id) + penalty_applied_count: int | float = 0 if penalty_entry: periods = penalty_entry.get(const.DATA_USER_PENALTY_PERIODS, {}) - applied_count = self.coordinator.stats.get_period_total( + penalty_applied_count = self.coordinator.stats.get_period_total( periods, const.PERIOD_ALL_TIME, const.DATA_USER_PENALTY_PERIOD_APPLIES, ) - else: - applied_count = 0 penalties_attr.append( { const.ATTR_EID: penalty_eid, const.ATTR_NAME: penalty_name, - const.ATTR_POINTS: penalty_points, - const.ATTR_APPLIED: applied_count, + const.ATTR_POINTS: penalty_info.get( + const.DATA_PENALTY_POINTS, 0 + ), + const.ATTR_APPLIED: penalty_applied_count, } ) - # Sort penalties by name (alphabetically) - penalties_attr.sort(key=lambda p: str(p.get(const.ATTR_NAME, "")).lower()) - # Penalties for this assignee - # Achievements assigned to this assignee - only build if gamification is enabled + penalties_attr.sort( + key=lambda penalty: str(penalty.get(const.ATTR_NAME, "")).lower() + ) + achievements_attr = [] if gamification_enabled: for ( @@ -4873,10 +5076,12 @@ def extra_state_attributes(self) -> dict: ) if not achievement_name: continue - # Get AssigneeAchievementProgressSensor entity_id achievement_eid = None if entity_registry: - unique_id = f"{self._entry.entry_id}_{self._assignee_id}_{achievement_id}{const.SENSOR_KC_UID_SUFFIX_ACHIEVEMENT_PROGRESS_SENSOR}" + unique_id = ( + f"{self._entry.entry_id}_{self._assignee_id}_{achievement_id}" + f"{const.SENSOR_KC_UID_SUFFIX_ACHIEVEMENT_PROGRESS_SENSOR}" + ) achievement_eid = entity_registry.async_get_entity_id( "sensor", const.DOMAIN, unique_id ) @@ -4887,10 +5092,12 @@ def extra_state_attributes(self) -> dict: } ) - # Sort achievements by name (alphabetically) - achievements_attr.sort(key=lambda a: (a.get(const.ATTR_NAME) or "").lower()) + achievements_attr.sort( + key=lambda achievement: str( + achievement.get(const.ATTR_NAME, "") + ).lower() + ) - # Challenges assigned to this assignee - only build if gamification is enabled challenges_attr = [] if gamification_enabled: for ( @@ -4906,10 +5113,12 @@ def extra_state_attributes(self) -> dict: ) if not challenge_name: continue - # Get AssigneeChallengeProgressSensor entity_id challenge_eid = None if entity_registry: - unique_id = f"{self._entry.entry_id}_{self._assignee_id}_{challenge_id}{const.SENSOR_KC_UID_SUFFIX_CHALLENGE_PROGRESS_SENSOR}" + unique_id = ( + f"{self._entry.entry_id}_{self._assignee_id}_{challenge_id}" + f"{const.SENSOR_KC_UID_SUFFIX_CHALLENGE_PROGRESS_SENSOR}" + ) challenge_eid = entity_registry.async_get_entity_id( "sensor", const.DOMAIN, unique_id ) @@ -4920,86 +5129,40 @@ def extra_state_attributes(self) -> dict: } ) - # Sort challenges by name (alphabetically) - challenges_attr.sort(key=lambda c: (c.get(const.ATTR_NAME) or "").lower()) + challenges_attr.sort( + key=lambda challenge: str(challenge.get(const.ATTR_NAME, "")).lower() + ) - # Point adjustment buttons for this assignee - only build if gamification is enabled points_buttons_attr = [] if gamification_enabled and entity_registry: from .helpers.entity_helpers import get_points_adjustment_buttons buttons = get_points_adjustment_buttons( - self.hass, self._entry.entry_id, self._assignee_id + self.coordinator.hass, self._entry.entry_id, self._assignee_id ) - # Remove delta key used internally for sorting points_buttons_attr = [ - {"eid": b["eid"], "name": b["name"]} for b in buttons + {"eid": button["eid"], "name": button["name"]} for button in buttons ] - # Get assignee's preferred dashboard language (default to English) - assignee_info_lang: AssigneeData = cast( - "AssigneeData", self.coordinator.assignees_data.get(self._assignee_id, {}) - ) - dashboard_language = assignee_info_lang.get( + dashboard_language = assignee_info.get( const.DATA_USER_DASHBOARD_LANGUAGE, const.DEFAULT_DASHBOARD_LANGUAGE ) - - # Build chores_by_label dictionary - # Group chores by label, with entity IDs sorted by due date - chores_by_label: dict[str, Any] = {} - for chore in chores_attr: - labels = chore.get(const.ATTR_CHORE_LABELS, []) - chore_eid = chore.get(const.ATTR_EID) - - # Skip chores without entity IDs - if not chore_eid: - continue - - # Add this chore to each label group it belongs to - for label in labels: - if label not in chores_by_label: - chores_by_label[label] = [] - chores_by_label[label].append(chore) - - # Sort chores within each label by due date (ascending, earliest first) - # Chores without due dates are placed at the end, sorted by entity_id - for label, chore_list in chores_by_label.items(): - chore_list.sort( - key=lambda c: ( - c.get(const.ATTR_CHORE_DUE_DATE) is None, # None values go last - c.get(const.ATTR_CHORE_DUE_DATE) - or "", # Sort by due_date (ISO format sorts correctly) - c.get(const.ATTR_EID) - or "", # Then by entity_id for chores without due dates - ) - ) - # Convert to list of entity IDs only - chores_by_label[label] = [c[const.ATTR_EID] for c in chore_list] - - # Sort labels alphabetically for consistent ordering - chores_by_label = dict(sorted(chores_by_label.items())) - - # Build pending approvals data if flags indicate changes pending_approvals = self._build_pending_approvals(entity_registry) - - # Reset change flags after building attributes self.coordinator.ui_manager.reset_pending_change_flags() - # Build core sensors dict (used by dashboard to avoid slug construction) core_sensors = self._build_core_sensors(entity_registry) - - # Build dashboard helpers dict (used by dashboard to avoid slug construction) - dashboard_helpers = self._build_dashboard_helpers(entity_registry) - - # Build resolved UI control payload for reviewed dashboard consumers only + dashboard_helpers = self._build_dashboard_helpers( + entity_registry, + translation_sensor_eid=translation_sensor_eid, + chore_helper_eids=chore_helper_eids, + ) ui_control = self.coordinator.ui_manager.get_dashboard_ui_control( self._assignee_id ) return { const.ATTR_PURPOSE: const.TRANS_KEY_PURPOSE_DASHBOARD_HELPER, - "chores": chores_attr, - const.ATTR_CHORES_BY_LABEL: chores_by_label, + "chores": self._sanitize_dashboard_chore_rows(chores_attr), "rewards": rewards_attr, const.ATTR_UI_CONTROL: ui_control, "badges": badges_attr, @@ -5020,8 +5183,201 @@ def extra_state_attributes(self) -> dict: "language": dashboard_language, "gamification_enabled": gamification_enabled, "chore_workflow_enabled": chore_workflow_enabled, + const.ATTR_SHARD_RUNTIME: shard_runtime, } + def _build_pending_approvals(self, entity_registry) -> dict: + """Build pending approvals data with button entity IDs. + + Returns: + dict: { + "chores": [ + { + "chore_id": "uuid", + "chore_name": "Take out Trash", + "timestamp": "2024-01-15T10:30:00+00:00", + "approve_button_eid": "button.kc_assignee_a_chore_1_approve", + "disapprove_button_eid": "button.kc_assignee_a_chore_1_disapprove" + } + ], + "rewards": [...] + } + """ + pending_chores = [] + pending_rewards = [] + + # Get all pending approvals from coordinator via manager query methods + pending_chore_approvals = ( + self.coordinator.chore_manager.get_pending_chore_approvals() + ) + pending_reward_approvals = ( + self.coordinator.reward_manager.get_pending_approvals() + ) + + # Filter for this assignee's pending chores + for approval in pending_chore_approvals: + if approval.get(const.DATA_USER_ID) != self._assignee_id: + continue + + chore_id = approval.get(const.DATA_CHORE_ID) + if not chore_id: + continue + chore_info: ChoreData = cast( + "ChoreData", self.coordinator.chores_data.get(chore_id, {}) + ) + chore_name = get_item_name_or_log_error( + "chore", chore_id, chore_info, const.DATA_CHORE_NAME + ) + if not chore_name: + continue + + # Build button unique IDs and lookup entity IDs + approve_uid = ( + f"{self._entry.entry_id}_{self._assignee_id}_{chore_id}" + f"{const.BUTTON_KC_UID_SUFFIX_APPROVE}" + ) + disapprove_uid = ( + f"{self._entry.entry_id}_{self._assignee_id}_{chore_id}" + f"{const.BUTTON_KC_UID_SUFFIX_DISAPPROVE}" + ) + + approve_eid = None + disapprove_eid = None + if entity_registry: + approve_eid = entity_registry.async_get_entity_id( + "button", const.DOMAIN, approve_uid + ) + disapprove_eid = entity_registry.async_get_entity_id( + "button", const.DOMAIN, disapprove_uid + ) + + pending_chores.append( + { + "chore_id": chore_id, + "chore_name": chore_name, + "timestamp": approval.get(const.DATA_CHORE_TIMESTAMP), + "approve_button_eid": approve_eid, + "disapprove_button_eid": disapprove_eid, + } + ) + + # Filter for this assignee's pending rewards + for approval in pending_reward_approvals: + if approval.get(const.DATA_USER_ID) != self._assignee_id: + continue + + reward_id = approval.get(const.DATA_REWARD_ID) + if not reward_id: + continue + reward_info: RewardData = cast( + "RewardData", self.coordinator.rewards_data.get(reward_id, {}) + ) + reward_name = get_item_name_or_log_error( + "reward", reward_id, reward_info, const.DATA_REWARD_NAME + ) + if not reward_name: + continue + + # Build button unique IDs and lookup entity IDs + approve_uid = ( + f"{self._entry.entry_id}_{self._assignee_id}_{reward_id}" + f"{const.BUTTON_KC_UID_SUFFIX_APPROVE_REWARD}" + ) + disapprove_uid = ( + f"{self._entry.entry_id}_{self._assignee_id}_{reward_id}" + f"{const.BUTTON_KC_UID_SUFFIX_DISAPPROVE_REWARD}" + ) + + approve_eid = None + disapprove_eid = None + if entity_registry: + approve_eid = entity_registry.async_get_entity_id( + "button", const.DOMAIN, approve_uid + ) + disapprove_eid = entity_registry.async_get_entity_id( + "button", const.DOMAIN, disapprove_uid + ) + + pending_rewards.append( + { + "reward_id": reward_id, + "reward_name": reward_name, + "timestamp": approval.get(const.DATA_REWARD_TIMESTAMP), + "approve_button_eid": approve_eid, + "disapprove_button_eid": disapprove_eid, + } + ) + + return {"chores": pending_chores, "rewards": pending_rewards} + + @property + def extra_state_attributes(self) -> dict: + """Return detailed aggregated structure as attributes. + + Format: + { + "chores": [ + {"eid": "sensor.assignee_a_chore_1", "name": "Take out Trash", "status": "overdue"}, + ... + ], + "rewards": [ + {"eid": "sensor.assignee_a_reward_1", "name": "Ice Cream", "cost": "10 Points"}, + ... + ], + } + """ + try: + entity_registry = async_get(self.hass) + except (KeyError, ValueError, AttributeError): + entity_registry = None + chores_attr = self._build_chore_rows(entity_registry) + + plan = self.coordinator.ui_manager.get_helper_shard_plan( + self._assignee_id, + const.HELPER_SHARD_FAMILY_CHORES, + ) + if plan is None: + plan = build_chore_shard_plan( + self.hass, + self.coordinator, + self._entry, + self._assignee_id, + self._assignee_name, + previous_plan=None, + ) + self.coordinator.ui_manager.set_helper_shard_plan( + self._assignee_id, + const.HELPER_SHARD_FAMILY_CHORES, + plan, + ) + + visible_chores = chores_attr + chore_helper_eids: list[str] = [] + if plan.mode == const.HELPER_SHARD_MODE_SHARDED: + visible_chores = [] + chore_helper_eids = self.coordinator.ui_manager.get_chore_shard_helper_eids( + self._assignee_id + ) + + payload = self._build_payload( + entity_registry, + visible_chores, + translation_sensor_eid=self._get_translation_sensor_eid(), + chore_helper_eids=chore_helper_eids, + shard_runtime={ + "family": const.HELPER_SHARD_FAMILY_CHORES, + "mode": plan.mode, + "expected_shard_count": plan.expected_shard_count, + "last_accepted_serialized_size": plan.last_accepted_serialized_size, + "last_reconciliation_outcome": plan.last_reconciliation_outcome, + }, + ) + plan.last_accepted_serialized_size = _serialized_payload_size(payload) + payload[const.ATTR_SHARD_RUNTIME]["last_accepted_serialized_size"] = ( + plan.last_accepted_serialized_size + ) + return payload + @property def icon(self) -> str | None: """Return None for icons.json fallback.""" diff --git a/custom_components/choreops/services.py b/custom_components/choreops/services.py index 5456e37..5e1afe4 100644 --- a/custom_components/choreops/services.py +++ b/custom_components/choreops/services.py @@ -5,6 +5,7 @@ Includes UI editor support with selectors for dropdowns and text inputs. """ +from copy import deepcopy from datetime import datetime from typing import TYPE_CHECKING, Any, cast @@ -1111,13 +1112,12 @@ async def handle_create_chore(call: ServiceCall) -> dict[str, Any]: internal_id, due_date_input, assignee_id=None ) - # Create chore status sensor entities for all assigned assignees - if coordinator._test_mode: - from .sensor import create_chore_entities - - create_chore_entities(coordinator, internal_id) - - await coordinator.async_sync_entities_after_service_create() + sync_context = coordinator.chore_manager.build_entity_sync_context( + internal_id, + mutation="created", + current_chore=deepcopy(coordinator.chores_data[internal_id]), + ) + await coordinator.async_sync_chore_entities(sync_context) const.LOGGER.info( "Service created chore '%s' with ID: %s", @@ -1205,6 +1205,7 @@ async def handle_update_chore(call: ServiceCall) -> dict[str, Any]: ) existing_chore = coordinator.chores_data[chore_id] + previous_chore = deepcopy(existing_chore) # Build data input, excluding name if it was used for lookup service_data = dict(call.data) @@ -1260,9 +1261,6 @@ async def handle_update_chore(call: ServiceCall) -> dict[str, Any]: existing_chore.get(const.DATA_CHORE_ASSIGNED_USER_IDS, []), ) ) - assignments_changed = const.DATA_CHORE_ASSIGNED_USER_IDS in data_input and set( - assigned_assignee_ids - ) != set(existing_chore.get(const.DATA_CHORE_ASSIGNED_USER_IDS, [])) validation_data = _build_service_chore_validation_data( data_input, @@ -1297,15 +1295,20 @@ async def handle_update_chore(call: ServiceCall) -> dict[str, Any]: chore_id, due_date_input, assignee_id=None ) + sync_context = coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation="updated", + previous_chore=previous_chore, + current_chore=deepcopy(coordinator.chores_data[chore_id]), + ) + await coordinator.async_sync_chore_entities(sync_context) + const.LOGGER.info( "Service updated chore '%s' with ID: %s", chore_dict[const.DATA_CHORE_NAME], chore_id, ) - if assignments_changed: - await coordinator.async_sync_entities_after_service_create() - return {const.SERVICE_FIELD_CHORE_CRUD_ID: chore_id} except EntityValidationError as err: @@ -1373,6 +1376,11 @@ async def handle_delete_chore(call: ServiceCall) -> dict[str, Any]: # Use Manager-owned CRUD method (handles cleanup and persistence) coordinator.chore_manager.delete_chore(chore_id) + sync_context = coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation="deleted", + ) + await coordinator.async_sync_chore_entities(sync_context) const.LOGGER.info("Service deleted chore with ID: %s", chore_id) diff --git a/custom_components/choreops/translations/en.json b/custom_components/choreops/translations/en.json index e8d64f7..fcf00ad 100644 --- a/custom_components/choreops/translations/en.json +++ b/custom_components/choreops/translations/en.json @@ -4743,6 +4743,9 @@ "dashboard_helpers": { "name": "Dashboard Helpers" }, + "shard_runtime": { + "name": "Chore List Runtime" + }, "chores_by_label": { "name": "Chores by Label" }, @@ -4753,6 +4756,29 @@ "name": "Language" } } + }, + "assignee_dashboard_chore_list_helper_sensor": { + "name": "UI Dashboard Chore List {shard_index}", + "state_attributes": { + "purpose": { + "name": "Purpose", + "state": { + "purpose_dashboard_chore_shard_helper": "Provides one chore list for dashboard display when a user's full chore list is too large for the main helper" + } + }, + "chores": { + "name": "Chores" + }, + "shard_index": { + "name": "Chore List Number" + }, + "shard_count": { + "name": "Total Chore Lists" + }, + "helper_contract_version": { + "name": "Helper Contract Version" + } + } } }, "select": { diff --git a/custom_components/choreops/type_defs.py b/custom_components/choreops/type_defs.py index 55ad67e..8bfda91 100644 --- a/custom_components/choreops/type_defs.py +++ b/custom_components/choreops/type_defs.py @@ -1300,6 +1300,22 @@ class ChoreUpdatedEvent(TypedDict, total=False): user_id: str | None # Optional: affected assignee when update is user-scoped +class ChoreEntitySyncContext(TypedDict): + """Runtime entity-sync contract for chore graph mutations. + + Used by the coordinator/runtime entity layer to distinguish add/remove/ + replace work from ordinary coordinator listener refreshes. + """ + + chore_id: str + mutation: Literal["created", "updated", "deleted"] + assignments_added: list[str] + assignments_removed: list[str] + affected_user_ids: list[str] + rename_sensitive_update: bool + shared_state_changed: bool + + class PenaltyAppliedEvent(TypedDict, total=False): """Event payload for SIGNAL_SUFFIX_PENALTY_APPLIED. diff --git a/docs/DASHBOARD_TEMPLATE_GUIDE.md b/docs/DASHBOARD_TEMPLATE_GUIDE.md index d0e14c4..f04cb7d 100644 --- a/docs/DASHBOARD_TEMPLATE_GUIDE.md +++ b/docs/DASHBOARD_TEMPLATE_GUIDE.md @@ -781,6 +781,74 @@ Authoring rule: The dashboard helper resolved via `dashboard_lookup_key = :` provides enriched chore data with these attributes: +#### Current helper field classes (v1 contract baseline) + +Treat the current dashboard-helper surface as four field classes when evaluating +sharding behavior: + +| Class | Current fields | Notes | +| ----- | -------------- | ----- | +| `row data` | `chores`, `rewards`, `badges`, `bonuses`, `penalties`, `achievements`, `challenges`, `points_buttons` | Lists consumed directly by dashboard cards | +| `derived indexes` | `chores_by_label` | Transitional backend convenience index scheduled for removal from transport | +| `auxiliary catalogs` | `pending_approvals`, `core_sensors`, `ui_control` | Supporting payloads that stay on the main helper in the first shard release | +| `plumbing/meta` | `dashboard_helpers`, `user_name`, `user_id`, `integration_entry_id`, `dashboard_lookup_key`, `language`, `gamification_enabled` | Lookup, identity, and helper-pointer fields | + +Authoring rule: + +- Treat `chores` as the canonical chore transport surface. +- Do not build new template dependencies on `chores_by_label`; it is being removed + from backend transport for the shard contract. + +#### Shared snippet lookup and caching contract + +Maintained chore dashboards are expected to follow this sequence once per card: + +1. Resolve `dashboard_helper` from `dashboard_lookup_key`. +2. Resolve `dashboard_helpers` from the main helper. +3. Read `dashboard_helpers.chore_helper_eids` once and default it to `[]`. +4. Read all referenced shard helpers once and merge their `chores` rows into one + in-memory `chore_list`. +5. Perform grouping, filtering, sorting, and label reconstruction from the merged + in-memory list instead of calling `state_attr()` repeatedly inside loops. + +Authoring rules: + +- `dashboard_helpers.chore_helper_eids` is always present and always a list. +- Below the shard threshold, use the inline `chores` list from the main helper and + expect `dashboard_helpers.chore_helper_eids == []`. +- Above the shard threshold, expect shard-backed `chores` transport and do not rely + on a parallel inline compatibility slice. +- Resolve shard helper payloads once per card and cache the merged result in local + Jinja namespace variables. +- Do not impose a template-side cap on resolved shard helpers in v1; consume the + full ordered pointer list exactly once per card. + +Recommended dynamic pattern: + +```jinja +{%- set dashboard_helper = config.entity -%} +{%- set dashboard_helpers = state_attr(dashboard_helper, 'dashboard_helpers') or {} -%} +{%- set ns = namespace(chore_list=(state_attr(dashboard_helper, 'chores') or [])) -%} +{%- for chore_helper in dashboard_helpers.get('chore_helper_eids', []) -%} + {%- set ns.chore_list = ns.chore_list + (state_attr(chore_helper, 'chores') or []) -%} +{%- endfor -%} +``` + +Use `ns.chore_list` as the only chore source for filtering, grouping, and rendering. +This works in both inline mode and shard mode because the shard pointer list is +empty when no extra helpers are needed. + +Limited legacy inline-only pattern: + +```jinja +{%- set chores = state_attr(config.entity, 'chores') or [] -%} +``` + +This shorter pattern still works for small households and quick one-off templates, +but it is not shard-aware. Once a user reaches roughly 40 assigned chores +(depending on row size and other helper payload fields), the helper may switch to +shard-backed transport and the inline-only pattern can miss rows. + #### Core Chore Fields (v0.4.x) | Field | Type | Description | Example | @@ -814,6 +882,128 @@ Note: Some runtime payload keys still use legacy names for backward compatibilit | `turn_user_name` | string\|null | Current turn holder user name | `"Bob"`, `null` | | `available_at` | string\|null | ISO timestamp when chore becomes available (waiting state) | `"2026-02-10T17:30:00Z"`, `null` | +#### Shard helper contract (v1) + +When shard mode activates, the main helper remains the canonical dashboard entry +point and exposes shard pointers at `dashboard_helpers.chore_helper_eids`. + +Main-helper contract additions: + +| Field | Type | Meaning | +| ----- | ---- | ------- | +| `dashboard_helpers.chore_helper_eids` | list[string] | Ordered shard helper entity IDs; always present, `[]` when no shards exist | + +Shard-helper payload contract: + +| Field | Type | Meaning | +| ----- | ---- | ------- | +| `purpose` | string | Typed purpose marker for chore shard helpers | +| `chores` | list[dict] | Chore rows for this shard only | +| `shard_index` | int | 1-based shard position | +| `shard_count` | int | Total shard count for the user | +| `helper_contract_version` | int | Shard contract version for template compatibility | + +Lifecycle and mode contract: + +- Shard mode is runtime-owned by `ui_manager`. +- The transport mode is mutually exclusive: either inline `chores` on the main + helper or shard-backed `chores`, not both in parallel for the same user state. +- Shard mode enters at or above 14 KB of accepted serialized helper size and exits + only at or below 12 KB. +- Dashboards consume shard helpers only through + `dashboard_helpers.chore_helper_eids`. + +Naming contract: + +- Backend unique IDs are deterministic, typed chore-shard identifiers with an + explicit shard index. +- Human-facing names follow the equivalent of `Chore List 1`, `Chore List 2`, and + so on. + +Diagnostics contract: + +- Keep shard-helper diagnostics minimal: `purpose`, `shard_index`, `shard_count`, + and `helper_contract_version`. +- Keep current mode, resolved shard helper entity IDs, last accepted serialized + size, and last reconciliation outcome on the main helper runtime diagnostic + surface. + +#### Label reconstruction contract + +`chores_by_label` is removed from backend transport in the shard contract. +Templates must rebuild label groups from the merged in-memory `chore_list`. + +Authoring rules: + +- Reconstruct label groups after the full merged `chore_list` is available. +- Apply label ordering and exclusions to reconstructed groups, not to backend index + data. +- Treat label reconstruction as a shared-snippet responsibility for maintained + chore dashboards. + +#### Legacy and partial-failure handling + +Maintained shard-aware snippets should treat missing or unavailable chore shard +helpers as contributing zero rows to the merged `chore_list` and continue +rendering the data that is available. + +Authoring rules: + +- Inspect `shard_runtime` on the main helper for diagnostics only; do not require + per-shard recovery logic in dashboard cards. +- Legacy dashboards that read only `state_attr(dashboard_helper, 'chores')` + continue to work for inline mode, but they do not automatically merge shard + helpers after threshold-triggered sharding activates. +- Maintained shared snippets are the only supported automatic merge path for + shard-backed chore transport. + +#### Compatibility matrix + +| Template/profile | Shard-aware shared snippet path required | Extended-scale target | +| ---------------- | ---------------------------------------- | --------------------- | +| `user-gamification-premier-v1` | Yes | Yes | +| `user-chores-standard-v1` | Yes | Yes | +| `user-chores-essential-v1` | Yes, for compatibility with `chores_by_label` removal | No | + +Compatibility rules: + +- `user-gamification-premier-v1` and `user-chores-standard-v1` are maintained + scale targets and must support the shard contract. +- `user-chores-essential-v1` must remain functionally compatible with the removal + of `chores_by_label`, but it is not an extended-scale acceptance target. +- Legacy dashboards that read `state_attr(dashboard_helper, 'chores')` directly do + not receive automatic shard merging and should be treated as fallback/legacy + consumers. + +#### Worked examples + +Small household example: + +- User with 20 chores: main helper carries inline `chores`; `dashboard_helpers` + includes `chore_helper_eids: []`. + +Large household example: + +- User with 120 chores: main helper remains the canonical lookup surface; + `dashboard_helpers.chore_helper_eids` contains ordered chore shard helper entity + IDs and maintained templates merge shard `chores` rows into one local + `chore_list` before rendering. + +Practical migration guidance: + +- If you want a template to stay dynamic as households grow, always merge chore + rows from `dashboard_helpers.chore_helper_eids` into one local `chore_list` + before rendering. +- If you intentionally want the shortest possible snippet for a smaller household, + the inline-only `state_attr(dashboard_helper, 'chores')` pattern is still valid, + but treat it as a limited convenience pattern rather than the long-term default. + +#### Future typed-family example + +The reusable shard pattern is documented for chores now and rewards next. +Future payload families should reuse the same lifecycle and pointer pattern while +keeping public helper surfaces typed by family. + ### Dashboard helper UI control fields The dashboard helper also exposes the persisted per-user UI control payload for @@ -926,7 +1116,12 @@ icon_color: | options: type: custom:mushroom-template-card primary: | - {%- for chore in state_attr(config.entity, 'chores') -%} + {%- set dashboard_helpers = state_attr(config.entity, 'dashboard_helpers') or {} -%} + {%- set ns = namespace(chore_list=(state_attr(config.entity, 'chores') or [])) -%} + {%- for chore_helper in dashboard_helpers.get('chore_helper_eids', []) -%} + {%- set ns.chore_list = ns.chore_list + (state_attr(chore_helper, 'chores') or []) -%} + {%- endfor -%} + {%- for chore in ns.chore_list -%} {%- set turn_user_name = chore.turn_user_name -%} {%- if turn_user_name -%} {{ chore.name }}: @@ -940,6 +1135,11 @@ icon_color: | {%- endfor -%} ``` +For a very small household, you can shorten the example back to a direct +`state_attr(config.entity, 'chores')` loop, but that should be treated as the +small-household shortcut only. The merged `ns.chore_list` pattern is the supported +dynamic version. + --- ## Adding a new template variant diff --git a/docs/in-process/CHORE_ACTIVE_BACKEND_IN-PROCESS.md b/docs/completed/CHORE_ACTIVE_BACKEND_IN-PROCESS.md similarity index 100% rename from docs/in-process/CHORE_ACTIVE_BACKEND_IN-PROCESS.md rename to docs/completed/CHORE_ACTIVE_BACKEND_IN-PROCESS.md diff --git a/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md new file mode 100644 index 0000000..f4ce0a4 --- /dev/null +++ b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md @@ -0,0 +1,284 @@ +# Initiative snapshot + +- **Name / Code**: Dynamic chore entity lifecycle without integration reload / `CHORE_DYNAMIC_ENTITY_LIFECYCLE` +- **Target release / milestone**: TBD after scope review; candidate for next post-1.0.x backend UX release +- **Owner / driver(s)**: TBD +- **Status**: Completed and ready for archive under `docs/completed` + +## Summary & immediate steps + +> **Critical implementation note:** Do not preserve or introduce fallback paths, compatibility wrappers, or temporary dual-behavior shims unless they are explicitly justified, narrowly scoped, and recorded in the active phase notes with a concrete reason they are still required. For this initiative, wrapper-heavy implementation is treated as a potential sign that the runtime sync contract was not fully solved at the root. + +| Phase / Step | Description | % complete | Quick notes | +| --- | --- | --- | --- | +| Phase 1 – Runtime contract and scope lock | Freeze invariants, mutation classes, and scope boundaries before code changes | 100% | Reload boundary, mutation classes, non-goals, and rename policy are now locked | +| Phase 2 – Dynamic entity orchestration | Add chore-scoped runtime add/remove/sync support for graph-mutating entity families | 100% | Runtime sync contract, production-safe sensor/button creation, and shared-sensor cleanup are in place | +| Phase 3 – Flow and service adoption | Move chore service and options-flow CRUD to targeted sync without touching settings reload paths | 100% | Chore services and chore options-flow CRUD now converge on the shared runtime sync contract | +| Phase 3B – Sparse edit preservation remediation | Stop and resolve partial-section options-flow regressions before any additional rollout work | 100% | Sparse edit regressions added, root cause fixed at the edit schema boundary, and validation is green | +| Phase 4 – Validation and rollout | Prove dashboard continuity, registry correctness, and backward-safe behavior | 100% | Targeted rollout validation is complete, docs are updated, and the remaining reload boundary is explicitly limited to sanctioned non-chore paths | + +1. **Key objective** – Eliminate full integration reloads for chore create, update, and delete so the active dashboard page keeps working while chore-backed entities and helper surfaces update live. +2. **Summary of recent work** – + - Confirmed chore CRUD in `ChoreManager` already follows modify → persist → `async_update_listeners()` → emit signal. + - Confirmed reload is still explicitly used for production entity graph refresh in `coordinator.py`, service handlers, and options flow. + - Confirmed some surfaces already behave dynamically: calendar cache invalidation on chore signals, selector options derived from coordinator state, and manager-driven orphan cleanup. + - Locked Phase 1 decisions: the reload boundary in `coordinator.py` is a temporary production fallback, mutation classes are fixed for v1, button runtime creation is mandatory, and no compatibility wrappers should be used unless explicitly justified. + - Implemented Phase 2 orchestration: typed chore entity sync context, coordinator runtime sync entry point, production-safe chore sensor/button registration, and correct shared/shared-first orphan cleanup. + - Implemented Phase 3 caller adoption: chore services and chore options-flow CRUD now use the shared runtime sync contract, while helper-only options-flow substeps no longer rely on deferred reload. +3. **Next steps (short term)** – + - Prepare PR/release metadata using the summary below: chore create, edit, and delete now update runtime sensors, buttons, and dashboard helper payloads without a full integration reload; sanctioned system-settings edits still reload. + - Keep the Phase 3B edit-boundary safeguard scoped to chore edit forms unless a separate audit proves other domains need the same pattern. + - Preserve the current shared-service/data-builder contracts; do not broaden the fix beyond edit-form schema behavior without a new defect signal. +4. **Risks / blockers** – + - Button entities are setup-time only today, so sensor-only dynamic creation is insufficient. + - Assignment changes and completion-criteria transitions change the entity graph, not just entity state, so update paths need add and remove orchestration. + - Entity names and helper attributes cache constructor-time values in several platforms, which can leave rename flows partially stale without targeted refresh logic or explicit replacement. + - Dashboard helper payloads derive from both coordinator state and registry lookups, so entity creation ordering matters. + - Options flow currently assumes entity CRUD requires deferred reload after the user returns to the main menu, and that assumption is shared with non-chore domains. + - Tests and test utilities already encode reload-era assumptions, so correct runtime behavior may fail old tests until the assertions are updated deliberately. + - `tests/test_options_flow_per_kid_helper.py::TestSchemaEdgeCases::test_esv06_edit_partial_section_payload_preserves_schedule_fields` fails in isolation and points to a likely backend gap where omitted optional edit fields are reset to defaults instead of preserving stored values. + - Direct transform and builder calls preserve the same fields correctly, which suggests the defect sits in the options-flow form/schema boundary rather than in manager merge logic alone. +5. **References** – + - [docs/ARCHITECTURE.md](../ARCHITECTURE.md) + - [docs/DEVELOPMENT_STANDARDS.md](../DEVELOPMENT_STANDARDS.md) + - [docs/CODE_REVIEW_GUIDE.md](../CODE_REVIEW_GUIDE.md) + - [docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_TRAPS_AND_OPPORTUNITIES.md](./CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_TRAPS_AND_OPPORTUNITIES.md) + - [tests/AGENT_TEST_CREATION_INSTRUCTIONS.md](../../tests/AGENT_TEST_CREATION_INSTRUCTIONS.md) + - [docs/RELEASE_CHECKLIST.md](../RELEASE_CHECKLIST.md) +6. **Decisions & completion check** + - **Decisions captured**: + - Keep system-settings reload behavior intact; this initiative only targets chore domain item CRUD. + - Preserve signal-first manager communication; do not reintroduce cross-manager orchestration or direct platform writes from services. + - Prefer targeted entity add/remove plus coordinator listener updates over full entry reload. + - Treat runtime entity graph mutation as a separate problem from state refresh; `async_update_listeners()` alone is not sufficient. + - Treat options flow as a second adopter of the runtime sync contract, not as the place where the contract is invented. + - Treat ESV06 as a real defect signal, not as a disposable test artifact. No further implementation should continue until sparse-edit preservation is characterized and covered by explicit regression tests. + - No `.storage/choreops/choreops_data` schema bump is expected unless execution later requires persisted entity-sync metadata. + - **Completion confirmation**: `[x]` All follow-up items completed (architecture updates, cleanup, documentation, etc.) before requesting owner approval to mark initiative done. + +> **Important:** Keep the entire Summary section (table + bullets) current with every meaningful update (after commits, tickets, or blockers change). Records should stay concise, fact-based, and readable so anyone can instantly absorb where each phase stands. This summary is the only place readers should look for the high-level snapshot. + +> **Phase update style:** Keep phase updates short and operational. For each update, briefly state what changed, any gap or deviation from plan, and the next direct action. Favor implementation progress and decision clarity over process-heavy narration. + +## Tracking expectations + +- **Summary upkeep**: Whoever works on the initiative must refresh the Summary section after each significant change, including updated percentages per phase, new blockers, or completed steps. Mention dates or commit references if helpful. +- **Detailed tracking**: Use the phase-specific sections below for granular progress, issues, decision notes, and action items. Do not merge those details into the Summary table—Summary remains high level. + +## Detailed phase tracking + +### Phase 1 – Runtime contract and scope lock + +- **Goal**: Define the runtime lifecycle contract for chore CRUD so implementation work can remove reloads without broadening scope into unrelated config-entry behavior. +- **Steps / detailed work items** + 1. [x] Document the current reload boundary in `custom_components/choreops/coordinator.py` around line 377 and classify it as a temporary production fallback to be replaced by chore-specific runtime sync, not by `async_request_refresh()` and not by a broad settings reload substitute. + 2. [x] Audit chore CRUD callers in `custom_components/choreops/services.py` around lines 1100-1325 and `custom_components/choreops/options_flow.py` around lines 1020-2575 plus 6110-6175 to distinguish pure state updates from entity-graph mutations. + 3. [x] Freeze the runtime mutation classes to support in v1: chore created, chore deleted, assignment expansion, assignment shrinkage, criteria transition, and rename-sensitive update. + 4. [x] Enumerate chore-backed entity families and owners in the plan or follow-up design note: `AssigneeChoreStatusSensor`, `SystemChoreSharedStateSensor`, chore claim/approve/disapprove buttons, dashboard helper payloads, selectors, and calendar views. Mark each family as one of `dynamic create required`, `dynamic remove required`, or `listener-only verification`. + 5. [x] Lock the non-goals for v1: do not change system settings reload behavior in `__init__.py` / `config_flow.py`, do not redesign dashboard YAML, do not generalize into a multi-domain sync framework, and do not introduce a schema version bump unless a persisted contract genuinely changes. + 6. [x] Decide whether runtime entity synchronization lives in a new helper/module or remains platform-local via callback registries in `sensor.py` and `button.py`. + 7. [x] Record explicit rename policy per entity family: update in place, replace entity, or defer with documented limitation. This decision must be made before coding because cached constructor names exist in `button.py` and `sensor.py`. +- **Phase 1 completion update (2026-04-12)** + - Accomplished: classified the reload boundary in `coordinator.py` as the production fallback to remove; confirmed service create and assignment-changing update paths are current graph-mutation callers; confirmed options flow still uses a shared deferred reload boundary for chore CRUD. + - Accomplished: locked v1 mutation classes to `created`, `deleted`, `assignments_added`, `assignments_removed`, `shared_state_changed`, and `rename_sensitive_update`. + - Accomplished: froze entity-family handling for v1: + - `AssigneeChoreStatusSensor`: dynamic create required, dynamic remove required, rename-sensitive. + - `SystemChoreSharedStateSensor`: dynamic create required, dynamic remove required. + - Chore claim/approve/disapprove buttons: dynamic create required, dynamic remove required, rename-sensitive. + - Dashboard helper payloads, selectors, calendars: listener-driven verification required; do not solve with wrapper refresh paths. + - Decision: runtime sync will use one chore-scoped entry point coordinated from `coordinator.py`, with platform-local add/remove registration in `sensor.py` and `button.py` rather than independent caller-specific logic or a broad compatibility layer. + - Decision: rename policy is strict. Update in place where attributes derive from coordinator state at read time; otherwise replace affected entities directly. Do not keep reload or compatibility wrappers just to hide constructor-cached naming gaps. + - Validation: `./utils/quick_lint.sh --fix` passed locally, including MyPy and boundary checks. Full test validation for this workstream was already completed successfully before this phase report and should not be rerun unless explicitly requested. + - Gap/deviation: none for Phase 1. The phase remained documentation-and-contract work only. + - Next action: begin Phase 2 by introducing the chore-scoped runtime sync entry point and production-safe button/sensor registration. +- **Key issues** + - `async_update_listeners()` already refreshes existing coordinator entities, so the missing capability is graph mutation, not data propagation. + - Some surfaces already update live by signal invalidation, which means the plan should avoid re-solving problems that are already handled by calendar/select/helper read paths. + - Test-mode sensor creation is currently stronger than production behavior. Any design that leaves that split intact is incomplete. +- **Phase exit checkpoint** + - Fallback path / wrapper inventory: `none` left or added in Phase 1. The phase locked a no-wrapper bias for implementation and rejected reload-preserving compatibility shims as a default strategy. + +### Phase 2 – Dynamic entity orchestration + +- **Goal**: Add targeted runtime add/remove behavior for chore-backed entities so create, assignment changes, and completion-criteria changes no longer require full integration reload. +- **Steps / detailed work items** + 1. [x] Extend the callback-registration pattern in `custom_components/choreops/sensor.py` around lines 553-710 from test-only chore sensor creation into a production-safe runtime path that can add all required chore sensors, including `SystemChoreSharedStateSensor` when completion criteria are shared. + 2. [x] Add an equivalent runtime callback registration path in `custom_components/choreops/button.py` near the initial setup block around lines 44-240 so chore claim, approve, and disapprove buttons can be created for new assignee/chore pairs without reload. + 3. [x] Introduce a focused entity-sync entry point, likely coordinated from `custom_components/choreops/coordinator.py`, that accepts a chore ID plus mutation context and performs add/remove/update work rather than calling `hass.config_entries.async_reload(...)`. + 4. [x] Represent graph mutations explicitly in the sync contract. At minimum, the implementation must distinguish `created`, `deleted`, `assignments_added`, `assignments_removed`, and `shared_state_changed` so add and remove work is deterministic. + 5. [x] Update `custom_components/choreops/managers/chore_manager.py` around lines 3160-3365 so create/update/delete paths continue to own storage mutation and cleanup, but expose enough mutation context for the new entity-sync layer to handle newly added assignee/chore combinations and shared-state transitions. + 6. [x] Keep targeted cleanup on delete and assignment removal by preserving `remove_entities_by_item_id()`, `remove_orphaned_assignee_chore_entities()`, and `remove_orphaned_shared_chore_sensors()` in `custom_components/choreops/helpers/entity_helpers.py` around lines 705-790 as the removal half of the live-sync contract. + 7. [x] Add ordering guarantees for dashboard-facing consistency: runtime entity creation/removal must complete before helper surfaces are relied on for entity IDs. Treat `sensor.py` dashboard helper payloads around lines 4546-5000 as a contract consumer, not as an afterthought. + 8. [x] Resolve rename-sensitive entities either by computing display-facing values from coordinator data at read time or by explicit replacement/update logic so chore renames do not require reload for a consistent UI surface. +- **Phase 2 completion update (2026-04-12)** + - Accomplished: added `ChoreEntitySyncContext`, a chore-scoped coordinator runtime sync entry point, and a small result contract for add/remove orchestration. + - Accomplished: upgraded sensor runtime creation from test-only behavior to a production-safe path that creates assignee chore sensors and shared/shared-first global state sensors only when missing. + - Accomplished: added production-safe chore button registration and runtime creation for claim, approve, and disapprove buttons. + - Accomplished: fixed shared chore cleanup to track the real shared-state sensor unique-id suffix and to preserve both `SHARED` and `SHARED_FIRST` sensors correctly. + - Accomplished: added targeted runtime sync regression tests covering create, assignment churn, rename-sensitive replacement, and shared-state transitions. + - Gap/deviation: service handlers and options flow still call the old reload-driven service/helper path. That is the explicit Phase 3 adoption work, not an unfinished Phase 2 implementation detail. + - Next action: replace service reload usage with the runtime sync contract, then move chore-specific options-flow CRUD to that same contract. +- **Key issues** + - Dynamic creation needs to cover both create and update paths because assignment changes and independent/shared transitions create new entities without creating a new chore record. + - The orchestration layer must not write storage itself; it should be a platform/entity sync concern triggered after manager-owned persistence. + - The dashboard helper currently walks `coordinator.chores_data` and registry lookups independently, so race ordering between entity registration and helper rebuild can produce temporary `eid=None` payloads if sequencing is wrong. + - Buttons cache chore and assignee names at construction. A create/delete-only implementation is not enough; rename safety must be resolved or explicitly deferred. +- **Phase exit checkpoint** + - Fallback path / wrapper inventory: + - Existing reload-driven caller path remains in `async_sync_entities_after_service_create()` and its service/options-flow callers. Justification: caller adoption belongs to Phase 3 and was intentionally not mixed into the orchestration phase. + - No new compatibility wrapper or dual-behavior shim was introduced in Phase 2. The new runtime sync layer is a direct implementation path, not a wrapper around reload. + +### Phase 3 – Flow and service adoption + +- **Goal**: Replace reload-driven chore CRUD behavior in services and options flow with the targeted runtime sync contract while keeping feature-flag and settings reload paths intact. +- **Steps / detailed work items** + 1. [x] Replace `async_sync_entities_after_service_create()` in `custom_components/choreops/coordinator.py` around lines 377-388 with a chore-focused runtime sync API that can be called from create, update, and delete service handlers without reloading the config entry. + 2. [x] Update `handle_create_chore`, `handle_update_chore`, and `handle_delete_chore` in `custom_components/choreops/services.py` around lines 1100-1400 so production uses the same runtime sync path currently reserved for test-only direct sensor creation. + 3. [x] Ensure service handlers do not create a second orchestration layer. They should delegate write ownership to `ChoreManager` and then request runtime entity sync using persisted mutation context. + 4. [x] Replace deferred reload behavior in `custom_components/choreops/options_flow.py` around lines 1020-2575 and 6110-6175 for chore entity CRUD paths, while preserving `_update_system_settings_and_reload()` for `config_entry.options` changes that still require integration reload. + 5. [x] Introduce chore-specific handling in options flow instead of weakening the global `_reload_needed` behavior for all entity types. The chore path must stop relying on reload without destabilizing badges, rewards, users, or other domains that still use the deferred reload pattern. + 6. [x] Review temporary helper-state paths in `options_flow.py` such as `_chore_being_edited`, `_chore_template_date_raw`, `_chore_template_applicable_days`, and `_chore_template_daily_multi_times` so live updates do not leave stale flow state or rely on a post-return reload to normalize state. + 7. [x] Verify dashboard helper sensors, selectors, and calendars continue to respond from coordinator state and signal invalidation alone, avoiding new bespoke refresh calls where existing listener behavior is already sufficient. +- **Phase 3 completion update (2026-04-12)** + - Accomplished: chore service create, update, and delete now delegate to the shared runtime sync contract after manager-owned persistence instead of using the reload-era service fallback. + - Accomplished: chore options-flow add, edit, and delete now use the same runtime sync contract, while chore helper substeps for per-assignee dates, per-assignee details, and daily-multi times no longer mark deferred reload. + - Accomplished: added focused tests proving service and options-flow chore callers use runtime sync directly and no longer rely on the old chore reload path. + - Gap/deviation: `async_sync_entities_after_service_create()` still exists for non-chore service callers and was intentionally not generalized away in this phase. + - Next action: expand Phase 4 validation around helper/dashboard continuity and remaining reload-era test/documentation assumptions. +- **Key issues** + - Options flow currently batches entity CRUD and performs reload at menu return, so removing reload may expose stale assumptions about coordinator references or flow summaries. + - Services and options flow should converge on the same runtime sync entry point to avoid one path becoming the new source of regressions. + - Service update currently excludes completion-criteria edits from the public schema, so shared-state transition handling is an options-flow adoption concern today, not a service-call concern. + - Options-flow delete already bypasses deferred reload, while per-assignee date/detail helpers and daily-multi helper steps currently mark reload even though they appear to be state-only updates rather than graph mutations. + - Test helpers and comments currently teach developers to reacquire `config_entry.runtime_data` after options-flow CRUD because reload creates a new coordinator. Those expectations must be updated in the same workstream. +- **Phase exit checkpoint** + - Fallback path / wrapper inventory: + - Existing `async_sync_entities_after_service_create()` remains for non-chore service callers. Justification: this phase intentionally converged chore callers only and did not generalize non-chore domains prematurely. + - No new compatibility wrapper or dual-behavior shim was introduced in Phase 3. Services and options flow now call the same chore runtime sync path directly. + +### Phase 4 – Validation and rollout + +- **Goal**: Validate that chore CRUD becomes live and dashboard-safe without regressions in entity registry cleanup, button authorization surfaces, or helper payloads. +- **Status**: Complete +- **Completed work** + - Added explicit service-path assertions that chore create, update, and delete do not call config-entry reload and still materialize the expected live sensor, button, and dashboard-helper surfaces. + - Added options-flow regression coverage proving chore add/edit/delete use runtime sync directly, while sanctioned system-settings edits still reload the integration. + - Added helper-step regression coverage proving the multi-assignee INDEPENDENT helper step and the DAILY_MULTI helper step no longer mark deferred reload. + - Updated test guidance in `tests/AGENT_TEST_CREATION_INSTRUCTIONS.md` so chore CRUD is no longer documented as reload-backed by default. + - Reviewed `tests/helpers/setup.py` reload-era comments and left the badge-specific note unchanged because it is not part of the chore runtime-sync surface. + - Discovered a critical follow-on defect signal in sparse edit preservation (ESV06), reopening rollout until the new Phase 3B work is complete. +- **Steps / detailed work items** + 1. [x] Add regression tests covering service-driven chore create/update/delete in `tests/test_chore_crud_services.py`, using scenario fixtures from `tests/scenarios/` and the patterns in `tests/AGENT_TEST_CREATION_INSTRUCTIONS.md`, with explicit assertions that no config-entry reload is required for chore CRUD. + 2. [x] Add entity-surface tests proving new chore status sensors and workflow buttons appear live after create and after assignment expansion, and disappear after delete or assignment removal. + 3. [x] Add coverage for completion-criteria transitions so independent-to-shared and shared-to-independent chore edits produce the correct shared-state sensor and assignee-chore entity graph. + 4. [x] Add dashboard/helper regression tests confirming helper payloads contain valid entity IDs after live chore CRUD and that label-grouped helper views remain consistent. + 5. [x] Add options-flow regression tests covering chore add/edit/delete without deferred reload assumptions, especially multi-assignee INDEPENDENT helper flows and DAILY_MULTI helper steps. + 6. [x] Update test helpers and test docs that currently assume options-flow entity CRUD forces reload and stale coordinator replacement, including `tests/helpers/setup.py` and `tests/AGENT_TEST_CREATION_INSTRUCTIONS.md` where relevant. + 7. [x] Run focused validation for the remaining sanctioned reload paths so system settings changes still reload correctly and this initiative does not blur the architecture boundary described in `docs/ARCHITECTURE.md`. + 8. [x] Update release notes and any relevant docs to state that chore CRUD now updates runtime entities dynamically while settings changes still reload the integration. +- **Key issues** + - Tests must verify both entity registry state and user-visible behavior; proving “no reload” only by absence of exceptions is not enough. + - Dashboard refresh symptoms can be indirect, so helper entity and selector state assertions are the safest backend proxy if frontend UI automation is out of scope. + - Existing tests already contain comments that rationalize missing runtime entity creation; those comments must be removed or rewritten so the suite does not normalize old limitations. + - Rollout cannot be declared complete while sparse section submissions in options flow may still reset omitted stored values to schema defaults. +- **Phase 4 completion update (2026-04-12)** + - Accomplished: completed targeted rollout validation across chore runtime sync, chore CRUD services, chore options-flow CRUD, per-assignee helper flows, DAILY_MULTI helper flows, and the Phase 3B sparse-edit regression surface. + - Accomplished: confirmed the sanctioned reload boundary remains intact for system settings, with explicit regression coverage proving those edits still call config-entry reload. + - Accomplished: updated developer-facing test guidance and user-facing README language so the new runtime behavior is documented consistently. + - Validation: `./utils/quick_lint.sh --fix` passed on 2026-04-12. `python -m pytest tests/test_chore_runtime_entity_sync.py tests/test_chore_crud_services.py tests/test_options_flow_entity_crud.py tests/test_options_flow_per_kid_helper.py tests/test_options_flow_daily_multi.py -v --tb=line` passed on 2026-04-12 (`100 passed`). + - Release note summary: chore create, edit, and delete now update runtime sensors, buttons, and dashboard helper payloads without a full integration reload. Sanctioned system-settings edits still reload the integration. + - Gap/deviation: none within the approved rollout scope. + - Next action: owner review and PR/release metadata alignment for issue 107. +- **Phase exit checkpoint** + - Record any fallback path, compatibility wrapper, or temporary shim still present after validation. Default expectation is `none`. If any remain, justify each one briefly, explain why the release can still be considered correct, and state the exact follow-up needed before completion can be claimed. + - Fallback path / wrapper inventory: + - No new fallback path, compatibility wrapper, or temporary shim was added in Phase 4. + - Existing `async_sync_entities_after_service_create()` remains scoped to non-chore service callers only, unchanged from Phase 3, and is not part of the chore CRUD runtime-sync path. + +## Testing & validation + +- Planned validation commands: + - `./utils/quick_lint.sh --fix` + - `mypy custom_components/choreops/` + - `mypy tests/` + - `python -m pytest tests/ -v --tb=line` + - Targeted suites during implementation: + - `python -m pytest tests/test_workflow_chores.py -v` + - `python -m pytest tests/test_config_flow.py -v` + - `python -m pytest tests/test_services.py -v` +- Tests executed: + - `./utils/quick_lint.sh --fix` passed on 2026-04-12. + - Full test validation for this workstream was reported complete at 100% success and should not be rerun unless explicitly requested. + - `python -m pytest tests/test_chore_runtime_entity_sync.py tests/test_chore_crud_services.py -v --tb=line` passed on 2026-04-12 (`22 passed`). + - `python -m pytest tests/test_chore_runtime_entity_sync.py tests/test_chore_crud_services.py tests/test_options_flow_entity_crud.py -v --tb=line` passed on 2026-04-12 (`58 passed`). + - `python -m pytest tests/test_chore_crud_services.py tests/test_options_flow_entity_crud.py tests/test_options_flow_per_kid_helper.py::TestPerAssigneeHelperAdd::test_pkh02_helper_completion_does_not_mark_deferred_reload tests/test_options_flow_daily_multi.py::TestDailyMultiOptionsFlow::test_of_03_helper_completion_does_not_mark_deferred_reload -v --tb=line` passed on 2026-04-12 (`62 passed`). + - Focused defect validation on 2026-04-12 confirmed `tests/test_options_flow_per_kid_helper.py::TestSchemaEdgeCases::test_esv06_edit_partial_section_payload_preserves_schedule_fields` fails in isolation, so this is a real backend defect signal rather than a suite-order artifact. + - Direct transform/builder isolation on 2026-04-12 preserved the ESV06 values correctly, narrowing the suspected defect surface to the options-flow edit boundary and schema default handling. +- Implementation validation policy: + - Use targeted chore-focused suites during implementation loops to keep iteration efficient. + - Do not rerun the full test suite for this workstream unless explicitly requested or a later blocker makes it necessary. +- Outstanding tests: Phase 3B must add explicit sparse-edit preservation coverage before rollout can resume. Do not treat ESV06 as independent or optional follow-up work. +- Outstanding tests: none for the approved targeted rollout scope. Full-suite rerun remains optional and out of the active implementation loop unless explicitly requested. + +### Phase 3B – Sparse edit preservation remediation + +- **Goal**: Prove and fix partial-section edit behavior so omitted optional chore fields preserve stored values instead of being overwritten by schema defaults during options-flow edit submissions. +- **Why this phase exists**: + - `test_esv06_edit_partial_section_payload_preserves_schedule_fields` in `tests/test_options_flow_per_kid_helper.py` around lines 1374-1449 fails in isolation. + - The failure returns `0d 1h 0m`, the chore due-window default, instead of the stored `3h`, indicating default injection rather than correct preserve-existing semantics. + - Direct calls through `flow_helpers.transform_chore_cfof_to_data()` and `data_builders.build_chore()` preserve the same values correctly, which means rollout should pause and the edit boundary should be remediated at the root. +- **Steps / detailed work items** + 1. [x] Add targeted reproduction tests in `tests/test_options_flow_per_kid_helper.py` around the ESV06 block to cover representative omission patterns by field family: schedule text offset, advanced text offset, consolidated multi-select notifications, and one explicit-overwrite control. + 2. [x] Add complementary options-flow tests in `tests/test_options_flow_entity_crud.py` for sparse edit submissions after stored non-default values exist, using a quality sampling approach across distinct field types rather than brute-force testing every optional field. + 3. [x] Add at least one negative-control test proving explicit user changes still overwrite stored values, and one preserve-on-omit control proving omitted values stay intact, so the remediation does not collapse omission and intentional update into the same behavior. + 4. [x] Audit `custom_components/choreops/helpers/flow_helpers.py` around lines 880-1045 and group optional edit fields by risk pattern: optional with defaulted text value, optional multi-select/synthesized selector, optional list/collection, optional field with explicit clear semantics, and required field that should be out of scope for preserve-on-omit concerns. + 5. [x] Trace the edit path in `custom_components/choreops/options_flow.py` around lines 1368-1404 to document whether Home Assistant returns schema-defaulted optional values in the submitted payload or whether ChoreOps is merging defaults too early. + 6. [x] Remediate the root cause in one place only: either stop using backend schema defaults for preserve-on-omit edit fields, or normalize the edit payload before validation/transform so omitted fields remain truly omitted. Do not add per-field compatibility shims. + 7. [x] Re-verify `flow_helpers.validate_chores_inputs()`, `flow_helpers.transform_chore_cfof_to_data()`, and `data_builders.build_chore()` still preserve their current separation of responsibilities after the edit-boundary fix. + 8. [x] Re-run the representative sparse-edit regression set plus the previously completed Phase 4 chore runtime-sync coverage before resuming rollout status updates. +- **Phase 3B completion update (2026-04-12)** + - Accomplished: expanded sparse-edit regression coverage with representative tests for omitted schedule text fields, omitted collection fields, omitted advanced notification fan-out, explicit overwrite behavior, and explicit clear behavior. + - Accomplished: confirmed the defect class at the live edit boundary rather than in shared transformation/building logic. Direct calls through `transform_chore_cfof_to_data()` and `build_chore()` remained correct. + - Accomplished: fixed the root cause by adding an edit-mode schema option that suppresses backend default injection for optional preserve-on-omit fields while continuing to use suggested values for UI display. + - Accomplished: kept the fix scoped to chore edit forms in `options_flow.py`; add flows, config flow, services, and `data_builders.build_chore()` were not behaviorally changed by this remediation. + - Validation: `./utils/quick_lint.sh --fix` passed locally, including Ruff, production MyPy, and boundary checks. `python -m pytest tests/test_chore_runtime_entity_sync.py tests/test_chore_crud_services.py tests/test_options_flow_entity_crud.py tests/test_options_flow_per_kid_helper.py tests/test_options_flow_daily_multi.py -v --tb=line` passed on 2026-04-12 (`100 passed`). + - Gap/deviation: none within the approved Phase 3B scope. + - Next action: await approval before resuming Phase 4 rollout validation/documentation completion. +- **Key issues** + - `build_chore_schema()` in `custom_components/choreops/helpers/flow_helpers.py` around lines 919-1037 currently assigns defaults to multiple optional schedule and advanced fields, which is safe for add flows but may be unsafe for sparse edit submissions. + - The edit handler in `custom_components/choreops/options_flow.py` around lines 1385-1394 passes sectioned user input through shared validation and transform logic, so a bug at this boundary can affect multiple optional fields, not just due-window offset. + - Notification consolidation is particularly risky because omission of the selector must preserve stored booleans, while explicit submission must still rewrite them deterministically. + - This phase must stay test-first. No production-code remediation should start until the new regression set proves the breadth of the defect surface. + - Coverage should be pattern-complete rather than field-exhaustive. The bar is to sample enough distinct field behaviors to prove the defect class is closed without creating noisy duplicate tests for every single optional field. + - Shared-usage safety boundary: `data_builders.build_chore()` and service-layer update/create flows remain shared contracts and were intentionally left unchanged. The remediation lives at the edit-form schema boundary only. +- **Phase exit checkpoint** + - Fallback path / wrapper inventory: default expectation is `none`. The remediation must not add field-specific fallback wrappers to mask edit-boundary defects. + +## Notes & follow-up + +- **Current architecture readout**: + - Chore CRUD is already manager-owned and signal-first in `custom_components/choreops/managers/chore_manager.py`. + - Calendar entities already invalidate on chore mutation signals in `custom_components/choreops/calendar.py` and are a valid model for read-path live updates. + - Select/helper surfaces mostly derive from coordinator state and are more likely to need listener verification than entity creation work. + - The main missing runtime capability is setup-time entity families that do not yet have a production add callback, especially buttons. +- **Implementation guardrails**: + - Service-path runtime sync should be proven before options-flow conversion if scope needs to be staged. + - A chore update that changes only values is not equivalent to one that changes assignments or criteria. The implementation must model mutation classes explicitly. + - Dashboard helper payload correctness is part of the acceptance bar. Storage correctness alone is insufficient. + - Rename correctness must be resolved intentionally because many entities cache constructor-time names and translation placeholders. +- **Recommended implementation bias**: + - Build a minimal, chore-scoped runtime entity sync contract first. + - Reuse existing targeted orphan cleanup for removals. + - Avoid a generic “dynamic everything” abstraction unless chore implementation proves the pattern is stable enough to promote. + - Use [docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_TRAPS_AND_OPPORTUNITIES.md](./CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_TRAPS_AND_OPPORTUNITIES.md) as the risk register during implementation and review. +- **Open decisions**: + - Whether the edit-boundary remediation should live entirely inside `build_chore_schema()` / form construction or in a single payload-normalization helper immediately before validation. + - Whether to create a dedicated entity-sync helper module now or first land a sensor-plus-button callback pattern and extract later. + - Whether options flow should switch all chore CRUD paths in the same change or follow after service/runtime sync is proven. + - Whether entity rename semantics require constructor-time attribute cleanup or are acceptable through state/attribute refresh only. + - Whether helper payloads should temporarily tolerate `eid=None` during in-flight creation or whether the sync contract must guarantee registry readiness before helper rebuild. +- **Effort estimate**: + - Chore-scoped dynamic sync: medium-high. + - Generalized multi-platform runtime sync framework: high. + +> **Archive note:** This initiative is complete. The canonical archived filename is `CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md` under `docs/completed/`. diff --git a/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_BUILDER_HANDOFF.md b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_BUILDER_HANDOFF.md new file mode 100644 index 0000000..dc3f824 --- /dev/null +++ b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_BUILDER_HANDOFF.md @@ -0,0 +1,163 @@ +# Dynamic chore entity lifecycle - Builder handoff + +--- + +status: COMPLETE +owner: Strategist Agent +created: 2026-04-12 +issue: 107 +parent_plan: CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md +handoff_from: ChoreOps Strategist +handoff_to: ChoreOps Builder +phase_focus: Phase 4 rollout validation and documentation closeout completed + +--- + +## Handoff button + +[HANDOFF_TO_BUILDER_CHORE_DYNAMIC_ENTITY_LIFECYCLE](CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md) + +## Handoff objective + +This handoff has been completed. The rollout-blocking sparse-edit defect was remediated, Phase 4 validation closed green, and the initiative is ready for owner review. + +Critical implementation bias: + +- Do not preserve or introduce fallback paths, compatibility wrappers, or temporary dual-behavior shims unless they are explicitly justified, narrowly scoped, and called out in the handback. +- For issue 107, wrappers should be treated as a sign that the runtime sync contract may not have been solved at the root. Prefer finishing the real implementation over masking gaps with compatibility layers. + +Required behavior: + +1. Keep manager-owned writes intact and perform runtime entity synchronization only after successful persistence. +2. Replace chore-specific reload-driven entity rebuilds with targeted runtime add, remove, and update behavior. +3. Support graph-mutating updates, not just brand-new chore creation. +4. Preserve system-settings and non-chore reload behavior. +5. Prove dashboard helper integrity and registry correctness through tests. +6. Before continuing any new rollout work, expand sparse-edit regression coverage and fix the root cause behind ESV06 so omitted optional chore edit fields preserve stored values. +7. Close Phase 4 with explicit rollout validation, documentation updates, and release-note wording that preserves the sanctioned reload boundary for system settings. + +## Implementation stance already locked + +These planning decisions are considered settled for issue 107 and should not be re-opened during implementation unless a blocker is discovered: + +1. Scope is chore domain item CRUD only. +2. System settings reload behavior stays intact. +3. Runtime graph mutation is the missing capability; `async_update_listeners()` alone is not sufficient. +4. Buttons are in scope for v1 and are not optional follow-up work. +5. Service and options-flow paths must converge on the same runtime sync contract. +6. No `.storage` schema bump is expected. +7. Public service update scope is narrower than options flow today because completion-criteria changes are not exposed by the update service schema. +8. ESV06 is a real defect signal, not a disposable test failure. Treat it as blocking rollout until test-first remediation is complete. + +## Recommended defaults for remaining design choices + +These are the defaults the builder should use unless implementation uncovers a concrete contradiction: + +1. Fix the sparse-edit boundary at the root rather than layering field-specific preservation shims around it. +2. Add tests first to define the exact breadth of the defect surface before production-code remediation begins. +3. Treat rename-sensitive entities as a real acceptance item. Prefer runtime recomputation where practical; use explicit replacement only where constructor-cached values make that necessary. +4. Do not tolerate helper payloads that emit missing entity IDs as a normal steady-state outcome. + +## In scope + +- Core orchestration and fallback replacement: + - [custom_components/choreops/coordinator.py](../../custom_components/choreops/coordinator.py) + - [custom_components/choreops/managers/chore_manager.py](../../custom_components/choreops/managers/chore_manager.py) +- Runtime entity families: + - [custom_components/choreops/sensor.py](../../custom_components/choreops/sensor.py) + - [custom_components/choreops/button.py](../../custom_components/choreops/button.py) + - [custom_components/choreops/helpers/entity_helpers.py](../../custom_components/choreops/helpers/entity_helpers.py) +- Chore callers/adopters: + - [custom_components/choreops/services.py](../../custom_components/choreops/services.py) + - [custom_components/choreops/options_flow.py](../../custom_components/choreops/options_flow.py) +- Validation and regression coverage: + - [tests/test_chore_crud_services.py](../../tests/test_chore_crud_services.py) + - [tests/test_entity_lifecycle_stability.py](../../tests/test_entity_lifecycle_stability.py) + - [tests/helpers/setup.py](../../tests/helpers/setup.py) + - [tests/AGENT_TEST_CREATION_INSTRUCTIONS.md](../../tests/AGENT_TEST_CREATION_INSTRUCTIONS.md) + +## Out of scope + +- Generic multi-domain runtime entity sync abstraction. +- Changes to non-chore domains that still rely on deferred reload. +- Dashboard YAML redesign. +- System settings reload redesign. +- Storage migration or schema version work unless a hard blocker proves it is required. + +## Completion summary + +1. Expanded ESV06 into a representative sparse-edit regression set before production remediation. +2. Audited the edit boundary and confirmed the defect lived in schema default injection, not shared builder/service logic. +3. Remediated the root cause in the chore edit-form schema path only. +4. Re-ran sparse-edit regression coverage and the chore runtime-sync suite successfully. +5. Closed Phase 4 with rollout validation, documentation updates, and explicit release-note wording. + +## Builder checklist + +### Package A - Runtime sync contract + +- [ ] Replace the chore-specific reload fallback in [custom_components/choreops/coordinator.py](../../custom_components/choreops/coordinator.py) with a chore-scoped runtime sync entry point. +- [ ] Ensure the sync entry point accepts mutation context instead of a bare refresh request. +- [ ] Keep storage mutation ownership in [custom_components/choreops/managers/chore_manager.py](../../custom_components/choreops/managers/chore_manager.py). + +### Package B - Runtime entity families + +- [ ] Extend [custom_components/choreops/sensor.py](../../custom_components/choreops/sensor.py) to support production-safe chore entity creation for create and update mutation paths. +- [ ] Extend [custom_components/choreops/button.py](../../custom_components/choreops/button.py) to support production-safe creation of claim, approve, and disapprove buttons for new assignee/chore pairs. +- [ ] Reuse cleanup helpers in [custom_components/choreops/helpers/entity_helpers.py](../../custom_components/choreops/helpers/entity_helpers.py) for delete and assignment-removal behavior. +- [ ] Resolve rename-sensitive display data intentionally rather than assuming state refresh will fix constructor-cached values. + +### Package C - Caller adoption + +- [ ] Update chore service handlers in [custom_components/choreops/services.py](../../custom_components/choreops/services.py) to use the shared runtime sync path. +- [ ] Replace chore-specific deferred reload behavior in [custom_components/choreops/options_flow.py](../../custom_components/choreops/options_flow.py) while preserving non-chore reload paths. +- [ ] Treat service delete and options-flow delete as verification-first paths because they already avoid the reload helper today; only add runtime sync work there if direct validation shows missing cleanup or stale helper payloads. +- [ ] Avoid introducing a second orchestration layer in callers. + +### Package D - Regression coverage + +- [ ] Add sparse-edit reproduction tests around ESV06 in [tests/test_options_flow_per_kid_helper.py](../../tests/test_options_flow_per_kid_helper.py). +- [ ] Add options-flow CRUD coverage for omitted optional fields after stored non-default values already exist, using a representative sampling of field patterns rather than one test per field. +- [ ] Add negative-control tests proving explicit edits still overwrite stored values and explicit clear semantics remain distinct from omission. +- [ ] Add or update service CRUD regression tests with explicit no-reload expectations. +- [ ] Add entity-surface coverage for live sensor and button creation/removal. +- [ ] Add helper payload integrity coverage so dashboard-facing IDs remain valid after live CRUD. +- [ ] Update test helpers and docs that currently assume options-flow CRUD replaces the coordinator via reload. + +## Acceptance criteria + +1. Creating a chore does not require a full integration reload and produces the expected live sensors and buttons. +2. Updating assignments adds and removes assignee-linked entities correctly without reload. +3. Shared/independent criteria transitions update the shared-state sensor correctly. +4. Deleting a chore removes linked entities and helper references without reload. +5. Dashboard helper payloads expose valid entity IDs after live chore CRUD. +6. Options-flow chore CRUD no longer depends on deferred reload for correctness. +7. System settings reload behavior remains intact. +8. Sparse edit submissions in chore options flow preserve omitted stored values for optional fields instead of resetting them to schema defaults. +9. Phase 3B coverage demonstrates closure across representative optional-field behavior patterns, not just the original ESV06 field. + +## Validation gates + +- `./utils/quick_lint.sh --fix` +- Targeted chore-focused suites during implementation, including at minimum: + - `python -m pytest tests/test_options_flow_per_kid_helper.py::TestSchemaEdgeCases::test_esv06_edit_partial_section_payload_preserves_schedule_fields -v --tb=line` + - `python -m pytest tests/test_chore_crud_services.py -v --tb=line` + - `python -m pytest tests/test_entity_lifecycle_stability.py -v --tb=line` +- Full-suite rerun is not part of the implementation loop for this issue branch unless explicitly requested or required by a newly discovered blocker. + +## Required handback from builder + +1. Changed-files summary grouped by Package A-D. +2. The final runtime sync contract shape and where it lives. +3. Any fallback path, compatibility wrapper, or temporary shim left in place, with explicit justification and removal condition. Default expectation is `none`. +4. Targeted and full-suite test results. +5. Confirmation that no schema bump was required. +6. Parent plan summary update in [docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md](CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md). + +## Final handback + +1. Runtime sync contract shape: chore-scoped mutation-context orchestration via `ChoreEntitySyncContext`, coordinated in `coordinator.py`, with platform-local entity creation in `sensor.py` and `button.py` plus shared cleanup helpers in `helpers/entity_helpers.py`. +2. Validation outcome: `./utils/quick_lint.sh --fix` passed, and `python -m pytest tests/test_chore_runtime_entity_sync.py tests/test_chore_crud_services.py tests/test_options_flow_entity_crud.py tests/test_options_flow_per_kid_helper.py tests/test_options_flow_daily_multi.py -v --tb=line` passed (`100 passed`) on 2026-04-12. +3. Fallback path inventory: no new fallback path, compatibility wrapper, or temporary shim was introduced. `async_sync_entities_after_service_create()` remains only for non-chore service callers and is outside the completed chore runtime-sync scope. +4. Schema bump confirmation: none required. +5. Release note summary: chore create, edit, and delete now update runtime sensors, buttons, and dashboard helper payloads without a full integration reload. Sanctioned system-settings edits still reload the integration. diff --git a/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_ISSUE_96_ENHANCEMENT_DRAFT.md b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_ISSUE_96_ENHANCEMENT_DRAFT.md new file mode 100644 index 0000000..b4a15c9 --- /dev/null +++ b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_ISSUE_96_ENHANCEMENT_DRAFT.md @@ -0,0 +1,40 @@ +# REQ template draft + +- **Title**: `[REQ] Add runtime chore entity sync for dashboard-driven chore changes` +- **Feature area**: `Integration logic/state` + +## Problem or use case + +Following the later discussion on closed issue [#96](https://github.com/ccpk1/ChoreOps/issues/96), this looks less like one remaining `update_chore` bug and more like a broader runtime limitation. + +Today, when chore membership or other chore-backed entity structure changes, ChoreOps relies on a full integration reload to rebuild the affected entities. That is a normal Home Assistant pattern, but it becomes disruptive for dashboard-driven chore changes because the dashboard depends on those live entities and helper surfaces. + +When a reload happens during an assignment change, the page can be left stale or temporarily broken until the user manually refreshes. The late comments in [#96](https://github.com/ccpk1/ChoreOps/issues/96) suggest the most fragile cases are adding or removing assignees from a chore. + +## Proposed solution + +Add a targeted runtime synchronization path for chore-backed entities so ChoreOps can handle live chore graph changes without depending on a full integration reload for those specific operations. + +This should cover chore create, update, and delete, including live add/remove/update of assignee-specific sensors, buttons, and dashboard helper surfaces. Full integration reload should remain available where it is still the right tool, but it should stop being the only backend path for chore CRUD cases that need smoother live behavior. + +## Expected outcome + +- Dashboard-driven chore updates stop leaving the page in a broken or stale state. +- Users no longer need a manual page refresh after chore assignment changes. +- Backend behavior becomes more consistent across service calls, dashboard actions, and config/options flows. +- Chore CRUD becomes a stronger foundation for more interactive native UI patterns in the future. + +## Alternatives considered + +- Adding delays or wait templates after dashboard scripts +- Forcing browser refresh with `browser_mod` +- Navigating away and back to the dashboard +- Continuing to patch isolated `update_chore` edge cases instead of addressing the runtime entity lifecycle directly + +## Additional context + +- In the latest discussion on [#96](https://github.com/ccpk1/ChoreOps/issues/96), the current understanding was that chore create/update/delete flows trigger integration reload because the system must recreate or remove user-specific entities when chore membership changes. +- That original reload-centric design appears intentional and standards-aligned with normal Home Assistant integration lifecycle patterns, not an accidental shortcut. +- The new ask is to move one step beyond that baseline for chore CRUD specifically, because ChoreOps now behaves more like an interactive application surface than a mostly static entity set. +- There is already active planning around this direction in the repo, but it would still be useful to track it as a user-facing enhancement request tied back to the field report and dashboard use case. +- @Crazypkr your follow-up testing and examples in [#96](https://github.com/ccpk1/ChoreOps/issues/96) were the key reason this broader backend limitation became clear. diff --git a/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_PHASE_3B_SPARSE_EDIT_REMEDIATION.md b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_PHASE_3B_SPARSE_EDIT_REMEDIATION.md new file mode 100644 index 0000000..fedf5ed --- /dev/null +++ b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_PHASE_3B_SPARSE_EDIT_REMEDIATION.md @@ -0,0 +1,199 @@ +# Phase 3B support plan - Sparse edit preservation remediation + +## Initiative snapshot + +- **Name / Code**: Sparse edit preservation remediation / `CHORE_DYNAMIC_ENTITY_LIFECYCLE_PHASE_3B` +- **Target release / milestone**: Same release train as issue 107; must complete before final rollout signoff +- **Owner / driver(s)**: TBD +- **Status**: Complete + +## Summary & immediate steps + +| Phase / Step | Description | % complete | Quick notes | +| --- | --- | --- | --- | +| Step 1 – Confirm defect breadth | Expand ESV06 into a minimal but explicit sparse-edit regression set | 100% | Representative sparse-edit regressions were added and reproduced the defect class | +| Step 2 – Audit edit boundary | Trace where omitted optional fields become defaulted values | 100% | Direct transform/build paths preserved values; edit schema boundary was the defect surface | +| Step 3 – Remediate root cause | Fix preserve-on-omit semantics in one shared place | 100% | Added edit-mode schema behavior that stops backend default injection for optional preserve-on-omit fields | +| Step 4 – Re-validate rollout | Re-run sparse-edit and runtime-sync coverage together | 100% | Repo gate passed and the combined targeted suite passed with 100 tests | + +1. **Key objective** – Close the options-flow sparse-edit defect surface before any more rollout work proceeds, using tests first and a root-cause fix rather than field-specific patches. +2. **Summary of recent work** + - Confirmed `test_esv06_edit_partial_section_payload_preserves_schedule_fields` in `tests/test_options_flow_per_kid_helper.py` fails in isolation. + - Confirmed the returned bad value is `0d 1h 0m`, which is the schema default for `chore_due_window_offset`, not the stored value `3h`. + - Confirmed direct calls to `flow_helpers.transform_chore_cfof_to_data()` and `data_builders.build_chore()` preserve the same omitted fields correctly, narrowing suspicion to the options-flow edit boundary. + - Confirmed `build_chore_schema()` assigns defaults for multiple optional schedule and advanced fields, creating a plausible preserve-on-omit risk when edit payloads are sparse. +3. **Next steps (short term)** + - Phase complete. Await owner approval before resuming Phase 4 rollout work. +4. **Risks / blockers** + - The defect may affect more than due-window fields, because multiple optional fields in the chore edit schema use defaults. + - Notifications are especially risky because omission must preserve prior booleans while explicit submission must still deterministically rewrite them. + - Any fix that alters form defaults globally could break add-flow ergonomics if create and edit semantics are not separated carefully. +5. **References** + - [docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md](./CHORE_DYNAMIC_ENTITY_LIFECYCLE_COMPLETED.md) + - [custom_components/choreops/helpers/flow_helpers.py](../../custom_components/choreops/helpers/flow_helpers.py) + - [custom_components/choreops/options_flow.py](../../custom_components/choreops/options_flow.py) + - [custom_components/choreops/data_builders.py](../../custom_components/choreops/data_builders.py) + - [tests/test_options_flow_per_kid_helper.py](../../tests/test_options_flow_per_kid_helper.py) + - [docs/DEVELOPMENT_STANDARDS.md](../DEVELOPMENT_STANDARDS.md) + - [docs/QUALITY_REFERENCE.md](../QUALITY_REFERENCE.md) +6. **Decisions & completion check** + - **Decisions captured**: + - Treat ESV06 as a blocker for rollout, not as an optional cleanup item. + - Use a test-first sequence before any code remediation. + - Fix preserve-on-omit semantics at one shared edit-boundary layer rather than through per-field special cases. + - Do not introduce wrappers, fallback shims, or hidden compatibility branches to hide sparse-edit defects. + - **Completion confirmation**: `[x]` All sparse-edit reproduction tests, remediation, and re-validation completed before Phase 4 can resume. + +## Detailed analysis + +### Confirmed observations + +- `tests/test_options_flow_per_kid_helper.py` around lines 1374-1449 creates a chore with non-default values for: + - `chore_due_window_offset = 3h` + - `chore_due_reminder_offset = 20m` + - `notify_on_claim = True` + - `notify_on_due_window = True` +- The same test reopens the edit form and submits only a partial section payload, intentionally omitting some schedule and advanced fields. +- The final assertion fails because stored `chore_due_window_offset` becomes `0d 1h 0m`, which matches `DEFAULT_DUE_WINDOW_OFFSET` instead of the stored `3h`. +- Running the test alone still fails, so the defect is not caused by prior test contamination. + +### Narrowed defect surface + +- `flow_helpers.transform_chore_cfof_to_data()` preserves omitted values correctly when `existing_chore` is passed. +- `data_builders.build_chore()` also preserves omitted values correctly in update mode when the transformed payload omits the field. +- That leaves the edit boundary as the most likely defect surface: + - `build_chore_schema()` in `helpers/flow_helpers.py` defines defaults for optional edit fields. + - `async_step_edit_chore()` in `options_flow.py` accepts sectioned user input and routes it into shared validation and transform logic. + - If Home Assistant submits schema-defaulted optional values back in sparse edit submissions, omission semantics are lost before preservation logic runs. + +### Risk map by field family + +Potentially affected fields are those that are optional on edit and also have schema defaults or synthesized defaults: + +- **Schedule section** + - `chore_due_window_offset` + - `due_date` + - `applicable_days` + - `custom_interval` + - `custom_interval_unit` +- **Advanced configuration section** + - `chore_due_reminder_offset` + - consolidated `notifications` + - any booleans or selectors whose omission should preserve existing values during edit +- **Lower risk / likely safe** + - fields already marked `Required` where edit semantics expect full re-submission + - fields with explicit clear controls that distinguish omission from deliberate clearing + +### Planned coverage model + +The Phase 3B test plan should be representative, not exhaustively one-test-per-field. + +Coverage should deliberately sample distinct behavior patterns: + +- **Optional text field with non-default stored value** + - Example: `chore_due_window_offset` + - Why: this is the confirmed ESV06 failure shape and the clearest schema-default injection signal. +- **Optional text field in a different section** + - Example: `chore_due_reminder_offset` + - Why: proves the defect is not limited to one section container. +- **Optional synthesized multi-select / fan-out field** + - Example: consolidated `notifications` + - Why: omission must preserve multiple stored booleans, while explicit submission must still rewrite them deterministically. +- **Optional list/collection field or selector-backed field** + - Example candidate: `applicable_days` + - Why: protects against collection replacement/defaulting regressions that differ from simple text offsets. +- **Explicit overwrite control** + - Use one representative field with a changed submitted value. + - Why: proves the fix preserves omitted values without breaking intentional updates. +- **Explicit clear control** + - Use an existing field with a supported clear semantic, such as due date clearing. + - Why: proves omission and deliberate clearing remain distinct after remediation. + +This coverage model is the bar for Phase 3B. The goal is not to add a nearly identical test for every optional chore field. The goal is to prove the defect class is closed across the main input patterns that matter. + +### Why this matters to issue 107 + +This defect is not part of the runtime entity-sync contract itself, but it was exposed during validation of that workstream and it affects the same chore options-flow edit surface. If omitted optional fields are silently reset during edit, users can lose stored schedule or notification behavior even though runtime sync itself works correctly. That is a release-quality defect and should block rollout signoff. + +## Implementation approach + +### Step 1 - Add tests before code changes + +Add targeted regression coverage first. Do not start remediation until these tests exist and fail for the right reason. + +1. In `tests/test_options_flow_per_kid_helper.py` near the ESV06 block: + - Add one test that omits only schedule optional fields while keeping advanced fields explicit. + - Add one test that omits only advanced optional fields while keeping schedule fields explicit. + - Add one test that omits the consolidated notifications selector and verifies stored notification booleans are preserved. + - Add one explicit-overwrite control test proving a user-provided new due-window value replaces the stored value. + - Prefer extending a small number of high-signal scenarios over adding many nearly identical field-by-field tests. +2. In `tests/test_options_flow_entity_crud.py`: + - Add a broader options-flow edit test that starts with stored non-default optional values, edits a chore with a sparse payload, and verifies the stored values remain intact. + - Add at least one system-settings or non-chore control assertion proving this remediation stays chore-edit scoped. + - Use this file for pattern-level coverage, not exhaustive duplication of helper-file assertions. +3. In `tests/AGENT_TEST_CREATION_INSTRUCTIONS.md` only after the fix is proven: + - Add guidance that sparse edit tests must distinguish omission from explicit clear/update behavior. + +### Step 2 - Audit the edit boundary + +Inspect and document the exact behavior of these call sites: + +1. `helpers/flow_helpers.py` around lines 919-1037: + - Identify each optional field that receives a backend default during schema creation. + - Classify whether that default is safe for create only, safe for edit, or unsafe for preserve-on-omit edit semantics. +2. `options_flow.py` around lines 1385-1394: + - Verify what payload shape `async_step_edit_chore()` receives from Home Assistant after sparse section submission. + - Determine whether omitted optional keys are absent or already materialized with default values. +3. `helpers/flow_helpers.py` around lines 1084-1565: + - Confirm validation and transform logic should remain preservation-aware and not be polluted with UI display defaults. + +### Step 3 - Fix at one shared layer + +Preferred remediation order: + +1. **Best option**: Separate create-form defaults from edit-form preserve semantics. + - Keep defaults for create UX. + - Avoid backend schema defaults for edit fields where omission must preserve stored values. +2. **Second option**: Normalize sparse edit payloads immediately before validation. + - Strip schema-injected default values for fields that were not explicitly changed. + - This must be centralized and deterministic, not handwritten per field in multiple callers. +3. **Avoid**: + - per-field patch branches in transform logic + - special-case wrappers in `update_chore()` + - test-only accommodations that hide production behavior + +### Step 4 - Prove the fix + +Required validation sequence after remediation: + +1. `./utils/quick_lint.sh --fix` +2. `python -m pytest tests/test_options_flow_per_kid_helper.py -v --tb=line` +3. `python -m pytest tests/test_options_flow_entity_crud.py -v --tb=line` +4. Re-run the previously green runtime-sync regression set: + - `python -m pytest tests/test_chore_crud_services.py tests/test_options_flow_entity_crud.py tests/test_options_flow_per_kid_helper.py::TestPerAssigneeHelperAdd::test_pkh02_helper_completion_does_not_mark_deferred_reload tests/test_options_flow_daily_multi.py::TestDailyMultiOptionsFlow::test_of_03_helper_completion_does_not_mark_deferred_reload -v --tb=line` + +Phase 4 may only resume once the new sparse-edit tests pass and the earlier runtime-sync validation remains green. + +## Completion notes + +- The final remediation did not change `data_builders.build_chore()` or the service-layer builder contract. +- The final remediation did not change create-flow schema behavior in config flow or options-flow add forms. +- The fix was intentionally scoped to chore edit-form schema generation by enabling preserve-on-omit behavior only on the edit call sites in `options_flow.py`. +- Validation completed successfully: + - `./utils/quick_lint.sh --fix` passed on 2026-04-12. + - `python -m pytest tests/test_chore_runtime_entity_sync.py tests/test_chore_crud_services.py tests/test_options_flow_entity_crud.py tests/test_options_flow_per_kid_helper.py tests/test_options_flow_daily_multi.py -v --tb=line` passed on 2026-04-12 (`100 passed`). + +## Acceptance criteria + +1. ESV06 passes without weakening its preserve-on-omit contract. +2. Additional sparse-edit tests prove the breadth of the defect is understood and closed across representative field patterns. +3. Explicit user edits still overwrite stored values. +4. Explicit clear semantics remain distinct from omission semantics. +5. No field-specific wrappers or compatibility shims are introduced. +6. Existing chore runtime-sync behavior remains green after the sparse-edit fix. + +## Notes & follow-up + +- This Phase 3B plan is intentionally test-first because the current evidence suggests a systemic edit-boundary defect rather than a one-off field bug. +- The test strategy is intentionally pattern-based. The objective is to cover the main classes of optional edit behavior with a compact, high-signal suite rather than an exhaustive per-field matrix. +- If the audit proves Home Assistant always materializes defaults for omitted optional fields in sectioned edit forms, ChoreOps should document a stable mitigation pattern for other domains too, but only after the chore fix is complete. diff --git a/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_TRAPS_AND_OPPORTUNITIES.md b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_TRAPS_AND_OPPORTUNITIES.md new file mode 100644 index 0000000..83f1407 --- /dev/null +++ b/docs/completed/CHORE_DYNAMIC_ENTITY_LIFECYCLE_SUP_TRAPS_AND_OPPORTUNITIES.md @@ -0,0 +1,473 @@ +# Chore dynamic entity lifecycle: traps and opportunities + +## Purpose + +This document is the risk register and implementation guardrail set for `CHORE_DYNAMIC_ENTITY_LIFECYCLE`. + +The target outcome is narrow and strict: + +- Chore create, update, and delete must stop requiring full integration reload. +- Active dashboard pages must remain usable without a manual Lovelace refresh. +- Existing signal-first manager architecture must remain intact. +- System settings and feature-flag reload behavior must stay separate unless explicitly expanded later. + +This is not a speculative architecture note. It is a practical list of the places an implementation can fail. + +## Non-negotiable invariants + +1. Manager-owned writes stay manager-owned. + The write path remains `modify -> persist -> notify -> emit` inside the owning manager. Services, options flow, and runtime entity sync helpers must not mutate `coordinator._data` directly. + +2. Runtime entity synchronization is not a substitute for persistence. + The entity graph must be synchronized only after the manager write has completed successfully. + +3. System settings reload behavior stays intact. + `config_entry.options` changes such as points label, update interval, and conditional entity flags continue to use integration reload unless explicitly separated in a follow-up initiative. + +4. Existing entities and new entities are different problems. + `async_update_listeners()` updates existing entities. It does not materialize missing ones. Any plan that treats those as equivalent will fail. + +5. Delete and update must be treated as entity-graph operations when assignments or criteria change. + Create is not the only graph mutation. Assignment edits, independent/shared transitions, and rename-sensitive constructor fields also affect runtime behavior. + +6. The dashboard helper is a contract surface, not just a convenience sensor. + If helper payloads point to missing or stale entity IDs, the dashboard can degrade even when backend writes are technically correct. + +7. Test-mode behavior must not remain more capable than production behavior. + The current create path already diverges by allowing direct sensor creation in tests while production reloads. The new design must remove that behavioral split. + +## What already works and should be reused + +### 1. Manager lifecycle contract already exists + +`custom_components/choreops/managers/chore_manager.py` already does the hardest backend part correctly: + +- `create_chore()` persists and emits `SIGNAL_SUFFIX_CHORE_CREATED` +- `update_chore()` persists, performs targeted orphan cleanup, and emits `SIGNAL_SUFFIX_CHORE_UPDATED` +- `delete_chore()` removes registry entities, cleans owned data, persists, and emits `SIGNAL_SUFFIX_CHORE_DELETED` + +Opportunity: + +- The implementation does not need a new CRUD architecture. +- The missing layer is runtime entity graph synchronization, not domain write logic. + +### 2. Calendar already models the right read-path pattern + +`custom_components/choreops/calendar.py` subscribes to chore mutation signals, invalidates caches, and writes state. + +Opportunity: + +- This proves that not every consumer needs explicit orchestration. +- Any surface that derives from coordinator data and registry lookups may only need signal/listener confirmation, not custom sync code. + +### 3. Selector surfaces are already mostly dynamic + +`custom_components/choreops/select.py` builds chore options from `coordinator.chores_data` on access. + +Opportunity: + +- Selectors likely do not need new entity creation for chore CRUD. +- They need validation that existing entities stay registered and that option lists reflect data changes immediately. + +### 4. Delete cleanup primitives already exist + +`remove_entities_by_item_id()`, `remove_orphaned_assignee_chore_entities()`, and `remove_orphaned_shared_chore_sensors()` already solve most removal cases. + +Opportunity: + +- Removal should be conservative and reuse these helpers. +- Most new work is on add-path symmetry, not delete-path invention. + +## Primary trap inventory + +### Trap 1: Production and test behavior diverge today + +Where: + +- `custom_components/choreops/coordinator.py` +- `custom_components/choreops/services.py` +- `custom_components/choreops/sensor.py` + +Current behavior: + +- Test mode can call `create_chore_entities()` directly. +- Production still reloads via `async_sync_entities_after_service_create()`. + +Why this is dangerous: + +- Tests can pass while production still depends on reload. +- A partial implementation may unintentionally preserve this split and create false confidence. + +Implementation guardrail: + +- Replace the divergence with one production-safe runtime sync path. +- If test-only branches remain, they should exercise the same runtime sync API, not a stronger alternate path. + +### Trap 2: Button platform has no runtime add callback + +Where: + +- `custom_components/choreops/button.py` + +Current behavior: + +- Buttons are fully setup-time entities. +- New chores or new assignee/chore combinations cannot create claim/approve/disapprove buttons without reload. + +Why this is dangerous: + +- A sensor-only implementation will look partially correct in helper data while still breaking actual button interactions. +- Dashboard payloads may list chores whose backend button entity IDs do not exist. + +Implementation guardrail: + +- Do not mark the feature complete until button creation is handled for chore create and assignment expansion. +- Treat button runtime creation as a first-class requirement, not phase-two polish. + +### Trap 3: Constructor-time name caching can leave stale UI copy + +Where: + +- `custom_components/choreops/button.py` +- `custom_components/choreops/sensor.py` +- `custom_components/choreops/select.py` + +Current behavior: + +- Many entities store `_chore_name`, `_assignee_name`, translation placeholders, or `_points_label` in `__init__`. + +Why this is dangerous: + +- Renames can leave existing entities showing stale names even if state values update. +- A no-reload change can appear broken only on rename paths, which are easy to miss in initial validation. + +Implementation guardrail: + +- Explicitly decide per entity family whether rename correctness requires: + - runtime attribute recomputation from coordinator data, or + - targeted entity replacement, or + - a known limitation documented and deferred. + +### Trap 4: Options flow assumes reload is the safe commit boundary + +Where: + +- `custom_components/choreops/options_flow.py` + +Current behavior: + +- `_reload_needed` is a generic flag used across many entity CRUD paths. +- Main menu entry can trigger deferred reload automatically. +- Chore helper steps store temp state in `_chore_being_edited` and related template fields. + +Why this is dangerous: + +- Removing reload only for chores can interact badly with a global deferred-reload mechanism still used by other entity types. +- A naive change can break options-flow navigation or leave stale temporary state after a live update. + +Implementation guardrail: + +- Separate chore runtime sync from the generic entity-reload flag rather than weakening the generic flag globally. +- Audit each chore step that currently calls `_mark_reload_needed()` and classify whether it actually needs graph sync, state refresh, or nothing. + +### Trap 5: Assignment changes are more complex than create/delete + +Where: + +- `custom_components/choreops/managers/chore_manager.py` +- `custom_components/choreops/options_flow.py` +- `custom_components/choreops/services.py` + +Current behavior: + +- Update paths can add assignees, remove assignees, and switch criteria. +- Orphan cleanup exists for removed combinations. + +Why this is dangerous: + +- If runtime sync handles only brand-new chore creation, updated chores will still miss new assignee-specific sensors or buttons. +- These failures will be silent until someone edits assignments rather than creating a new chore. + +Implementation guardrail: + +- Treat these updates as first-class graph mutations: + - assignee added + - assignee removed + - shared-state sensor introduced + - shared-state sensor removed + +### Trap 6: Completion-criteria transitions are structural, not cosmetic + +Where: + +- `custom_components/choreops/managers/chore_manager.py` + +Current behavior: + +- Criteria transitions route through specialized logic. + +Why this is dangerous: + +- `INDEPENDENT -> SHARED` and `SHARED -> INDEPENDENT` alter which entity families should exist. +- Shared-state sensor presence depends on criteria. + +Implementation guardrail: + +- Runtime sync must accept mutation context, not just a chore ID. +- The sync layer needs to know whether to add or remove a `SystemChoreSharedStateSensor` and whether per-assignee entities changed meaningfully. + +### Trap 7: Dashboard helper payload can expose missing entity IDs + +Where: + +- `custom_components/choreops/sensor.py` in `AssigneeDashboardHelperSensor.extra_state_attributes` + +Current behavior: + +- Helper iterates over `coordinator.chores_data`, then looks up entity IDs from the registry. +- It can include chore entries with `eid=None`. + +Why this is dangerous: + +- Dashboard cards may assume a valid `eid` exists for chore rows. +- Helper freshness depends on both coordinator data and registry state being aligned. + +Implementation guardrail: + +- Live create must ensure registry entities exist before the helper is expected to surface them. +- Live delete and de-assignment must ensure helper payload is rebuilt after registry cleanup. +- Add explicit tests for helper payload integrity, not just raw storage contents. + +### Trap 8: Pending-change flags are not a general chore CRUD signal + +Where: + +- `custom_components/choreops/managers/ui_manager.py` +- `custom_components/choreops/sensor.py` + +Current behavior: + +- UI manager tracks `pending_chore_changed` for approval queue style changes. +- Flags are reset after helper attribute generation. + +Why this is dangerous: + +- These flags are not a safe proxy for all chore CRUD changes. +- A plan that assumes the helper will rebuild for create/delete because pending flags exist is incorrect. + +Implementation guardrail: + +- Ensure helper refresh relies on coordinator entity updates and registry consistency, not on pending-approval flags. + +### Trap 9: Tests already encode current reload assumptions + +Where: + +- `tests/test_chore_crud_services.py` +- `tests/test_options_flow_entity_crud.py` +- `tests/helpers/setup.py` +- `tests/AGENT_TEST_CREATION_INSTRUCTIONS.md` + +Current behavior: + +- Some comments and helper patterns explicitly assume options flow reloads and that fresh coordinators must be reacquired afterward. +- Some service tests still describe sensor creation as incomplete without re-setup. + +Why this is dangerous: + +- A correct runtime-sync implementation will invalidate these assumptions. +- If tests are not updated deliberately, maintainers may “fix” the new behavior back toward reloads to satisfy old test expectations. + +Implementation guardrail: + +- Update tests and test docs in the same workstream. +- Add explicit “no reload required” assertions for chore CRUD. + +### Trap 10: Global reload flag in options flow is shared by many domains + +Where: + +- `custom_components/choreops/options_flow.py` + +Current behavior: + +- `_mark_reload_needed()` is called by chores, badges, rewards, users, and more. + +Why this is dangerous: + +- Removing or changing this mechanism globally for chore work can accidentally destabilize unrelated entity types. + +Implementation guardrail: + +- Limit chore changes to chore-specific branches and leave non-chore reload paths untouched unless separately planned. + +### Trap 11: Registry ordering matters for dashboard-facing consistency + +Where: + +- `custom_components/choreops/sensor.py` +- `custom_components/choreops/select.py` + +Current behavior: + +- Several helper structures depend on registry lookups to produce entity IDs. + +Why this is dangerous: + +- If helper rebuild happens before entity registration settles, the helper can momentarily return missing IDs. + +Implementation guardrail: + +- Runtime sync should finish entity add/remove work before relying on helper attribute rebuilds. +- Validation must include `await hass.async_block_till_done()` style sequencing in tests to catch ordering bugs. + +### Trap 12: Runtime sync can overreach into generic infrastructure too early + +Where: + +- `custom_components/choreops/coordinator.py` +- any new helper/module added for runtime sync + +Why this is dangerous: + +- A generic entity synchronization framework is attractive, but it increases blast radius. +- Chores are the highest-value domain, so correctness matters more than elegance on the first pass. + +Implementation guardrail: + +- Keep v1 chore-scoped unless the abstraction emerges naturally after chore success. + +## Opportunity inventory + +### Opportunity 1: This is likely not a schema or migration initiative + +No evidence so far suggests `.storage/choreops/choreops_data` needs to change. + +Value: + +- Lower rollout risk. +- No migration branch needed unless runtime metadata is later introduced. + +### Opportunity 2: Delete path is already closer to final-state than create path + +Delete already removes registry entities and owned data. + +Value: + +- The add path is the main missing capability. +- Implementation effort should concentrate on symmetric creation and update churn. + +### Opportunity 3: Calendar and selectors reduce required code changes + +Some surfaces already re-derive from coordinator state and signals. + +Value: + +- Not every platform needs explicit dynamic creation logic. +- The plan can prioritize button and sensor setup first. + +### Opportunity 4: Helper sensor gives a backend-visible proxy for dashboard correctness + +The dashboard helper already exposes chore lists, entity IDs, core sensor references, and grouped views. + +Value: + +- Backend tests can verify a large portion of dashboard continuity without frontend UI automation. + +### Opportunity 5: Existing orphan-cleanup helpers can be promoted into the runtime contract + +Current cleanup helpers already represent the negative-space definition of valid chore entities. + +Value: + +- They can anchor post-update cleanup after assignment shrinkage or criteria change. + +## Required mutation classes for the runtime sync layer + +The runtime sync layer must be designed around mutation classes, not a single “refresh” action. + +1. Chore created + - add assignee chore status sensors + - add claim/approve/disapprove buttons for assigned assignees + - add shared-state sensor if criteria is shared + +2. Chore deleted + - remove all chore-linked sensors and buttons + - remove shared-state sensor if present + - allow helper/select/calendar surfaces to naturally update from state + registry cleanup + +3. Chore updated without graph change + - no entity add/remove required + - existing entities must refresh state/attributes + - rename path may still need special handling + +4. Chore updated with assignment expansion + - add newly needed assignee-linked entities + +5. Chore updated with assignment shrinkage + - remove orphaned assignee-linked entities + +6. Chore updated with criteria transition + - add or remove shared-state sensor + - validate whether existing assignee-linked entities remain valid or need replacement + +## Recommended implementation sequence + +1. Freeze contract and non-goals. +2. Add production-safe runtime add callbacks for chore sensors. +3. Add production-safe runtime add callbacks for chore buttons. +4. Create one chore-scoped runtime sync API that can handle create and update mutation classes. +5. Convert service handlers to call the new sync API. +6. Validate helper payload and entity registry behavior. +7. Convert options flow chore paths from deferred reload to targeted runtime sync. +8. Update tests and docs that currently assume reload. + +This order is intentional. If options flow is converted first, the work will be harder to reason about because the generic reload system and temporary helper-state mechanics will obscure whether the entity-sync layer is actually correct. + +## Explicit anti-patterns to avoid + +1. Do not replace reload with plain `async_request_refresh()` and call it done. + That only refreshes existing entities. + +2. Do not let services create entities directly while options flow still reloads. + That preserves behavioral split and makes defects harder to reproduce. + +3. Do not remove `_mark_reload_needed()` globally. + It is shared by other domains. + +4. Do not add direct storage writes to services, options flow, or runtime sync helpers. + Managers still own all writes. + +5. Do not assume helper payload correctness from storage correctness alone. + Registry state is part of the contract. + +6. Do not postpone rename behavior analysis until after create/delete are done. + Constructor-cached names will otherwise become a latent correctness bug. + +## Acceptance criteria for “done correctly” + +1. Creating a chore via service does not reload the integration and results in live sensor and button availability for all assigned assignees. +2. Updating a chore assignment adds entities for new assignees and removes entities for removed assignees without reload. +3. Updating completion criteria adds or removes the shared-state sensor correctly. +4. Deleting a chore removes all linked registry entities and helper references without reload. +5. Dashboard helper payload contains valid chore entity IDs after live CRUD operations. +6. Options flow chore CRUD can complete without relying on deferred reload for correctness. +7. System settings and feature-flag changes still use the sanctioned reload path and remain stable. +8. Tests no longer describe production as requiring reload or re-setup for chore sensor creation. + +## Rollout recommendation + +Recommended first release shape: + +- chore-scoped runtime entity sync only +- services and chore options-flow paths included +- no generic multi-domain sync framework +- no config-entry settings refactor +- no schema version change + +Recommended fallback if implementation risk spikes: + +- land service-path runtime sync first +- hold options-flow conversion until the runtime sync layer is proven stable + +That fallback still improves the most automation-friendly path while reducing the chance of entangling chore work with the generic options-flow reload system too early. diff --git a/docs/completed/DASHBOARD_HELPER_HYBRID_SHARDING_COMPLETE.md b/docs/completed/DASHBOARD_HELPER_HYBRID_SHARDING_COMPLETE.md new file mode 100644 index 0000000..fb7688f --- /dev/null +++ b/docs/completed/DASHBOARD_HELPER_HYBRID_SHARDING_COMPLETE.md @@ -0,0 +1,352 @@ +# Initiative snapshot + +- **Name / Code**: Dashboard helper hybrid sharding / `DASHBOARD_HELPER_HYBRID_SHARDING` +- **Target release / milestone**: TBD after prototype and dashboard complexity review; candidate for next dashboard-focused feature release +- **Owner / driver(s)**: TBD +- **Status**: Phase 4 in progress; focused admin template shard support is now validated and Phase 5 audit has identified the main documentation and breaking-change surfaces + +## Summary & immediate steps + +| Phase / Step | Description | % complete | Quick notes | +| --- | --- | --- | --- | +| Phase 1 – Contract review and prototype shape | Define the helper-pointer contract and decide what stays inline vs moves to companion helpers | 100% | Contract, lifecycle, compatibility matrix, and guide updates are documented | +| Phase 2 – Backend helper sharding | Add runtime-owned companion helper surfaces for large chore payloads and auxiliary data | 100% | Runtime shard plans, companion helper sensors, and targeted validation are complete | +| Phase 3 – Shared dashboard snippet migration | Update shared snippets to resolve, merge, and cache chore shards in memory once per card | 100% | Canonical branch `ccpk1/issue124` created; shared chore-engine snippets now merge shard helpers and rebuild labels from merged rows | +| Phase 4 – Testing, thresholds, and rollout | Validate size gains, template responsiveness, and backward compatibility | 90% | Added 120-density acceptance coverage, shard lifecycle/reload/orphan diagnostics tests, a strict one-edit cross-user flip case, a passing full opted-in stress matrix, a verified dev-runtime startup fix, fallback-free shard pointer publication, direct end-to-end `chore_list_1` proof, and focused shard-aware admin template validation; live manual validation and broader template/mixed-load coverage remain open | +| Phase 5 – Documentation, migration, and breaking-change handling | Document contract changes, migration guidance, and direct-helper compatibility impact | 15% | Initial audit complete: first-party guide is mostly aligned, wiki cookbook is the main stale migration surface, and dashboard preference docs appear low-risk | + +1. **Key objective** – Preserve the current dashboard authoring model of “resolve helper once per card, work from local in-memory data” while allowing the main helper to point at an ordered list of additional chore UI helpers under `dashboard_helpers.chore_helper_eids` when the inline `chores` payload becomes too large, so the same contract scales naturally from one shard to many. +2. **Summary of recent work** – + - Reviewed the current helper payload builder in `custom_components/choreops/sensor.py` around lines 4603-5078 and confirmed the main helper currently mixes chores, gamification, UI control, sensor pointers, and approvals in one attribute surface. + - Reviewed the shared chore engine in `custom_components/choreops/dashboards/templates/shared/chore_engine/context_v1.yaml` and `prepare_groups_v1.yaml` around lines 1-260 and confirmed all maintained chore dashboards already normalize a single `chore_list` in one central place. + - Reviewed `custom_components/choreops/dashboards/templates/user-gamification-premier-v1.yaml` around lines 82-130 and 354-430 and confirmed complex dashboards still follow the same core pattern: resolve `dashboard_helper`, pull needed attributes into local variables, then render from that in-memory data. + - Reviewed the helper pointer precedent in `docs/ARCHITECTURE.md` around lines 782-787 and confirmed translation indirection already demonstrates a successful size-reduction pattern via helper pointers. +3. **Next steps (short term)** – + - Phase 3 shared-snippet migration is complete in the canonical dashboard repo and synced into the vendored runtime mirror. + - Maintained chore dashboards now merge shard helper payloads once per card through the shared chore-engine snippets instead of relying on backend `chores_by_label` transport. + - Phase 4 now includes targeted lifecycle validation for shard mode, reload reconstruction, orphan cleanup, runtime diagnostics, and an above-100 acceptance scenario at 120 chores per assignee. + - The opt-in dense stress module now understands shard-backed helpers and the full opted-in stress matrix passes across 40-120 chores per assignee. + - The local dev Home Assistant instance now loads the integration again after correcting the `core/custom_components` symlink and fixing pre-attach shard planning to tolerate an unset entity `hass` reference. + - Live manual validation is no longer blocked by startup or shard-helper publication issues; dashboard responsiveness and authenticated dense-scenario load checks are now the main remaining validation slices. + - The deterministic fallback helper-ID publication path has been removed from `ui_manager`; main-helper shard pointers now resolve only from real registry-backed shard helpers. + - The focused shard suite is green again after adding direct end-to-end coverage for `sensor.zoe_choreops_ui_dashboard_chore_list_1` and aligning lifecycle assertions with fallback-free runtime behavior. + - `admin-shared-v1` and `admin-peruser-v1` now merge shard-backed chore helper rows in their approval and chore-management paths instead of assuming the main helper always carries inline `chores`. + - Focused admin dashboard smoke coverage now proves both shared-admin and per-user admin chore-management views resolve a selected chore from shard-only helper payloads. + - The stale CRUD expectation that still required helper-level `chores_by_label` transport was updated to the current chore-row label contract, and the previously failing targeted test now passes. + - A dedicated follow-on documentation phase is now needed to cover migration guidance, compatibility messaging, and the breaking-change impact for dashboards or automations that read helper attributes directly. + - The first Phase 5 audit found that `docs/DASHBOARD_TEMPLATE_GUIDE.md` is mostly aligned with the shard contract, but it still contains at least one stale inline-chores example that should be rewritten as a shard-aware merge example. + - The wiki cookbook now looks like the largest real-world migration surface because it still teaches several direct `state_attr(helper, 'chores')` setup patterns that will not scale once shard mode activates. + - The dashboard repo preference documents appear comparatively low-risk: they mostly describe UI options and behavior, with no broad pattern of raw helper chore transport assumptions. + - Preserve the closed Phase 1 and Phase 2 contracts as the implementation source of truth; do not reopen approved naming, threshold, or runtime-plan decisions without new evidence. +4. **Risks / blockers** – + - Jinja can merge lists dynamically, but repeated `state_attr()` calls inside loops will hurt dashboard responsiveness if the contract is not designed to resolve shard payloads once per card. + - A multi-helper merge contract is feasible in shared snippets, but debugging broken pointers or partial helper availability will be more complex than the current single-helper model. + - Backward compatibility matters because existing power-user dashboards may read helper attributes directly instead of going through shared snippets. + - If shard discovery becomes too dynamic, template readability and support burden may grow faster than the size benefit. + - Focused backend shard lifecycle coverage is green, but manual dashboard render responsiveness and shared-template regression coverage are still incomplete. + - Dashboard authoring source-of-truth rules matter here: canonical template edits belong in `ccpk1/ChoreOps-Dashboards`, then must be synced into the integration repo via `python utils/sync_dashboard_assets.py` and parity-checked before validation. +5. **References** – + - [docs/ARCHITECTURE.md](../ARCHITECTURE.md) + - [docs/DEVELOPMENT_STANDARDS.md](../DEVELOPMENT_STANDARDS.md) + - [docs/CODE_REVIEW_GUIDE.md](../CODE_REVIEW_GUIDE.md) + - [docs/DASHBOARD_TEMPLATE_GUIDE.md](../DASHBOARD_TEMPLATE_GUIDE.md) + - [tests/AGENT_TEST_CREATION_INSTRUCTIONS.md](../../tests/AGENT_TEST_CREATION_INSTRUCTIONS.md) + - [docs/RELEASE_CHECKLIST.md](../RELEASE_CHECKLIST.md) +6. **Decisions & completion check** + - **Decisions captured**: + - Keep the current dashboard ergonomics: one helper resolution per card, then local in-memory processing. + - Treat shared dashboard snippets as the main migration surface; avoid per-template custom merge logic where possible. + - Use the existing translation-sensor pointer pattern as the architectural precedent for helper indirection. + - Prefer runtime-computed shard pointers over new persistent storage fields unless later requirements force stored shard policy. + - Focus the first iteration on chores, but define the contract so the same sharding pattern can be reused for rewards, badges, approvals, or future helper-heavy surfaces. + - Design the chores contract for multi-helper expansion from day one; do not hard-code a single extra chore helper assumption into helper names, pointer fields, or Jinja merge logic. + - All implementation work for this initiative must continue to adhere to the standing repository contracts in [docs/DEVELOPMENT_STANDARDS.md](../DEVELOPMENT_STANDARDS.md), [docs/DASHBOARD_TEMPLATE_GUIDE.md](../DASHBOARD_TEMPLATE_GUIDE.md), and [docs/ARCHITECTURE.md](../ARCHITECTURE.md) outside the specific helper-sharding changes being introduced. + - Dashboard template source edits for this initiative must be made in `ccpk1/ChoreOps-Dashboards`, then synced into the integration vendored runtime mirror using `python utils/sync_dashboard_assets.py` and verified with `python utils/sync_dashboard_assets.py --check`. + - `user-chores-essential-v1` is an explicit non-goal for scale extension in this initiative; manual and automated validation focus on maintained profiles that are expected to scale further, especially `user-gamification-premier-v1` and `user-chores-standard-v1`. + - Shard pointers for chores will live at `dashboard_helpers.chore_helper_eids`, and that field must always be present as a list, including `[]` when no chore shards exist. + - `chores_by_label` will be removed from backend helper transport and rebuilt from merged `chores` rows in shared dashboard snippets; this change must be validated across all dashboard templates, including `user-chores-essential-v1`, even though essentials remains a non-goal for extended-scale support. + - Chore sharding is threshold-triggered, not always-on, and the contract is mutually exclusive at scale: the helper will use either inline `chores` or shard-backed `chores`, but not both in parallel for the same user state. + - Once sharding is triggered, dedicated chore shard helpers should carry only chore-row payload for this initiative unless a later measurement proves minimal metadata is required for diagnosability. + - Dashboards must consume shard helpers only via the ordered entity IDs exposed by the main helper; helper naming patterns are backend-managed and not part of the dashboard contract. + - For the broader architecture, prefer a reusable shard-helper implementation internally, but keep public shard-helper surfaces typed by payload family so chores, rewards, or future overflow families each retain clear contracts, names, and diagnostics. + - Missing-shard behavior should stay simple in v1: render the data that is available, avoid over-engineering recovery paths, and provide only minimal diagnostics needed for support. + - Whether a user is in inline mode or shard mode must be runtime-owned state, not persisted storage configuration and not something recomputed ad hoc only inside `extra_state_attributes`. + - Shard lifecycle should follow the recent chore runtime-sync approach: reconcile adds/removals through a single mutation-driven orchestration path, then let helper attributes read the resulting runtime plan. + - To avoid threshold flapping for users near the limit, shard-mode activation must use hysteresis with separate enter/exit guards rather than a single exact cutoff. + - Shard lifecycle ownership for v1 is assigned to `ui_manager`; do not introduce a separate dashboard-helper lifecycle manager unless implementation pressure later proves `ui_manager` has become too broad. + - `ui_manager` owns startup reconciliation, reload reconciliation, unload cleanup of runtime shard plans, and post-mutation shard reconciliation for affected users. + - Shard helper unique IDs must be stable across restarts and ordinary edit churn and must not be derived from current chore count, current payload size, or other unstable runtime-only measurements. + - Shard reconciliation must accept all affected user IDs for a mutation, not just the most obvious edited user, so cross-user assignment churn can push one user into shard mode while pulling another out. + - Shard-mode diagnostics must remain intentionally small but must be sufficient for support: current mode, expected shard count, resolved shard entity IDs, and last accepted size measurement or equivalent runtime evidence. + - Chore shard helper naming for v1 is deterministic and typed: backend-managed unique IDs should use a stable chore-shard suffix with an explicit shard index, while human-facing names should follow the equivalent of `Chore List 1`, `Chore List 2`, and so on. + - Shard mode must activate from final exact serialized helper size with hysteresis anchored below the 16 KB recorder ceiling: enter shard mode at or above 14 KB projected/finalized size and exit shard mode only after the accepted payload falls to 12 KB or below. + - Minimal shard-helper diagnostic metadata for v1 is fixed to `purpose`, `shard_index`, `shard_count`, and `helper_contract_version`; size and reconciliation evidence belong on the main helper runtime diagnostic surface, not duplicated heavily on every shard. + - The reusable typed-shard pattern documentation for v1 should name chores as the implemented family and rewards as the explicit next-family example so the pattern is concrete without widening current implementation scope. + - The minimum `ui_manager` shard runtime plan for each user is fixed to mode, ordered shard membership, expected shard count, last accepted serialized size, and last reconciliation outcome. + - **Completion confirmation**: `[ ]` All follow-up items completed (architecture updates, cleanup, documentation, etc.) before requesting owner approval to mark initiative done. + +> **Important:** Keep the entire Summary section (table + bullets) current with every meaningful update (after commits, tickets, or blockers change). Records should stay concise, fact-based, and readable so anyone can instantly absorb where each phase stands. This summary is the only place readers should look for the high-level snapshot. + +## Tracking expectations + +- **Summary upkeep**: Whoever works on the initiative must refresh the Summary section after each significant change, including updated percentages per phase, new blockers, or completed steps. Mention dates or commit references if helpful. +- **Detailed tracking**: Use the phase-specific sections below for granular progress, issues, decision notes, and action items. Do not merge those details into the Summary table—Summary remains high level. + +## Detailed phase tracking + +### Phase 1 – Contract review and prototype shape + +- **Goal**: Define a helper contract that supports threshold-triggered pointer-based chore sharding while preserving the current single-helper dashboard authoring experience below the threshold. +- **Status**: Complete +- **Steps / detailed work items** + 1. [x] Document the current helper field classes in `custom_components/choreops/sensor.py` around lines 4603-5078 as `row data`, `derived indexes`, `auxiliary catalogs`, and `plumbing/meta` so sharding decisions are made against explicit categories. + 2. [x] Document the current shared lookup and caching pattern in `custom_components/choreops/dashboards/templates/shared/chore_engine/context_v1.yaml` around lines 95-186 and `custom_components/choreops/dashboards/templates/shared/chore_engine/prepare_groups_v1.yaml` around lines 1-120 to identify the minimal snippet changes needed to support merged shard lists. + 3. [x] Define the main-helper contract in `docs/DASHBOARD_TEMPLATE_GUIDE.md` around the helper-field section near lines 780-860 with `dashboard_helpers.chore_helper_eids` as the canonical ordered pointer list and require the field to exist even when empty. + 4. [x] Lock the initial chore shard payload to `chores` rows only and explicitly reject parallel inline-plus-shard choreography for the same user state unless later testing proves that a tiny compatibility slice is required. + 5. [x] Document that `chores_by_label` is removed from backend transport, then map the shared-snippet and profile-level template updates required to rebuild label grouping from merged `chores` rows across premier, standard, and essentials templates. + 6. [x] Record the selected trigger model in this plan: threshold-triggered overflow-only chore spillover with mutually exclusive output modes (`inline` below threshold, `shards` above threshold). + 7. [x] Define the minimum supported shard count and scaling expectation for v1 planning, with the contract explicitly supporting at least two extra chore helpers even if the first implementation rarely needs them. + 8. [x] By the end of Phase 1, document the approved helper entity naming pattern, pointer field names, helper-key contract, and worked examples covering at least a 20-chore user and a 120-chore user. + 9. [x] Decide and document the future-family shard model: use one reusable backend shard-helper implementation pattern, but expose typed public shard-helper surfaces for each payload family rather than a single generic overflow sensor contract. + 10. [x] Add a compact compatibility matrix to the plan or guide covering maintained dashboards (`user-gamification-premier-v1`, `user-chores-standard-v1`) and explicit non-goals (`user-chores-essential-v1`) so rollout expectations are fixed before implementation starts. + 11. [x] Define the shard-mode lifecycle contract explicitly in `ui_manager`: where per-user mode/allocation state lives in runtime memory, what mutation classes trigger reconciliation, and the approved hysteresis policy of entering at 14 KB and exiting at 12 KB based on accepted serialized size. + 12. [x] Specify startup, reload, and unload behavior: how `ui_manager` rebuilds shard plans from current coordinator data on setup, how it reconciles registry/runtime mismatches on reload, and how runtime shard plans are cleared on unload. + 13. [x] Specify shard helper identity rules: stable unique ID pattern with typed chore-shard indexing, `Chore List N` display naming, and explicit prohibition on unique IDs that depend on current shard payload content or current item counts. + 14. [x] Record the full reconciliation trigger inventory for v1, including startup, reload, chore create, chore edit, chore delete, assignment churn, and user deletion. + 15. [x] Define the minimum `ui_manager` runtime plan structure for each user as mode, shard membership/order, expected shard count, last accepted serialized size, and last reconciliation outcome. +- **Phase 1 completion update (2026-05-05)** + - Documented the current dashboard-helper payload classes and the shared snippet merge/caching contract in [docs/DASHBOARD_TEMPLATE_GUIDE.md](../DASHBOARD_TEMPLATE_GUIDE.md). + - Wrote the approved shard contract into the guide, including `dashboard_helpers.chore_helper_eids`, shard payload shape, label reconstruction rules, compatibility matrix, worked examples, and future typed-family guidance. + - Converted Phase 1 from open planning items to completed contract deliverables and updated the initiative summary to reflect Phase 1 completion. +- **Key issues** + - The pointer list must be structurally stable, not optional; an always-present empty list is easier for templates than a missing key. + - Threshold-triggered mode switching is acceptable here because the user explicitly wants `inline or shards`, not partial parallel transport. + - If shard order matters for stable UI grouping or deterministic merges, the contract must define ordering explicitly. + - A stable contract is more valuable than the smallest possible implementation. If the trigger rule creates multiple helper shapes that templates must special-case, the operational cost may outweigh the size win. + - Naming approval cannot drift into Phase 2. If helper names and pointer keys are still unsettled when coding starts, backend and dashboard work will diverge quickly. + - Reusing implementation is good, but a generic public “overflow shard” sensor type would blur payload semantics and make future rewards or approvals harder to reason about than typed shard surfaces. + - If shard-mode selection is left inside helper rendering instead of a runtime lifecycle owner, entity creation and cleanup will become nondeterministic and will not meet the same bar as the recent chore runtime-sync work. + - Startup, reload, and unload semantics must be treated as first-class contract items. If those are left implicit, runtime correctness will depend on incidental registry state rather than owned lifecycle behavior. + +### Phase 2 – Backend helper sharding + +- **Goal**: Design backend-owned shard helpers that reduce main helper size without giving up the open, entity-based architecture. +- **Steps / detailed work items** + 1. [x] Prototype a runtime helper layout in `custom_components/choreops/sensor.py` around lines 4119-4478 and 4603-5078 where the existing per-user helper continues to expose core metadata and `dashboard_helpers.chore_helper_eids` can point at an ordered list of additional per-user chore helper sensors. + 2. [x] Introduce one `ui_manager`-owned shard reconciliation path, parallel to chore runtime sync, that accepts affected user IDs after chore graph mutations and updates shard-helper entity presence before listener refresh. + 3. [x] Base shard-helper creation/cleanup on the existing translation-sensor lifecycle pattern where possible, but extend it with explicit per-user runtime plan tracking so shard mode and shard count are not inferred solely from registry lookups. + 4. [x] Define the minimal shard payload as `chores` only for the first release, allowing only minimal diagnostic metadata if support needs prove it is necessary. + 5. [x] Remove `chores_by_label` from backend transport and shift label reconstruction to merged-chores logic in shared snippets, while confirming the full dashboard-template impact including essentials. + 6. [x] Keep non-chore payloads on the main helper for the first release and document that future rewards or other overflow families should follow the same typed-shard pattern rather than reusing chore shard entities. + 7. [x] Define simple failure handling for missing shard helpers so dashboards degrade cleanly when one pointer resolves to `unknown`, `unavailable`, or a stale entity ID without introducing complex recovery behavior. + 8. [x] Define the byte-budget policy for helper emission in `custom_components/choreops/sensor.py` around lines 4603-5078 using the selected hybrid strategy: cheap estimation during assembly plus exact serialized-byte measurement at final emission. + 9. [x] Define hysteresis for shard mode using separate enter/exit thresholds so users near the boundary do not teeter between inline and shard mode on small edits. + 10. [x] Decide whether chore shard allocation should be `spill until full` or `balanced distribution` once more than one extra chore helper is needed, and document why that strategy is easier to reason about for future payload families. + 11. [x] Add deterministic shard-membership rules and shard cleanup rules so ordinary chore edits do not cause unnecessary reshuffling between helpers and helper removal is defined when households shrink back below the shard exit threshold. + 12. [x] Define the approved v1 shard diagnostic metadata surface as `purpose`, `shard_index`, `shard_count`, and `helper_contract_version`, with size and reconciliation evidence exposed from the main helper runtime diagnostics instead of every shard. + 13. [x] Implement startup reconciliation in `ui_manager` so shard plans are rebuilt from coordinator data and registry state is reconciled before dashboard helpers rely on `dashboard_helpers.chore_helper_eids`. + 14. [x] Implement reload/unload lifecycle handling so shard runtime plans are refreshed on reload, stale runtime state is cleared on unload, and orphan helper entities are cleaned up deterministically when needed. + 15. [x] Ensure shard reconciliation accepts and processes all affected user IDs for a mutation, including assignment transfers that can move shard pressure from one user to another in a single edit. + 16. [x] Keep shard helper unique IDs stable and backend-owned, with predictable registry lookup and cleanup behavior even as users cross the shard threshold in both directions. + 17. [x] Make the `ui_manager` shard runtime state family-aware from day one so future reward shard work can reuse the pattern without colliding with chore-specific runtime keys, and document rewards as the next planned typed-family example in the guide. +- **Key issues** + - The backend should not create card-specific helpers; shard helpers must remain general-purpose per-user surfaces. + - A chore-only shard model is easier to adopt than full multi-concern sharding and should be considered the first release candidate. + - No `.storage` schema change should be required if shard policy is derived at runtime from current payload size and helper composition. + - The runtime lifecycle owner should be `ui_manager`, not the dashboard-helper sensor itself. The sensor should read a prepared plan; it should not decide whether entities need to be created or removed. + - An overflow-only spillover model is the smallest behavioral change, but it requires logic to decide exactly where the cut line falls and to keep the main helper under the budget deterministically. + - A sequential paging model is simpler mechanically and generalizes better across payload families, but it makes helper semantics less obvious because data can move between numbered helpers as the household grows. + - Even if the first rollout usually creates only one extra helper, the shard allocator and pointer field should not assume that ceiling. The next 80 chores should be an extension of the same contract, not a second redesign. + - Future reuse should happen at the implementation level, not by publishing one generic shard-sensor contract for all payload families. Public sensor contracts should remain typed so troubleshooting and dashboard expectations stay obvious. + - The recent chore runtime-sync work is the right quality bar here: mutation-driven orchestration, production-safe add/remove behavior, shared cleanup helpers, and no reload-era fallback should be the target shape for shard helpers too. + - Cross-user mutations are part of the contract surface. A reconciliation path that only thinks in terms of one edited user will miss real assignment-churn cases and leave runtime/entity state inconsistent. + +### Phase 3 – Shared dashboard snippet migration + +- **Goal**: Update the maintained dashboard ecosystem to resolve and merge shard helpers once per card so dashboards stay responsive and template complexity stays centralized. +- **Steps / detailed work items** + 1. [x] Create a matching working branch in `ccpk1/ChoreOps-Dashboards` for the canonical template work associated with this integration branch, and record the branch/linkage in the plan summary once created. + 2. [x] Update `custom_components/choreops/dashboards/templates/shared/chore_engine/context_v1.yaml` around lines 95-186 so it reads `dashboard_helpers.chore_helper_eids`, resolves referenced helper entities once, and constructs one merged in-memory `chore_list` before grouping or rendering begins. + 3. [x] Update `custom_components/choreops/dashboards/templates/shared/chore_engine/prepare_groups_v1.yaml` around lines 1-260 so label grouping and sorting operate on merged chore rows and do not assume all chores came from a single helper entity. + 4. [x] Review `custom_components/choreops/dashboards/templates/user-gamification-premier-v1.yaml` around lines 354-430 and the chore-card sections around lines 215-289 to ensure the complex profile can adopt shard-aware shared snippets without bespoke card-local merge logic. + 5. [x] Review `custom_components/choreops/dashboards/templates/user-chores-standard-v1.yaml` and `user-chores-essential-v1.yaml` around their shared-snippet call sites to confirm the migration surface is truly centralized and to verify that removing `chores_by_label` does not break those templates. + 6. [x] Decide whether helper pointer resolution should be capped to a small number of shards per user, such as 2-4, to keep Jinja merge overhead predictable while still supporting the intended “80 becomes 160” expansion path. + 7. [x] Document a fallback behavior in `docs/DASHBOARD_TEMPLATE_GUIDE.md` for legacy dashboards that continue reading `state_attr(dashboard_helper, 'chores')` directly and therefore will not benefit from automatic shard merges once threshold-triggered sharding activates. + 8. [x] Run the required canonical-to-vendored workflow for every template change: update the dashboard repo source, sync into `custom_components/choreops/dashboards/`, and parity-check before integration validation. + 9. [x] Document the template-side assumptions for shard-mode diagnostics and partial-failure handling so shared snippets know exactly which runtime fields are safe to inspect and which missing-shard cases should simply degrade to partial render. +- **Phase 3 completion update (2026-05-05)** + - Created the matching canonical dashboard branch `ccpk1/issue124` in `ccpk1/ChoreOps-Dashboards` for the template migration work. + - Updated the shared chore-engine context snippet to resolve `dashboard_helpers.chore_helper_eids` once, merge shard-backed `chores` rows into one in-memory `chore_list`, and preserve inline mode when no shard helpers are present. + - Updated the shared grouping snippet to rebuild label groups from filtered merged chore rows instead of relying on backend `chores_by_label` transport. + - Confirmed maintained profile call sites remain centralized through the shared snippets, documented the no-cap pointer-resolution rule and legacy fallback behavior, and synced canonical assets into the vendored runtime mirror. +- **Key issues** + - Jinja can merge lists from multiple helpers, but it must do so once into a local namespace variable rather than repeatedly querying helpers during grouping and sorting. + - Because `chores_by_label` is being removed, label-group reconstruction becomes a mandatory shared-snippet responsibility and needs explicit verification across all templates, not just maintained scale targets. + - Shared snippet changes are powerful, but they raise the importance of good debug surfaces when one shard pointer is broken. + - The numbered step list must remain sequential after dashboard-repo branch setup is added; keep the final plan tidy before handoff. + +### Phase 4 – Testing, thresholds, and rollout + +- **Goal**: Prove that the hybrid shard design actually expands capacity while keeping template behavior acceptable and avoiding broad dashboard breakage. +- **Steps / detailed work items** + 1. [x] Extend `tests/test_dashboard_helper_density_stress.py` around lines 24-77 to add shard-aware scenarios once a prototype exists, measuring both recorder-limit compliance and the number of chores supported per assignee under the new contract. + 2. [x] Add scenario coverage using the YAML scenario framework described in `tests/AGENT_TEST_CREATION_INSTRUCTIONS.md` and the existing dense scenarios under `tests/scenarios/` to compare inline-only vs shard-only helper behavior on the same households once the threshold is crossed. + 3. [ ] Add dashboard-template regression tests or compile-time contract tests for the shared snippets so `context_v1` and `prepare_groups_v1` continue to render when shard pointers are absent, present, or partially unavailable. + 4. [ ] Re-run the live-load workflow using `utils/load_test_scenario_to_live_ha.py` with dense scenarios so manual dashboard responsiveness can be evaluated alongside recorder-size wins. + 5. [x] Update `tests/SCENARIOS.md`, `tests/AGENT_TESTING_USAGE_GUIDE.md`, and `docs/DASHBOARD_TEMPLATE_GUIDE.md` with the new helper contract, expected dashboard behavior, the removal of `chores_by_label`, and debugging guidance. + 6. [x] Record a go/no-go threshold for adoption using an explicitly named acceptance scenario that is above 100 chores per assignee in the dense test family without materially degrading dashboard responsiveness, with dense 120 per assignee as the default planning target unless implementation evidence requires a revised signoff number. + 7. [ ] Add a mixed-load stress scenario or validation slice that combines dense chores with realistic labels and auxiliary payloads so the test target is not limited to chore-only density. + 8. [x] Treat “supports more than 100 chores” as a required success criterion for the initiative, not an aspirational target, and record the exact acceptance threshold and scenario shape in the validation notes. + 9. [ ] Obtain manual confirmation in the development Home Assistant environment that the agreed acceptance-threshold setup using the new helper sensors does not break `user-gamification-premier-v1` or `user-chores-standard-v1` dashboard templates. + 10. [ ] Explicitly exclude `user-chores-essential-v1` from the extended-scale acceptance target and document that its practical chore limit remains an acknowledged template-specific constraint. + 11. [x] Add targeted lifecycle validation for users near the threshold so repeated small edits prove shard mode does not flap, orphan helpers are cleaned up correctly, and runtime sync leaves entity registry state stable. + 12. [x] Add startup/reload validation proving users already in shard mode are reconstructed correctly after integration restart or reload and users returned to inline mode do not retain stale shard entities. + 13. [x] Add cross-user mutation validation proving one chore edit can move one user into shard mode while moving another out without leaving stale helper state or broken dashboard pointers. + 14. [x] Add entity-registry-focused validation proving shard helper unique IDs remain stable through ordinary edits, helper count changes only when mode changes require it, and orphan shard entities are removed when users leave shard mode. + 15. [x] Add diagnostics validation covering the minimal shard metadata surface so support tooling can explain current mode, expected shard count, and resolved shard helper entity IDs for affected users. +- **Phase 4 progress update (2026-05-06)** + - Added `tests/scenarios/scenario_density_starblum_120.yaml` and updated `utils/generate_dense_test_scenarios.py` so the dense scenario family now extends beyond 100 chores per assignee. + - Added focused shard validation in `tests/test_dashboard_helper_sharding.py` for 120-density acceptance, reload reconstruction, orphan cleanup on inline fallback, threshold-edge cross-user reconciliation, registry stability, and minimal shard diagnostics. + - Updated the focused shard expectations to the translated `UI Dashboard Chore List {shard_index}` naming contract and added explicit coverage that the shared helper attributes publish registered `dashboard_helpers.chore_helper_eids` values. + - Updated `tests/test_dashboard_helper_density_stress.py` to merge shard-backed chores from `dashboard_helpers.chore_helper_eids` so claim-path stress checks remain valid once the main helper no longer carries inline chores. + - Updated `tests/SCENARIOS.md` and `tests/AGENT_TESTING_USAGE_GUIDE.md` to document the 120-density acceptance band and shard-aware validation focus. + - Tightened the cross-user threshold-edge test into a deterministic one-edit move-out/move-in case by using a real oversized swing chore transfer between users near opposite hysteresis boundaries. + - Ran the full opted-in dense stress suite with the default pytest stress exclusion overridden and confirmed all 16 stress cases pass across 40-120 chores per assignee. + - Resolved the local dev-runtime startup regression by fixing the missing top-level `core/custom_components/choreops` link and making helper payload assembly tolerate pre-attach shard planning before `entity.hass` is populated. + - Re-ran the full opted-in dense stress suite after the startup fix and confirmed it still passes at `16 passed` across 40-120 chores per assignee. + - Added a direct end-to-end regression that looks up `sensor.zoe_choreops_ui_dashboard_chore_list_1` and proves its live shard attributes exist before and after reload. + - Fixed the real startup root cause by marking chore shard helpers as always-required in the central entity registry and keeping startup shard creation in the initial sensor entity add path. + - Removed the deterministic `ui_manager` fallback that synthesized shard helper IDs and kept only registry-backed shard pointer publication. + - Re-ran the focused shard suite after the fallback removal and confirmed it passes at `10 passed` with the fallback-free runtime behavior. + - Updated `admin-shared-v1` and `admin-peruser-v1` in the canonical dashboard repo so the admin approval and chore-management paths merge `dashboard_helpers.chore_helper_eids` instead of reading only inline helper `chores`. + - Synced those canonical admin template changes into the vendored runtime dashboard mirror and parity-checked the sync. + - Added focused smoke coverage proving both shared-admin and per-user admin chore-management views can resolve a selected chore from shard-only helper payloads. + - Re-ran `tests/test_dashboard_template_render_smoke.py` and confirmed the updated focused admin/dashboard smoke slice passes at `24 passed`. + - Re-ran the previously failing targeted CRUD test for helper labels after aligning it to the post-`chores_by_label` helper contract and confirmed it passes at `1 passed`. +- **Key issues** + - The current stress suite measures attribute size, not dashboard render latency; manual validation will still matter. + - Backward compatibility needs explicit tests because many dashboards may still assume one helper contains all chore rows inline. + - If the shard-aware template logic becomes too complex relative to the size gain, the simpler “trim and split by concern” path may remain the better default. + - Passing at exactly 100 chores is not enough for this initiative if the user requirement is “more than 100”; the threshold and scenario must be written precisely enough to avoid ambiguity during sign-off. + - Because the contract changes from possible inline chores to shard-only chores above threshold, validation must prove both states render correctly and that dashboards do not rely on mixed transport. + - Threshold-edge validation is required, not optional. If users can teeter around the guard, platinum-level entity handling depends on hysteresis and deterministic cleanup behaving correctly under edit churn. + - Platinum-level signoff here depends on lifecycle validation as much as payload-size validation. Restart, reload, registry cleanup, and cross-user mutation behavior are part of the acceptance bar, not secondary follow-up work. + - Admin dashboards were a real compatibility gap even after the shared chore-engine migration because they still performed direct helper chore lookups outside the shared snippets. + - The broader breaking-change surface is now documentation-heavy: dashboards, cookbook examples, power-user template snippets, and any direct helper consumers need explicit migration guidance once `chores_by_label` and inline-only assumptions are no longer safe at scale. + +## Testing & validation + +- Planned validation commands: + - `./utils/quick_lint.sh --fix` + - `mypy custom_components/choreops/` + - `mypy tests/` + - `python utils/sync_dashboard_assets.py` + - `python utils/sync_dashboard_assets.py --check` + - `python -m pytest tests/test_dashboard_helper_density_stress.py -o addopts='' -m stress` + - `python -m pytest tests/ -v --tb=line` +- Tests executed: + - `./utils/quick_lint.sh --fix` ✅ + - `python -m pytest tests/test_dashboard_template_contract.py tests/test_dashboard_template_render_smoke.py tests/test_sync_dashboard_assets.py -v --tb=line` ✅ (`29 passed`) + - `python -m pytest tests/test_dashboard_helper_sharding.py -v --tb=line` ✅ (`2 passed`) + - `./utils/quick_lint.sh --fix` ✅ (Phase 2 backend implementation rerun) + - `python -m pytest tests/test_dashboard_helper_sharding.py -v --tb=line` ✅ (`7 passed`) + - `CHOREOPS_RUN_STRESS=1 python -m pytest -o addopts='' -m stress tests/test_dashboard_helper_density_stress.py -v --tb=line` ✅ (`16 passed`) + - `./utils/quick_lint.sh --fix` ✅ (Phase 4 targeted validation rerun; includes mypy and boundary checks) + - `python -m pytest tests/test_dashboard_helper_sharding.py -q` ✅ (`7 passed`) after the pre-attach `hass` fallback fix + - `CHOREOPS_RUN_STRESS=1 python -m pytest -o addopts='' -m stress tests/test_dashboard_helper_density_stress.py -v --tb=line` ✅ (`16 passed`) after the startup fix + - `python -m pytest tests/test_dashboard_helper_sharding.py::TestDashboardHelperSharding::test_end_to_end_setup_and_reload_expose_chore_list_1_attributes -q` ✅ (`1 passed`) after removing the deterministic fallback path + - `python -m pytest tests/test_dashboard_helper_sharding.py -q` ✅ (`10 passed`) after removing the deterministic fallback path and keeping lifecycle assertions aligned to real registry-backed shard publication + - `python utils/sync_dashboard_assets.py` ✅ after the admin template shard-support updates + - `python utils/sync_dashboard_assets.py --check` ✅ after the admin template shard-support updates + - `python -m pytest tests/test_dashboard_template_render_smoke.py -q` ✅ (`24 passed`) after adding shared-admin and per-user admin shard regressions + - `python -m pytest tests/test_chore_crud_services.py::TestCreateChoreEndToEnd::test_created_chore_dashboard_helper_attributes_match_input -v --tb=line` ✅ (`1 passed`) after removing the stale `chores_by_label` expectation +- Outstanding tests: Manual live-load validation, mixed-load acceptance validation, template-side shard-pointer absence/partial-availability regression coverage, and the Phase 5 documentation/migration updates remain pending. + +## Phase 4 validation notes + +- **Acceptance threshold**: Dense 120 chores per assignee is the current explicit Phase 4 acceptance-band target for “supports more than 100 chores”. +- **Automated evidence added**: + - Main helper stays below the 16 KB recorder ceiling in shard mode for the 120-density acceptance scenario. + - Users already in shard mode reconstruct correctly after reload with stable shard helper entity IDs. + - Ordinary edits do not flap shard mode, and shrinking back to inline removes orphan shard helpers deterministically. + - Main-helper shard diagnostics and shard-helper minimal metadata remain internally consistent. + - A single oversized chore edit can now be validated moving one user out of shard mode while pushing another into shard mode in the same mutation. + - The full opt-in dense stress suite passes across both size checks and representative claim-path behavior from 40 through 120 chores per assignee. +- **Still required for signoff**: + - Manual dashboard responsiveness checks in a live Home Assistant environment. + - Authenticated live-load execution of the dense acceptance scenario in the dev Home Assistant instance. + - Template-side regression coverage for shard-pointer absent/present/partial-availability cases and a mixed-load validation slice. + - Documentation and migration guidance covering the direct-helper compatibility impact, including explicit breaking-change communication for power-user dashboards and cookbook-style helper consumers. + +### Phase 5 – Documentation, migration, and breaking-change handling + +- **Goal**: Document the real compatibility impact of hybrid helper sharding, provide migration guidance for direct-helper consumers, and decide how the breaking-change surface is communicated before release signoff. +- **Status**: In progress +- **Steps / detailed work items** + 1. [ ] Update this initiative plan summary and supporting notes whenever the compatibility or migration story changes so the breaking-change surface remains visible at the top level. + 2. [ ] Audit first-party documentation for helper-contract assumptions, including `docs/DASHBOARD_TEMPLATE_GUIDE.md`, any dashboard-generation technical notes, and any places that still imply helper-level `chores_by_label` transport or guaranteed inline `chores` at scale. + 3. [ ] Audit the wiki and cookbook surfaces for direct helper usage, especially chore examples that currently use `state_attr(helper, 'chores')` or helper-level label indexes, and record which examples need shard-aware replacement patterns. + 4. [ ] Define and document the supported migration pattern for direct-helper consumers: resolve the main helper once, inspect `dashboard_helpers.chore_helper_eids`, merge shard-backed chore rows once per card/template, and rebuild any derived label grouping locally from merged rows. + 5. [ ] Explicitly document the breaking-change contract: `chores_by_label` is no longer guaranteed on the helper, and above threshold the main helper may omit inline `chores` in favor of shard pointers. + 6. [ ] Decide where release-facing breaking-change communication belongs for this repo set, including README/release notes/wiki callouts and any dashboard registry or release-checklist notes needed before ship. + 7. [ ] Add or update troubleshooting guidance for partial shard availability, stale direct-helper assumptions, and how to identify the active helper mode from runtime attributes or diagnostics. + 8. [ ] Document the explicit non-goal boundary for templates such as `user-chores-essential-v1` so scale expectations and migration expectations do not drift together. + 9. [ ] Decide whether any compatibility bridge or deprecation window is warranted for common direct-helper patterns, or whether the contract should be treated as an immediate hard break with documentation-only mitigation. +- **Phase 5 progress update (2026-05-06)** + - Audited first-party docs, the wiki cookbook, and the dashboard repo preference markdown files for direct-helper assumptions tied to inline `chores`, helper-level `chores_by_label`, or missing `dashboard_helpers.chore_helper_eids` awareness. + - Confirmed `docs/DASHBOARD_TEMPLATE_GUIDE.md` already documents the shard contract well overall, including the compatibility matrix and the legacy-consumer warning, but it still contains at least one stale example that iterates directly over `state_attr(config.entity, 'chores')` and should be replaced with the supported shard-aware merge pattern. + - Confirmed the main stale migration surface is the wiki cookbook page `Tips-&-Tricks:-Template-Cookbook-for-Chores,-Rewards,-and-Approvals.md`, which still teaches direct single-helper and multi-helper `state_attr(helper, 'chores')` accumulation patterns. + - Confirmed the dashboard repo preference documents are mostly descriptive configuration references rather than code-pattern guides; they currently appear low-risk for helper-contract breakage and do not show a broader pattern of raw inline-chore assumptions. + - Confirmed at least one in-process dashboard doc outside the main initiative plan still references `dashboard_helper.chores` and `dashboard_helper.chores_by_label` as dynamic sources, so the Phase 5 sweep needs to include in-process design docs where those references matter for future work. +- **Key issues** + - This is not only a scale enhancement; it changes what third-party or power-user dashboards can safely assume about the main helper payload. + - The highest-risk breakage is likely outside the maintained shared snippets: custom dashboards, cookbook snippets, and one-off automations that read helper attributes directly. + - Documentation needs to separate two ideas cleanly: maintained shard-aware templates that remain supported, and direct-helper patterns that may now require migration at higher chore densities. + - Release communication has to be explicit enough that users understand why dashboards may appear to "lose chores" if they only read inline helper `chores` and ignore `dashboard_helpers.chore_helper_eids`. + - The most useful remediation target is not the dashboard preference docs; it is the cookbook/setup-block guidance that users are likely to copy into template sensors and custom cards. + - First-party guide drift is now more subtle than total: the risk is isolated stale examples inside otherwise-correct contract documentation, which can be more misleading than a fully outdated section because readers assume the surrounding page is authoritative. + +## Notes & follow-up + +- **Recommended first prototype**: threshold-triggered chore spillover with multi-helper support baked into the contract. Keep the current main helper contract and payload shape as intact as possible below the guard, expose `dashboard_helpers.chore_helper_eids` as an always-present list, enforce a conservative size guard around 14 KB of serialized attributes, and when the chore payload would push the helper past that guard, switch to ordered dedicated chore helpers while the main helper retains stable plumbing and does not carry a parallel inline chore slice. +- **Candidate strategy comparison**: + - **Option A – Overflow spillover from the current helper**: + - Main helper remains the canonical dashboard entry point and retains `core_sensors`, `dashboard_helpers`, `ui_control`, identity fields, and any small inline surfaces. + - When the payload estimate exceeds the guard, chore rows are moved into dedicated extra helper entities and the main helper stops carrying inline `chores` for that state. + - Best match for backward compatibility because the main helper stays conceptually the same. + - Primary cost is cutoff logic: the backend must choose a deterministic split point that leaves enough headroom for fixed helper metadata and future small payload growth. + - If designed as an ordered helper list rather than a single `extra_helper`, it scales cleanly from one overflow helper to two or more without changing the template contract. + - **Option B – Sequential helper paging (`_2`, `_3`, ...)**: + - Treat dashboard payload as a paged sequence of helper entities, with the first helper carrying stable identity and plumbing plus as much content as fits. + - Easier to reason about mechanically because the splitter just fills helper pages in order. + - More reusable across other payload families because it is not chore-specific. + - Higher support cost because numbered helper semantics are less self-describing and direct-helper consumers may find debugging harder. + - **Option C – Always-sharded chore helpers**: + - The main helper always points to chore helper entities even for small households. + - Simplest template contract because there is one way to read chores regardless of scale. + - Largest migration step because even small households stop getting a fully self-contained chore list inline. +- **Current draft recommendation**: + - Try **Option A** first, but implement it as an ordered-list shard contract rather than a single-extra-helper special case. + - That preserves today’s mental model, limits dashboard churn, and still gives an immediate path from roughly one chore shard to two or more when households move from ~80 chores toward ~160. + - Design the pointer field and merge logic so **Option B** remains available later if you want a more generic paging model for rewards, badges, approvals, or other helper-heavy sections. +- **Recommended initial non-goals**: + - Do not introduce card-specific helper entities. + - Do not make shard discovery depend on raw storage data or hidden helper registries. + - Do not move every payload family at once unless early measurements show chore sharding alone is insufficient. + - Do not extend `user-chores-essential-v1` beyond its already-known practical inline template ceiling as part of this initiative. +- **Closed design choices**: + - **Helper naming pattern**: use deterministic typed chore-shard helpers with stable backend unique IDs that include shard index, while dashboards continue to consume only `dashboard_helpers.chore_helper_eids`; human-facing names should follow the equivalent of `Chore List 1`, `Chore List 2`, and so on. + - **Byte threshold and headroom policy**: use final exact serialized helper size as the controlling measurement, enter shard mode at or above 14 KB, and exit shard mode only at or below 12 KB to provide hysteresis below the 16 KB recorder ceiling. + - **Minimal shard diagnostics**: keep shard-helper diagnostics to `purpose`, `shard_index`, `shard_count`, and `helper_contract_version`; keep current mode, resolved shard entity IDs, last accepted serialized size, and last reconciliation outcome on the main helper runtime diagnostic surface. + - **Named future payload families**: document chores as the implemented typed-shard family and rewards as the explicit next-family example for the reusable pattern, without widening v1 implementation scope beyond chores. + - **`ui_manager` runtime plan shape**: store per-user mode, ordered shard membership, expected shard count, last accepted serialized size, and last reconciliation outcome. +- **Effort estimate**: + - Chore-only shard prototype through shared snippets: medium. + - Chore shards plus concern-based helper split: medium-high. + - Full backward-compatible dual-contract support for legacy direct-helper dashboards: medium-high. +- **Recommendation threshold**: + - Pursue the hybrid design only if the final prototype supports more than 100 chores under the agreed validation scenario without making the shared snippets significantly harder to maintain. + - Prefer a contract that naturally supports the next expansion step to roughly 160 chores via two chore helpers, not one that would require revisiting helper semantics after the first overflow case ships. + - Back down if template complexity, partial-failure handling, or manual render responsiveness becomes worse than the simpler helper-trimming path. + +> **Template usage notice:** Do **not** modify this template. Copy it for each new initiative and replace the placeholder content while keeping the structure intact. Save the copy under `docs/in-process/` with the suffix `_IN-PROCESS` (for example: `MY-INITIATIVE_PLAN_IN-PROCESS.md`). Once the work is complete, rename the document to `_COMPLETE` and move it to `docs/completed/`. The template itself must remain unchanged so we maintain consistency across planning documents. diff --git a/docs/in-process/DASHBOARD_USER_CHORES_LITE_V1_IN-PROCESS.md b/docs/completed/DASHBOARD_USER_CHORES_LITE_V1_COMPLETE.md similarity index 100% rename from docs/in-process/DASHBOARD_USER_CHORES_LITE_V1_IN-PROCESS.md rename to docs/completed/DASHBOARD_USER_CHORES_LITE_V1_COMPLETE.md diff --git a/docs/in-process/POINTS_DECIMAL_PRECISION_BACKEND_IN-PROCESS.md b/docs/completed/POINTS_DECIMAL_PRECISION_BACKEND_COMPLETE.md similarity index 100% rename from docs/in-process/POINTS_DECIMAL_PRECISION_BACKEND_IN-PROCESS.md rename to docs/completed/POINTS_DECIMAL_PRECISION_BACKEND_COMPLETE.md diff --git a/docs/in-process/POINTS_DECIMAL_PRECISION_BACKEND_SUP_BUILDER_HANDOFF.md b/docs/completed/POINTS_DECIMAL_PRECISION_BACKEND_SUP_BUILDER_HANDOFF.md similarity index 100% rename from docs/in-process/POINTS_DECIMAL_PRECISION_BACKEND_SUP_BUILDER_HANDOFF.md rename to docs/completed/POINTS_DECIMAL_PRECISION_BACKEND_SUP_BUILDER_HANDOFF.md diff --git a/docs/in-process/UI_ERROR_SURFACING_AND_KIOSK_DISCOVERABILITY_IN-PROCESS.md b/docs/completed/UI_ERROR_SURFACING_AND_KIOSK_DISCOVERABILITY_COMPLETE.md similarity index 100% rename from docs/in-process/UI_ERROR_SURFACING_AND_KIOSK_DISCOVERABILITY_IN-PROCESS.md rename to docs/completed/UI_ERROR_SURFACING_AND_KIOSK_DISCOVERABILITY_COMPLETE.md diff --git a/docs/in-process/UI_ERROR_SURFACING_AND_KIOSK_DISCOVERABILITY_SUP_ISSUE_DRAFT.md b/docs/completed/UI_ERROR_SURFACING_AND_KIOSK_DISCOVERABILITY_SUP_ISSUE_DRAFT.md similarity index 100% rename from docs/in-process/UI_ERROR_SURFACING_AND_KIOSK_DISCOVERABILITY_SUP_ISSUE_DRAFT.md rename to docs/completed/UI_ERROR_SURFACING_AND_KIOSK_DISCOVERABILITY_SUP_ISSUE_DRAFT.md diff --git a/docs/in-process/UI_ERROR_SURFACING_AUTH_WORDING_INVENTORY_IN-PROCESS.md b/docs/completed/UI_ERROR_SURFACING_AUTH_WORDING_INVENTORY_COMPLETE.md similarity index 100% rename from docs/in-process/UI_ERROR_SURFACING_AUTH_WORDING_INVENTORY_IN-PROCESS.md rename to docs/completed/UI_ERROR_SURFACING_AUTH_WORDING_INVENTORY_COMPLETE.md diff --git a/pyproject.toml b/pyproject.toml index a72a5cd..b383b62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "choreops" -version = "1.0.7" +version = "1.0.8" description = "ChoreOps Home Assistant Integration - Gamified chore tracking for households" readme = "README.md" authors = [{ name = "ChoreOps Contributors" }] diff --git a/tests/AGENT_TESTING_USAGE_GUIDE.md b/tests/AGENT_TESTING_USAGE_GUIDE.md index 639b55e..40d6b2a 100644 --- a/tests/AGENT_TESTING_USAGE_GUIDE.md +++ b/tests/AGENT_TESTING_USAGE_GUIDE.md @@ -59,6 +59,14 @@ python -m pytest tests/test_performance_comprehensive.py -s --tb=short # Custom scenario via environment variable PERF_SCENARIO=scenario_full.yaml python -m pytest tests/test_performance_comprehensive.py -s + +# Dense per-user scenarios (same files used for manual live-load testing) +PERF_SCENARIO=scenario_density_starblum_40.yaml python -m pytest tests/test_performance_comprehensive.py -s +PERF_SCENARIO=scenario_density_starblum_50.yaml python -m pytest tests/test_performance_comprehensive.py -s +PERF_SCENARIO=scenario_density_starblum_100.yaml python -m pytest tests/test_performance_comprehensive.py -s + +# Opt-in dashboard helper density stress tests +CHOREOPS_RUN_STRESS=1 python -m pytest tests/test_dashboard_helper_density_stress.py -s --tb=short ``` ### What the Performance Test Measures @@ -74,8 +82,33 @@ PERF_SCENARIO=scenario_full.yaml python -m pytest tests/test_performance_compreh | -------------------------------- | -------------------------------------------- | ----------------------- | | `scenario_stress.yaml` (default) | 19 assignees, 19 chores, 5 badges (~510 entities) | Standard stress test | | `scenario_full.yaml` | 3 assignees, 18 chores, 2 badges (~236 entities) | Quick integration check | +| `scenario_density_starblum_40.yaml` | 3 assignees, 40 chores each (120 chores total) | First lower-density threshold check | +| `scenario_density_starblum_50.yaml` | 3 assignees, 50 chores each (150 chores total) | Lower-density threshold check | +| `scenario_density_starblum_60.yaml` | 3 assignees, 60 chores each (180 chores total) | Lower-density threshold check | +| `scenario_density_starblum_70.yaml` | 3 assignees, 70 chores each (210 chores total) | Per-user density validation | +| `scenario_density_starblum_80.yaml` | 3 assignees, 80 chores each (240 chores total) | Per-user density validation | +| `scenario_density_starblum_90.yaml` | 3 assignees, 90 chores each (270 chores total) | Per-user density validation | +| `scenario_density_starblum_100.yaml` | 3 assignees, 100 chores each (300 chores total) | High-density helper/manual test | +| `scenario_density_starblum_120.yaml` | 3 assignees, 120 chores each (360 chores total) | Acceptance-band shard validation above 100 chores | | `scenario_minimal.yaml` | 1 assignee, 1 chore | Fastest sanity check | +### Manual live-load workflow + +The dense `scenario_density_starblum_*.yaml` files are committed fixtures and can +be loaded directly into a dev Home Assistant instance with the existing loader: + +```bash +python utils/load_test_scenario_to_live_ha.py \ + --scenario tests/scenarios/scenario_density_starblum_50.yaml \ + --reset +``` + +To regenerate the dense scenario YAMLs after updating the template utility: + +```bash +python utils/generate_dense_test_scenarios.py +``` + ### Performance Thresholds Limits scale with entity count: diff --git a/tests/AGENT_TEST_CREATION_INSTRUCTIONS.md b/tests/AGENT_TEST_CREATION_INSTRUCTIONS.md index 67b8a08..107da9c 100644 --- a/tests/AGENT_TEST_CREATION_INSTRUCTIONS.md +++ b/tests/AGENT_TEST_CREATION_INSTRUCTIONS.md @@ -14,7 +14,7 @@ - **Read source code FIRST**: Know what step IDs a flow returns, what methods are called, what data structures are used - **Check return values**: Options flows return to `async_step_init()` after entity add, not `manage_entity` (example: [options_flow.py](../custom_components/choreops/options_flow.py) line 397) -- **Understand data flow**: After options flow changes, integration reloads → old coordinator references become stale +- **Understand data flow**: Some options flow changes still reload the integration, but chore CRUD helper paths no longer do. Check the specific flow before assuming coordinator references become stale. ### 2. Use Established Patterns @@ -30,7 +30,7 @@ Before writing each test file: - [ ] Identify the exact flow step sequence and return values - [ ] Check existing tests for coordinator access patterns - [ ] Verify field names match what the schemas expect -- [ ] Understand reload behavior and stale reference risks +- [ ] Understand which exact flow paths still reload and which now stay on the live runtime-sync path ### 4. Leverage Existing Infrastructure @@ -813,4 +813,4 @@ _For family background, see `README.md`._ 6. **Use SetupResult** - access `coordinator`, `assignee_ids`, `chore_ids` by name 7. **Mock notifications** - `patch.object(coordinator.notification_manager, "notify_assignee", new=AsyncMock())` 8. **Pass user context** - service calls need `context=Context(user_id=...)` -9. **Get fresh coordinator after reload** - use `config_entry.runtime_data` pattern +9. **Get fresh coordinator after reload-backed flows** - use `config_entry.runtime_data` when the specific path actually reloads diff --git a/tests/SCENARIOS.md b/tests/SCENARIOS.md index 8993748..a6bdba8 100644 --- a/tests/SCENARIOS.md +++ b/tests/SCENARIOS.md @@ -22,6 +22,26 @@ Quick reference for selecting the right YAML scenario for your test. **Content**: All features, complex relationships **Use for**: Comprehensive integration tests, badge systems, full dashboard +### `scenario_density_starblum_40.yaml` to `scenario_density_starblum_120.yaml` + +**Family**: Complete Stårblüm family +**Content**: 3 assignees with 40, 50, 60, 70, 80, 90, 100, or 120 independent chores each +**Use for**: Dashboard helper density checks, per-user scaling analysis, manual dev loading + +These scenarios are generated by: + +```bash +python utils/generate_dense_test_scenarios.py +``` + +They are also designed to work with the live loader utility: + +```bash +python utils/load_test_scenario_to_live_ha.py \ + --scenario tests/scenarios/scenario_density_starblum_50.yaml \ + --reset +``` + ### `scenario_notifications.yaml` **Family**: Notification-optimized setup @@ -49,6 +69,7 @@ Additional scenarios exist for specific test needs (approval resets, chore servi | Time-based features | `scheduling` | Due dates, recurring, overdue | | Config/Options flows | `minimal` | Simple setup, clear validation | | Performance testing | `full` | Maximum entity load | +| Helper density limits | `scenario_density_starblum_*.yaml` | High chores-per-user validation | ## Usage Pattern diff --git a/tests/scenarios/scenario_density_starblum_100.yaml b/tests/scenarios/scenario_density_starblum_100.yaml new file mode 100644 index 0000000..dda21c2 --- /dev/null +++ b/tests/scenarios/scenario_density_starblum_100.yaml @@ -0,0 +1,3929 @@ +system: + points_label: Star Points + points_icon: mdi:star +assignees: +- name: Zoë + ha_user: assignee1 + dashboard_language: en +- name: Max! + ha_user: assignee2 + dashboard_language: en +- name: Lila + ha_user: assignee3 + dashboard_language: en +approvers: +- name: Môm Astrid Stårblüm + ha_user: approver1 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +- name: Dad Leo Stårblüm + ha_user: approver2 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +chores: +- name: Zoë Dense Chore 001 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 002 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 003 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 004 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 005 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 006 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 007 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 008 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 009 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 010 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 011 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 012 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 013 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 014 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 015 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 016 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 017 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 018 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 019 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 020 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 021 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 022 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 023 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 024 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 025 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 026 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 027 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 028 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 029 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 030 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 031 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 032 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 033 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 034 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 035 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 036 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 037 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 038 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 039 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 040 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 041 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 042 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 043 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 044 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 045 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 046 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 047 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 048 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 049 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 050 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 051 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 052 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 053 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 054 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 055 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 056 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 057 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 058 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 059 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 060 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 061 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 062 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 063 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 064 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 065 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 066 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 067 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 068 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 069 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 070 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 071 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 072 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 073 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 074 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 075 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 076 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 077 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 078 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 079 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 080 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 081 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 81 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 082 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 82 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 083 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 83 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 084 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 84 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 085 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 85 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 086 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 86 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 087 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 87 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 088 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 88 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 089 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 89 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 090 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 90 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 091 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 91 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 092 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 92 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 093 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 93 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 094 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 94 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 095 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 95 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 096 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 96 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 097 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 97 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 098 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 98 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 099 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 99 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 100 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 100 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Max! Dense Chore 001 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 002 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 003 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 004 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 005 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 006 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 007 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 008 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 009 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 010 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 011 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 012 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 013 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 014 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 015 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 016 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 017 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 018 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 019 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 020 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 021 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 022 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 023 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 024 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 025 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 026 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 027 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 028 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 029 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 030 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 031 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 032 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 033 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 034 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 035 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 036 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 037 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 038 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 039 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 040 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 041 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 042 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 043 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 044 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 045 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 046 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 047 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 048 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 049 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 050 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 051 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 052 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 053 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 054 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 055 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 056 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 057 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 058 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 059 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 060 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 061 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 062 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 063 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 064 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 065 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 066 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 067 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 068 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 069 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 070 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 071 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 072 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 073 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 074 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 075 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 076 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 077 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 078 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 079 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 080 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 081 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 81 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 082 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 82 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 083 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 83 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 084 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 84 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 085 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 85 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 086 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 86 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 087 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 87 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 088 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 88 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 089 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 89 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 090 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 90 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 091 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 91 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 092 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 92 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 093 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 93 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 094 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 94 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 095 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 95 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 096 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 96 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 097 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 97 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 098 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 98 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 099 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 99 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 100 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 100 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Lila Dense Chore 001 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 002 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 003 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 004 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 005 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 006 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 007 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 008 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 009 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 010 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 011 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 012 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 013 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 014 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 015 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 016 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 017 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 018 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 019 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 020 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 021 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 022 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 023 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 024 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 025 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 026 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 027 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 028 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 029 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 030 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 031 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 032 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 033 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 034 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 035 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 036 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 037 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 038 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 039 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 040 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 041 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 042 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 043 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 044 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 045 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 046 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 047 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 048 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 049 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 050 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 051 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 052 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 053 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 054 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 055 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 056 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 057 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 058 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 059 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 060 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 061 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 062 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 063 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 064 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 065 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 066 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 067 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 068 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 069 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 070 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 071 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 072 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 073 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 074 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 075 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 076 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 077 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 078 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 079 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 080 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 081 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 81 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 082 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 82 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 083 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 83 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 084 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 84 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 085 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 85 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 086 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 86 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 087 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 87 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 088 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 88 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 089 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 89 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 090 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 90 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 091 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 91 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 092 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 92 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 093 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 93 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 094 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 94 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 095 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 95 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 096 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 96 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 097 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 97 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 098 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 98 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 099 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 99 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 100 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 100 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen diff --git a/tests/scenarios/scenario_density_starblum_120.yaml b/tests/scenarios/scenario_density_starblum_120.yaml new file mode 100644 index 0000000..3d14768 --- /dev/null +++ b/tests/scenarios/scenario_density_starblum_120.yaml @@ -0,0 +1,4709 @@ +system: + points_label: Star Points + points_icon: mdi:star +assignees: +- name: Zoë + ha_user: assignee1 + dashboard_language: en +- name: Max! + ha_user: assignee2 + dashboard_language: en +- name: Lila + ha_user: assignee3 + dashboard_language: en +approvers: +- name: Môm Astrid Stårblüm + ha_user: approver1 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +- name: Dad Leo Stårblüm + ha_user: approver2 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +chores: +- name: Zoë Dense Chore 001 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 002 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 003 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 004 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 005 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 006 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 007 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 008 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 009 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 010 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 011 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 012 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 013 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 014 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 015 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 016 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 017 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 018 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 019 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 020 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 021 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 022 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 023 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 024 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 025 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 026 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 027 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 028 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 029 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 030 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 031 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 032 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 033 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 034 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 035 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 036 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 037 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 038 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 039 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 040 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 041 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 042 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 043 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 044 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 045 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 046 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 047 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 048 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 049 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 050 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 051 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 052 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 053 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 054 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 055 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 056 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 057 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 058 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 059 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 060 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 061 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 062 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 063 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 064 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 065 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 066 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 067 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 068 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 069 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 070 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 071 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 072 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 073 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 074 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 075 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 076 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 077 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 078 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 079 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 080 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 081 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 81 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 082 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 82 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 083 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 83 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 084 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 84 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 085 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 85 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 086 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 86 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 087 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 87 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 088 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 88 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 089 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 89 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 090 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 90 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 091 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 91 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 092 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 92 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 093 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 93 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 094 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 94 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 095 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 95 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 096 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 96 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 097 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 97 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 098 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 98 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 099 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 99 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 100 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 100 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 101 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 101 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 102 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 102 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 103 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 103 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 104 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 104 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 105 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 105 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 106 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 106 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 107 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 107 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 108 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 108 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 109 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 109 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 110 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 110 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 111 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 111 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 112 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 112 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 113 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 113 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 114 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 114 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 115 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 115 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 116 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 116 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 117 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 117 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 118 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 118 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 119 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 119 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 120 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 120 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Max! Dense Chore 001 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 002 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 003 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 004 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 005 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 006 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 007 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 008 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 009 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 010 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 011 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 012 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 013 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 014 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 015 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 016 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 017 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 018 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 019 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 020 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 021 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 022 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 023 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 024 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 025 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 026 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 027 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 028 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 029 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 030 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 031 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 032 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 033 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 034 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 035 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 036 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 037 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 038 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 039 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 040 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 041 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 042 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 043 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 044 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 045 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 046 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 047 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 048 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 049 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 050 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 051 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 052 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 053 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 054 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 055 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 056 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 057 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 058 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 059 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 060 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 061 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 062 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 063 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 064 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 065 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 066 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 067 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 068 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 069 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 070 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 071 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 072 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 073 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 074 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 075 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 076 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 077 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 078 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 079 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 080 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 081 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 81 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 082 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 82 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 083 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 83 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 084 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 84 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 085 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 85 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 086 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 86 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 087 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 87 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 088 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 88 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 089 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 89 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 090 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 90 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 091 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 91 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 092 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 92 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 093 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 93 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 094 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 94 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 095 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 95 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 096 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 96 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 097 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 97 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 098 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 98 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 099 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 99 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 100 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 100 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 101 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 101 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 102 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 102 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 103 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 103 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 104 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 104 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 105 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 105 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 106 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 106 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 107 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 107 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 108 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 108 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 109 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 109 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 110 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 110 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 111 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 111 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 112 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 112 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 113 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 113 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 114 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 114 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 115 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 115 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 116 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 116 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 117 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 117 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 118 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 118 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 119 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 119 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 120 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 120 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Lila Dense Chore 001 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 002 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 003 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 004 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 005 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 006 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 007 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 008 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 009 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 010 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 011 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 012 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 013 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 014 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 015 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 016 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 017 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 018 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 019 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 020 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 021 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 022 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 023 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 024 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 025 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 026 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 027 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 028 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 029 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 030 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 031 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 032 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 033 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 034 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 035 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 036 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 037 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 038 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 039 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 040 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 041 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 042 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 043 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 044 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 045 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 046 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 047 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 048 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 049 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 050 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 051 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 052 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 053 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 054 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 055 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 056 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 057 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 058 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 059 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 060 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 061 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 062 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 063 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 064 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 065 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 066 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 067 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 068 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 069 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 070 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 071 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 072 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 073 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 074 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 075 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 076 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 077 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 078 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 079 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 080 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 081 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 81 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 082 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 82 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 083 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 83 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 084 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 84 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 085 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 85 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 086 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 86 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 087 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 87 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 088 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 88 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 089 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 89 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 090 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 90 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 091 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 91 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 092 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 92 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 093 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 93 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 094 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 94 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 095 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 95 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 096 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 96 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 097 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 97 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 098 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 98 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 099 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 99 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 100 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 100 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 101 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 101 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 102 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 102 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 103 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 103 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 104 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 104 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 105 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 105 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 106 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 106 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 107 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 107 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 108 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 108 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 109 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 109 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 110 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 110 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 111 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 111 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 112 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 112 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 113 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 113 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 114 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 114 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 115 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 115 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 116 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 116 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 117 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 117 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 118 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 118 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 119 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 119 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 120 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 120 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom diff --git a/tests/scenarios/scenario_density_starblum_40.yaml b/tests/scenarios/scenario_density_starblum_40.yaml new file mode 100644 index 0000000..19bc209 --- /dev/null +++ b/tests/scenarios/scenario_density_starblum_40.yaml @@ -0,0 +1,1589 @@ +system: + points_label: Star Points + points_icon: mdi:star +assignees: +- name: Zoë + ha_user: assignee1 + dashboard_language: en +- name: Max! + ha_user: assignee2 + dashboard_language: en +- name: Lila + ha_user: assignee3 + dashboard_language: en +approvers: +- name: Môm Astrid Stårblüm + ha_user: approver1 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +- name: Dad Leo Stårblüm + ha_user: approver2 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +chores: +- name: Zoë Dense Chore 001 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 002 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 003 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 004 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 005 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 006 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 007 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 008 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 009 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 010 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 011 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 012 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 013 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 014 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 015 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 016 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 017 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 018 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 019 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 020 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 021 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 022 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 023 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 024 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 025 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 026 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 027 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 028 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 029 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 030 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 031 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 032 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 033 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 034 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 035 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 036 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 037 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 038 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 039 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 040 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Max! Dense Chore 001 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 002 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 003 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 004 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 005 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 006 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 007 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 008 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 009 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 010 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 011 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 012 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 013 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 014 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 015 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 016 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 017 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 018 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 019 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 020 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 021 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 022 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 023 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 024 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 025 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 026 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 027 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 028 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 029 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 030 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 031 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 032 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 033 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 034 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 035 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 036 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 037 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 038 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 039 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 040 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Lila Dense Chore 001 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 002 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 003 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 004 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 005 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 006 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 007 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 008 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 009 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 010 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 011 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 012 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 013 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 014 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 015 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 016 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 017 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 018 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 019 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 020 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 021 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 022 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 023 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 024 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 025 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 026 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 027 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 028 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 029 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 030 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 031 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 032 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 033 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 034 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 035 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 036 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 037 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 038 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 039 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 040 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen diff --git a/tests/scenarios/scenario_density_starblum_50.yaml b/tests/scenarios/scenario_density_starblum_50.yaml new file mode 100644 index 0000000..87621dd --- /dev/null +++ b/tests/scenarios/scenario_density_starblum_50.yaml @@ -0,0 +1,1979 @@ +system: + points_label: Star Points + points_icon: mdi:star +assignees: +- name: Zoë + ha_user: assignee1 + dashboard_language: en +- name: Max! + ha_user: assignee2 + dashboard_language: en +- name: Lila + ha_user: assignee3 + dashboard_language: en +approvers: +- name: Môm Astrid Stårblüm + ha_user: approver1 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +- name: Dad Leo Stårblüm + ha_user: approver2 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +chores: +- name: Zoë Dense Chore 001 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 002 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 003 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 004 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 005 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 006 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 007 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 008 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 009 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 010 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 011 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 012 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 013 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 014 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 015 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 016 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 017 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 018 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 019 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 020 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 021 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 022 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 023 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 024 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 025 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 026 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 027 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 028 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 029 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 030 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 031 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 032 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 033 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 034 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 035 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 036 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 037 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 038 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 039 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 040 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 041 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 042 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 043 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 044 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 045 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 046 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 047 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 048 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 049 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 050 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Max! Dense Chore 001 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 002 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 003 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 004 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 005 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 006 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 007 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 008 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 009 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 010 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 011 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 012 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 013 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 014 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 015 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 016 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 017 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 018 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 019 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 020 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 021 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 022 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 023 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 024 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 025 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 026 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 027 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 028 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 029 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 030 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 031 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 032 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 033 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 034 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 035 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 036 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 037 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 038 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 039 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 040 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 041 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 042 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 043 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 044 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 045 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 046 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 047 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 048 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 049 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 050 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Lila Dense Chore 001 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 002 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 003 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 004 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 005 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 006 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 007 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 008 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 009 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 010 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 011 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 012 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 013 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 014 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 015 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 016 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 017 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 018 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 019 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 020 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 021 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 022 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 023 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 024 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 025 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 026 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 027 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 028 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 029 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 030 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 031 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 032 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 033 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 034 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 035 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 036 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 037 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 038 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 039 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 040 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 041 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 042 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 043 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 044 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 045 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 046 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 047 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 048 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 049 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 050 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School diff --git a/tests/scenarios/scenario_density_starblum_60.yaml b/tests/scenarios/scenario_density_starblum_60.yaml new file mode 100644 index 0000000..c3a0789 --- /dev/null +++ b/tests/scenarios/scenario_density_starblum_60.yaml @@ -0,0 +1,2369 @@ +system: + points_label: Star Points + points_icon: mdi:star +assignees: +- name: Zoë + ha_user: assignee1 + dashboard_language: en +- name: Max! + ha_user: assignee2 + dashboard_language: en +- name: Lila + ha_user: assignee3 + dashboard_language: en +approvers: +- name: Môm Astrid Stårblüm + ha_user: approver1 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +- name: Dad Leo Stårblüm + ha_user: approver2 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +chores: +- name: Zoë Dense Chore 001 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 002 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 003 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 004 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 005 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 006 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 007 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 008 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 009 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 010 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 011 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 012 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 013 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 014 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 015 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 016 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 017 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 018 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 019 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 020 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 021 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 022 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 023 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 024 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 025 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 026 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 027 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 028 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 029 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 030 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 031 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 032 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 033 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 034 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 035 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 036 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 037 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 038 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 039 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 040 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 041 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 042 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 043 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 044 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 045 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 046 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 047 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 048 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 049 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 050 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 051 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 052 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 053 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 054 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 055 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 056 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 057 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 058 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 059 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 060 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Max! Dense Chore 001 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 002 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 003 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 004 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 005 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 006 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 007 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 008 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 009 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 010 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 011 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 012 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 013 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 014 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 015 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 016 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 017 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 018 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 019 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 020 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 021 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 022 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 023 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 024 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 025 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 026 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 027 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 028 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 029 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 030 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 031 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 032 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 033 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 034 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 035 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 036 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 037 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 038 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 039 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 040 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 041 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 042 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 043 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 044 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 045 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 046 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 047 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 048 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 049 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 050 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 051 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 052 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 053 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 054 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 055 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 056 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 057 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 058 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 059 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 060 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Lila Dense Chore 001 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 002 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 003 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 004 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 005 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 006 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 007 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 008 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 009 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 010 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 011 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 012 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 013 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 014 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 015 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 016 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 017 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 018 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 019 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 020 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 021 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 022 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 023 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 024 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 025 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 026 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 027 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 028 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 029 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 030 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 031 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 032 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 033 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 034 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 035 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 036 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 037 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 038 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 039 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 040 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 041 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 042 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 043 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 044 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 045 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 046 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 047 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 048 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 049 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 050 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 051 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 052 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 053 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 054 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 055 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 056 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 057 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 058 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 059 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 060 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom diff --git a/tests/scenarios/scenario_density_starblum_70.yaml b/tests/scenarios/scenario_density_starblum_70.yaml new file mode 100644 index 0000000..3149e46 --- /dev/null +++ b/tests/scenarios/scenario_density_starblum_70.yaml @@ -0,0 +1,2759 @@ +system: + points_label: Star Points + points_icon: mdi:star +assignees: +- name: Zoë + ha_user: assignee1 + dashboard_language: en +- name: Max! + ha_user: assignee2 + dashboard_language: en +- name: Lila + ha_user: assignee3 + dashboard_language: en +approvers: +- name: Môm Astrid Stårblüm + ha_user: approver1 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +- name: Dad Leo Stårblüm + ha_user: approver2 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +chores: +- name: Zoë Dense Chore 001 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 002 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 003 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 004 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 005 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 006 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 007 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 008 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 009 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 010 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 011 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 012 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 013 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 014 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 015 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 016 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 017 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 018 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 019 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 020 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 021 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 022 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 023 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 024 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 025 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 026 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 027 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 028 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 029 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 030 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 031 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 032 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 033 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 034 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 035 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 036 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 037 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 038 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 039 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 040 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 041 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 042 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 043 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 044 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 045 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 046 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 047 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 048 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 049 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 050 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 051 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 052 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 053 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 054 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 055 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 056 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 057 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 058 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 059 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 060 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 061 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 062 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 063 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 064 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 065 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 066 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 067 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 068 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 069 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 070 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Max! Dense Chore 001 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 002 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 003 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 004 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 005 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 006 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 007 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 008 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 009 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 010 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 011 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 012 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 013 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 014 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 015 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 016 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 017 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 018 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 019 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 020 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 021 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 022 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 023 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 024 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 025 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 026 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 027 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 028 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 029 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 030 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 031 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 032 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 033 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 034 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 035 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 036 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 037 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 038 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 039 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 040 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 041 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 042 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 043 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 044 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 045 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 046 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 047 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 048 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 049 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 050 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 051 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 052 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 053 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 054 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 055 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 056 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 057 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 058 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 059 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 060 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 061 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 062 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 063 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 064 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 065 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 066 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 067 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 068 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 069 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 070 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Lila Dense Chore 001 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 002 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 003 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 004 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 005 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 006 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 007 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 008 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 009 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 010 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 011 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 012 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 013 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 014 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 015 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 016 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 017 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 018 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 019 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 020 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 021 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 022 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 023 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 024 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 025 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 026 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 027 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 028 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 029 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 030 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 031 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 032 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 033 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 034 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 035 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 036 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 037 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 038 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 039 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 040 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 041 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 042 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 043 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 044 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 045 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 046 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 047 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 048 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 049 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 050 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 051 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 052 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 053 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 054 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 055 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 056 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 057 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 058 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 059 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 060 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 061 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 062 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 063 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 064 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 065 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 066 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 067 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 068 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 069 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 070 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen diff --git a/tests/scenarios/scenario_density_starblum_80.yaml b/tests/scenarios/scenario_density_starblum_80.yaml new file mode 100644 index 0000000..d9b0963 --- /dev/null +++ b/tests/scenarios/scenario_density_starblum_80.yaml @@ -0,0 +1,3149 @@ +system: + points_label: Star Points + points_icon: mdi:star +assignees: +- name: Zoë + ha_user: assignee1 + dashboard_language: en +- name: Max! + ha_user: assignee2 + dashboard_language: en +- name: Lila + ha_user: assignee3 + dashboard_language: en +approvers: +- name: Môm Astrid Stårblüm + ha_user: approver1 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +- name: Dad Leo Stårblüm + ha_user: approver2 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +chores: +- name: Zoë Dense Chore 001 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 002 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 003 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 004 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 005 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 006 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 007 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 008 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 009 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 010 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 011 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 012 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 013 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 014 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 015 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 016 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 017 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 018 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 019 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 020 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 021 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 022 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 023 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 024 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 025 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 026 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 027 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 028 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 029 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 030 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 031 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 032 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 033 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 034 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 035 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 036 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 037 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 038 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 039 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 040 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 041 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 042 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 043 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 044 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 045 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 046 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 047 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 048 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 049 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 050 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 051 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 052 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 053 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 054 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 055 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 056 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 057 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 058 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 059 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 060 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 061 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 062 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 063 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 064 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 065 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 066 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 067 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 068 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 069 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 070 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 071 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 072 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 073 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 074 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 075 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 076 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 077 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 078 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 079 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 080 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Max! Dense Chore 001 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 002 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 003 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 004 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 005 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 006 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 007 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 008 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 009 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 010 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 011 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 012 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 013 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 014 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 015 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 016 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 017 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 018 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 019 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 020 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 021 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 022 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 023 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 024 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 025 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 026 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 027 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 028 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 029 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 030 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 031 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 032 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 033 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 034 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 035 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 036 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 037 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 038 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 039 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 040 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 041 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 042 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 043 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 044 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 045 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 046 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 047 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 048 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 049 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 050 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 051 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 052 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 053 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 054 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 055 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 056 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 057 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 058 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 059 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 060 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 061 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 062 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 063 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 064 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 065 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 066 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 067 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 068 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 069 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 070 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 071 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 072 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 073 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 074 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 075 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 076 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 077 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 078 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 079 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 080 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Lila Dense Chore 001 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 002 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 003 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 004 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 005 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 006 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 007 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 008 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 009 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 010 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 011 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 012 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 013 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 014 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 015 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 016 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 017 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 018 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 019 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 020 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 021 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 022 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 023 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 024 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 025 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 026 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 027 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 028 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 029 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 030 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 031 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 032 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 033 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 034 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 035 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 036 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 037 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 038 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 039 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 040 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 041 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 042 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 043 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 044 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 045 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 046 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 047 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 048 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 049 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 050 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 051 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 052 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 053 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 054 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 055 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 056 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 057 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 058 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 059 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 060 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 061 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 062 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 063 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 064 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 065 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 066 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 067 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 068 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 069 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 070 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 071 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 072 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 073 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 074 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 075 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 076 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 077 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 078 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 079 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 080 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School diff --git a/tests/scenarios/scenario_density_starblum_90.yaml b/tests/scenarios/scenario_density_starblum_90.yaml new file mode 100644 index 0000000..382b1a2 --- /dev/null +++ b/tests/scenarios/scenario_density_starblum_90.yaml @@ -0,0 +1,3539 @@ +system: + points_label: Star Points + points_icon: mdi:star +assignees: +- name: Zoë + ha_user: assignee1 + dashboard_language: en +- name: Max! + ha_user: assignee2 + dashboard_language: en +- name: Lila + ha_user: assignee3 + dashboard_language: en +approvers: +- name: Môm Astrid Stårblüm + ha_user: approver1 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +- name: Dad Leo Stårblüm + ha_user: approver2 + assignees: + - Zoë + - Max! + - Lila + mobile_notify_service: '' +chores: +- name: Zoë Dense Chore 001 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 002 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 003 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 004 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 005 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 006 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 007 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 008 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 009 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 010 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 011 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 012 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 013 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 014 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 015 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 016 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 017 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 018 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 019 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 020 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 021 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 022 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 023 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 024 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 025 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 026 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 027 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 028 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 029 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 030 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 031 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 032 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 033 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 034 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 035 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 036 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 037 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 038 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 039 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 040 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 041 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 042 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 043 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 044 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 045 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 046 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 047 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 048 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 049 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 050 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 051 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 052 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 053 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 054 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 055 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 056 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 057 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 058 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 059 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 060 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 061 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 062 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 063 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 064 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 065 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 066 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 067 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 068 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 069 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 070 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 071 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 072 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 073 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 074 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 075 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 076 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 077 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 078 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 079 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 080 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 081 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 81 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 082 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 82 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 083 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 83 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 084 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 84 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Zoë Dense Chore 085 + assigned_to: + - Zoë + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 85 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bedroom +- name: Zoë Dense Chore 086 + assigned_to: + - Zoë + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 86 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Bathroom +- name: Zoë Dense Chore 087 + assigned_to: + - Zoë + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 87 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Laundry +- name: Zoë Dense Chore 088 + assigned_to: + - Zoë + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 88 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - School +- name: Zoë Dense Chore 089 + assigned_to: + - Zoë + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 89 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Outdoor +- name: Zoë Dense Chore 090 + assigned_to: + - Zoë + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 90 for Zoë + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Kitchen +- name: Max! Dense Chore 001 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 002 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 003 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 004 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 005 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 006 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 007 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 008 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 009 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 010 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 011 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 012 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 013 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 014 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 015 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 016 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 017 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 018 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 019 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 020 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 021 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 022 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 023 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 024 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 025 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 026 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 027 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 028 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 029 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 030 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 031 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 032 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 033 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 034 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 035 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 036 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 037 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 038 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 039 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 040 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 041 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 042 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 043 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 044 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 045 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 046 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 047 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 048 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 049 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 050 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 051 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 052 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 053 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 054 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 055 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 056 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 057 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 058 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 059 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 060 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 061 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 062 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 063 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 064 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 065 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 066 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 067 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 068 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 069 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 070 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 071 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 072 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 073 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 074 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 075 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 076 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 077 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 078 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 079 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 080 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 081 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 81 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 082 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 82 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 083 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 83 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 084 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 84 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Max! Dense Chore 085 + assigned_to: + - Max! + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 85 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Bathroom +- name: Max! Dense Chore 086 + assigned_to: + - Max! + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 86 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - Laundry +- name: Max! Dense Chore 087 + assigned_to: + - Max! + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 87 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - School +- name: Max! Dense Chore 088 + assigned_to: + - Max! + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 88 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Outdoor +- name: Max! Dense Chore 089 + assigned_to: + - Max! + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 89 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Kitchen +- name: Max! Dense Chore 090 + assigned_to: + - Max! + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 90 for Max! + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bedroom +- name: Lila Dense Chore 001 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 1 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 002 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 2 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 003 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 3 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 004 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 4 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 005 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 5 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 006 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 6 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 007 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 7 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 008 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 8 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 009 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 9 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 010 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 10 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 011 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 11 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 012 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 12 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 013 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 13 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 014 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 14 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 015 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 15 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 016 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 16 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 017 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 17 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 018 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 18 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 019 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 19 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 020 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 20 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 021 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 21 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 022 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 22 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 023 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 23 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 024 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 24 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 025 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 25 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 026 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 26 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 027 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 27 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 028 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 28 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 029 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 29 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 030 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 30 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 031 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 31 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 032 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 32 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 033 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 33 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 034 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 34 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 035 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 35 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 036 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 36 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 037 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 37 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 038 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 38 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 039 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 39 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 040 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 40 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 041 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 41 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 042 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 42 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 043 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 43 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 044 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 44 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 045 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 45 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 046 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 46 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 047 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 47 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 048 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 48 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 049 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 49 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 050 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 50 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 051 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 51 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 052 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 52 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 053 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 53 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 054 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 54 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 055 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 55 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 056 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 56 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 057 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 57 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 058 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 58 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 059 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 59 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 060 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 60 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 061 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 61 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 062 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 62 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 063 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 63 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 064 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 64 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 065 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 65 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 066 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 66 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 067 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 67 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 068 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 68 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 069 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 69 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 070 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 70 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 071 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 71 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 072 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 72 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 073 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 73 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 074 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 74 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 075 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 75 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 076 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 76 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 077 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 77 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 078 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 78 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 079 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 79 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 080 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 80 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 081 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 81 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 082 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 82 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 083 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 83 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 084 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 84 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom +- name: Lila Dense Chore 085 + assigned_to: + - Lila + points: 5.0 + icon: mdi:broom + description: Generated dense dashboard chore 85 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Kitchen + - Laundry +- name: Lila Dense Chore 086 + assigned_to: + - Lila + points: 10.0 + icon: mdi:spray-bottle + description: Generated dense dashboard chore 86 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bedroom + - School +- name: Lila Dense Chore 087 + assigned_to: + - Lila + points: 15.0 + icon: mdi:trash-can-outline + description: Generated dense dashboard chore 87 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Bathroom + - Outdoor +- name: Lila Dense Chore 088 + assigned_to: + - Lila + points: 20.0 + icon: mdi:book-open-page-variant + description: Generated dense dashboard chore 88 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Laundry + - Kitchen +- name: Lila Dense Chore 089 + assigned_to: + - Lila + points: 25.0 + icon: mdi:washing-machine + description: Generated dense dashboard chore 89 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - School + - Bedroom +- name: Lila Dense Chore 090 + assigned_to: + - Lila + points: 30.0 + icon: mdi:shovel + description: Generated dense dashboard chore 90 for Lila + completion_criteria: independent + recurring_frequency: daily + auto_approve: true + show_on_calendar: false + labels: + - Outdoor + - Bathroom diff --git a/tests/test_chore_crud_services.py b/tests/test_chore_crud_services.py index 7cf7814..4fd064e 100644 --- a/tests/test_chore_crud_services.py +++ b/tests/test_chore_crud_services.py @@ -39,6 +39,7 @@ if TYPE_CHECKING: from homeassistant.core import HomeAssistant, State + from homeassistant.helpers.entity_registry import EntityRegistry # ============================================================================ @@ -112,6 +113,27 @@ def find_chore_in_dashboard_helper( return None +def get_dashboard_helper_state(hass: HomeAssistant, assignee_slug: str) -> State: + """Return a dashboard helper state for an assignee slug.""" + helper_state = hass.states.get( + f"sensor.{assignee_slug}_choreops_ui_dashboard_helper" + ) + assert helper_state is not None + return helper_state + + +def get_button_entity_id( + entity_registry: EntityRegistry, + entry_id: str, + assignee_id: str, + chore_id: str, + suffix: str, +) -> str | None: + """Return a chore workflow button entity ID from unique-id parts.""" + unique_id = f"{entry_id}_{assignee_id}_{chore_id}{suffix}" + return entity_registry.async_get_entity_id("button", DOMAIN, unique_id) + + # ============================================================================ # CREATE CHORE - SCHEMA VALIDATION TESTS # ============================================================================ @@ -288,12 +310,71 @@ async def test_rejects_custom_frequency_without_custom_interval( class TestCreateChoreEndToEnd: - """Test create_chore end-to-end functionality. + """Test create_chore end-to-end functionality.""" - Note: After creating a chore via service, the chore exists in coordinator storage - and dashboard helper, but sensors are not automatically created without re-setup. - These tests verify via coordinator + dashboard helper, not via chore status sensors. - """ + @pytest.mark.asyncio + async def test_create_uses_runtime_entity_sync( + self, + hass: HomeAssistant, + scenario_full: SetupResult, + ) -> None: + """Test create_chore uses the shared runtime sync path.""" + with ( + patch.object(scenario_full.coordinator, "_persist", new=MagicMock()), + patch.object( + scenario_full.coordinator, + "async_sync_chore_entities", + new=AsyncMock(), + ) as mock_sync, + patch.object( + scenario_full.coordinator, + "async_sync_entities_after_service_create", + new=AsyncMock(), + ) as legacy_sync, + ): + response = await hass.services.async_call( + DOMAIN, + SERVICE_CREATE_CHORE, + { + "name": "Runtime Sync Create Contract", + "assigned_user_names": ["Zoë"], + "points": 15, + }, + blocking=True, + return_response=True, + ) + + assert response is not None + mock_sync.assert_awaited_once() + legacy_sync.assert_not_awaited() + + @pytest.mark.asyncio + async def test_create_does_not_reload_config_entry( + self, + hass: HomeAssistant, + scenario_full: SetupResult, + ) -> None: + """Test chore create does not fall back to config-entry reload.""" + with ( + patch.object(scenario_full.coordinator, "_persist", new=MagicMock()), + patch.object( + hass.config_entries, "async_reload", new=AsyncMock() + ) as reload_entry, + ): + response = await hass.services.async_call( + DOMAIN, + SERVICE_CREATE_CHORE, + { + "name": "No Reload Create Chore", + "assigned_user_names": ["Zoë"], + "points": 10, + }, + blocking=True, + return_response=True, + ) + + assert response is not None + reload_entry.assert_not_awaited() @pytest.mark.asyncio async def test_created_chore_appears_in_dashboard_helper( @@ -394,6 +475,65 @@ async def test_created_chore_dashboard_helper_attributes_match_input( assert chore_sensor.attributes["completion_criteria"] == "shared_first" assert chore_sensor.attributes["recurring_frequency"] == "weekly" + helper_state = get_dashboard_helper_state(hass, "zoe") + assert helper_state is not None + assert chore.get(const.ATTR_LABELS) == ["test", "e2e"] + + @pytest.mark.asyncio + async def test_created_chore_exposes_live_status_and_buttons( + self, + hass: HomeAssistant, + entity_registry: EntityRegistry, + scenario_full: SetupResult, + ) -> None: + """Test service create materializes live chore sensor and workflow buttons.""" + config_entry = scenario_full.config_entry + coordinator = scenario_full.coordinator + + with patch.object(coordinator, "_persist", new=MagicMock()): + response = await hass.services.async_call( + DOMAIN, + SERVICE_CREATE_CHORE, + { + "name": "Live Surface Create Chore", + "assigned_user_names": ["Zoë"], + "points": 15, + }, + blocking=True, + return_response=True, + ) + + assert response is not None + chore_id = response["id"] + await hass.async_block_till_done() + + zoe_chore = find_chore_in_dashboard_helper( + hass, "zoe", "Live Surface Create Chore" + ) + assert zoe_chore is not None + assert zoe_chore["eid"] is not None + assert hass.states.get(zoe_chore["eid"]) is not None + + zoe_id = scenario_full.assignee_ids["Zoë"] + approve_eid = get_button_entity_id( + entity_registry, + config_entry.entry_id, + zoe_id, + chore_id, + const.BUTTON_KC_UID_SUFFIX_APPROVE, + ) + disapprove_eid = get_button_entity_id( + entity_registry, + config_entry.entry_id, + zoe_id, + chore_id, + const.BUTTON_KC_UID_SUFFIX_DISAPPROVE, + ) + assert approve_eid is not None + assert disapprove_eid is not None + assert hass.states.get(approve_eid) is not None + assert hass.states.get(disapprove_eid) is not None + @pytest.mark.asyncio async def test_create_independent_weekly_uses_per_assignee_due_dates_only( self, @@ -609,21 +749,26 @@ class TestUpdateChoreEndToEnd: """Test update_chore end-to-end functionality via dashboard helper.""" @pytest.mark.asyncio - async def test_assignment_change_triggers_entity_sync( + async def test_assignment_change_uses_runtime_entity_sync( self, hass: HomeAssistant, scenario_full: SetupResult, ) -> None: - """Test assignment changes trigger runtime entity synchronization.""" + """Test assignment changes use the shared runtime sync path.""" chore_id = scenario_full.chore_ids["Täke Öut Trash"] with ( patch.object(scenario_full.coordinator, "_persist", new=MagicMock()), patch.object( scenario_full.coordinator, - "async_sync_entities_after_service_create", + "async_sync_chore_entities", new=AsyncMock(), ) as mock_sync, + patch.object( + scenario_full.coordinator, + "async_sync_entities_after_service_create", + new=AsyncMock(), + ) as legacy_sync, ): await hass.services.async_call( DOMAIN, @@ -637,6 +782,137 @@ async def test_assignment_change_triggers_entity_sync( ) mock_sync.assert_awaited_once() + legacy_sync.assert_not_awaited() + + @pytest.mark.asyncio + async def test_update_does_not_reload_config_entry( + self, + hass: HomeAssistant, + scenario_full: SetupResult, + ) -> None: + """Test chore update does not reload the config entry.""" + chore_id = scenario_full.chore_ids["Täke Öut Trash"] + + with ( + patch.object(scenario_full.coordinator, "_persist", new=MagicMock()), + patch.object( + hass.config_entries, "async_reload", new=AsyncMock() + ) as reload_entry, + ): + response = await hass.services.async_call( + DOMAIN, + SERVICE_UPDATE_CHORE, + {"id": chore_id, "points": 77}, + blocking=True, + return_response=True, + ) + + assert response is not None + reload_entry.assert_not_awaited() + + @pytest.mark.asyncio + async def test_assignment_change_updates_live_entity_surfaces( + self, + hass: HomeAssistant, + entity_registry: EntityRegistry, + scenario_full: SetupResult, + ) -> None: + """Test assignment expansion and shrinkage update live helper and button surfaces.""" + chore_id = scenario_full.chore_ids["Täke Öut Trash"] + config_entry = scenario_full.config_entry + + with patch.object(scenario_full.coordinator, "_persist", new=MagicMock()): + response = await hass.services.async_call( + DOMAIN, + SERVICE_UPDATE_CHORE, + { + "id": chore_id, + "assigned_user_names": ["Zoë", "Max!"], + }, + blocking=True, + return_response=True, + ) + + assert response is not None + await hass.async_block_till_done() + + max_chore = find_chore_in_dashboard_helper(hass, "max", "Täke Öut Trash") + assert max_chore is not None + assert max_chore["eid"] is not None + assert hass.states.get(max_chore["eid"]) is not None + + max_id = scenario_full.assignee_ids["Max!"] + approve_eid = get_button_entity_id( + entity_registry, + config_entry.entry_id, + max_id, + chore_id, + const.BUTTON_KC_UID_SUFFIX_APPROVE, + ) + assert approve_eid is not None + + with patch.object(scenario_full.coordinator, "_persist", new=MagicMock()): + response = await hass.services.async_call( + DOMAIN, + SERVICE_UPDATE_CHORE, + { + "id": chore_id, + "assigned_user_names": ["Max!"], + }, + blocking=True, + return_response=True, + ) + + assert response is not None + await hass.async_block_till_done() + + assert find_chore_in_dashboard_helper(hass, "zoe", "Täke Öut Trash") is None + assert ( + get_button_entity_id( + entity_registry, + config_entry.entry_id, + scenario_full.assignee_ids["Zoë"], + chore_id, + const.BUTTON_KC_UID_SUFFIX_APPROVE, + ) + is None + ) + + @pytest.mark.asyncio + async def test_rename_uses_runtime_entity_sync( + self, + hass: HomeAssistant, + scenario_full: SetupResult, + ) -> None: + """Test rename updates also use the shared runtime sync path.""" + chore_id = scenario_full.chore_ids["Täke Öut Trash"] + + with ( + patch.object(scenario_full.coordinator, "_persist", new=MagicMock()), + patch.object( + scenario_full.coordinator, + "async_sync_chore_entities", + new=AsyncMock(), + ) as mock_sync, + patch.object( + scenario_full.coordinator, + "async_sync_entities_after_service_create", + new=AsyncMock(), + ) as legacy_sync, + ): + await hass.services.async_call( + DOMAIN, + SERVICE_UPDATE_CHORE, + { + "id": chore_id, + "name": "Runtime Sync Renamed by Service", + }, + blocking=True, + return_response=True, + ) + + mock_sync.assert_awaited_once() + legacy_sync.assert_not_awaited() @pytest.mark.asyncio async def test_updated_points_reflects_in_dashboard_helper( @@ -720,6 +996,60 @@ async def test_update_independent_weekly_due_date_applies_to_all_assignees( class TestDeleteChoreEndToEnd: """Test delete_chore end-to-end functionality via dashboard helper.""" + @pytest.mark.asyncio + async def test_delete_uses_runtime_entity_sync( + self, + hass: HomeAssistant, + scenario_full: SetupResult, + ) -> None: + """Test delete_chore uses the shared runtime sync path.""" + chore_id = scenario_full.chore_ids["Täke Öut Trash"] + + with ( + patch.object(scenario_full.coordinator, "_persist", new=MagicMock()), + patch.object( + scenario_full.coordinator, + "async_sync_chore_entities", + new=AsyncMock(), + ) as mock_sync, + ): + response = await hass.services.async_call( + DOMAIN, + SERVICE_DELETE_CHORE, + {"id": chore_id}, + blocking=True, + return_response=True, + ) + + assert response is not None + mock_sync.assert_awaited_once() + + @pytest.mark.asyncio + async def test_delete_does_not_reload_config_entry( + self, + hass: HomeAssistant, + scenario_full: SetupResult, + ) -> None: + """Test chore delete does not reload the config entry.""" + chore_id = scenario_full.chore_ids["Täke Öut Trash"] + + with ( + patch.object(scenario_full.coordinator, "_persist", new=MagicMock()), + patch.object( + hass.config_entries, "async_reload", new=AsyncMock() + ) as reload_entry, + ): + response = await hass.services.async_call( + DOMAIN, + SERVICE_DELETE_CHORE, + {"id": chore_id}, + blocking=True, + return_response=True, + ) + + assert response is not None + reload_entry.assert_not_awaited() + @pytest.mark.asyncio async def test_deleted_chore_removed_from_dashboard_helper( self, @@ -731,10 +1061,7 @@ async def test_deleted_chore_removed_from_dashboard_helper( E2E Pattern: Service call → Storage deletion → Dashboard helper removal → Verify """ # First, create a chore to delete - with ( - patch.object(scenario_full.coordinator, "_persist", new=MagicMock()), - patch("custom_components.choreops.sensor.create_chore_entities"), - ): + with patch.object(scenario_full.coordinator, "_persist", new=MagicMock()): create_response = await hass.services.async_call( DOMAIN, SERVICE_CREATE_CHORE, diff --git a/tests/test_chore_runtime_entity_sync.py b/tests/test_chore_runtime_entity_sync.py new file mode 100644 index 0000000..bed3bb2 --- /dev/null +++ b/tests/test_chore_runtime_entity_sync.py @@ -0,0 +1,309 @@ +"""Targeted tests for chore runtime entity synchronization. + +These tests exercise the Phase 2 runtime sync contract directly without relying +on the older service reload fallback path. +""" + +from copy import deepcopy +from datetime import UTC, datetime +from unittest.mock import AsyncMock, patch + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +import pytest + +from custom_components.choreops import const +from custom_components.choreops.coordinator import ChoreOpsDataCoordinator +from tests.helpers import SetupResult, setup_from_yaml + +DOMAIN = const.DOMAIN +SERVICE_CREATE_CHORE = const.SERVICE_CREATE_CHORE + + +def _get_user_id_by_name(coordinator: ChoreOpsDataCoordinator, user_name: str) -> str: + """Return the user ID for a named assignee.""" + for user_id, user_data in coordinator.assignees_data.items(): + if user_data.get(const.DATA_USER_NAME) == user_name: + return user_id + raise AssertionError(f"Assignee not found: {user_name}") + + +def _get_single_assignee_chore_id( + coordinator: ChoreOpsDataCoordinator, + *, + completion_criteria: str | None = None, +) -> str: + """Return a chore ID with exactly one assigned assignee. + + Optionally constrain the chore by completion criteria. + """ + for chore_id, chore_data in coordinator.chores_data.items(): + assigned_ids = chore_data.get(const.DATA_CHORE_ASSIGNED_USER_IDS, []) + if len(assigned_ids) != 1: + continue + if ( + completion_criteria is not None + and chore_data.get(const.DATA_CHORE_COMPLETION_CRITERIA) + != completion_criteria + ): + continue + return chore_id + raise AssertionError("No single-assignee chore available in scenario") + + +def _sensor_unique_id(entry_id: str, assignee_id: str, chore_id: str) -> str: + """Return the unique ID for an assignee chore status sensor.""" + return ( + f"{entry_id}_{assignee_id}_{chore_id}" + f"{const.SENSOR_KC_UID_SUFFIX_CHORE_STATUS_SENSOR}" + ) + + +def _shared_sensor_unique_id(entry_id: str, chore_id: str) -> str: + """Return the unique ID for a shared chore state sensor.""" + return ( + f"{entry_id}_{chore_id}" + f"{const.SENSOR_KC_UID_SUFFIX_SHARED_CHORE_GLOBAL_STATE_SENSOR}" + ) + + +def _button_unique_id( + entry_id: str, + assignee_id: str, + chore_id: str, + suffix: str, +) -> str: + """Return the unique ID for a chore workflow button.""" + return f"{entry_id}_{assignee_id}_{chore_id}{suffix}" + + +@pytest.fixture +async def scenario_full( + hass: HomeAssistant, + mock_hass_users: dict[str, object], +) -> SetupResult: + """Load the full test scenario.""" + return await setup_from_yaml( + hass, + mock_hass_users, + "tests/scenarios/scenario_full.yaml", + ) + + +async def test_runtime_sync_create_adds_missing_chore_entities( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + scenario_full: SetupResult, +) -> None: + """Runtime sync should create status sensors and workflow buttons for a new chore.""" + coordinator = scenario_full.coordinator + config_entry = scenario_full.config_entry + + with patch.object( + coordinator, + "async_sync_chore_entities", + new=AsyncMock(), + ): + response = await hass.services.async_call( + DOMAIN, + SERVICE_CREATE_CHORE, + { + "name": "Runtime Sync Created Chore", + "assigned_user_names": ["Zoë"], + "frequency": "daily", + "due_date": datetime(2099, 1, 1, 9, 0, tzinfo=UTC), + }, + blocking=True, + return_response=True, + ) + + assert response is not None + chore_id = response[const.SERVICE_FIELD_CHORE_CRUD_ID] + assignee_id = _get_user_id_by_name(coordinator, "Zoë") + + sync_context = coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation="created", + current_chore=coordinator.chores_data[chore_id], + ) + result = await coordinator.async_sync_chore_entities(sync_context) + await hass.async_block_till_done() + + assert result.buttons_created >= 1 + + sensor_entity_id = entity_registry.async_get_entity_id( + "sensor", + DOMAIN, + _sensor_unique_id(config_entry.entry_id, assignee_id, chore_id), + ) + assert sensor_entity_id is not None + + approve_button_entity_id = entity_registry.async_get_entity_id( + "button", + DOMAIN, + _button_unique_id( + config_entry.entry_id, + assignee_id, + chore_id, + const.BUTTON_KC_UID_SUFFIX_APPROVE, + ), + ) + assert approve_button_entity_id is not None + + +async def test_runtime_sync_assignment_change_adds_and_removes_entities( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + scenario_full: SetupResult, +) -> None: + """Runtime sync should add new assignee entities and remove orphaned ones.""" + coordinator = scenario_full.coordinator + config_entry = scenario_full.config_entry + chore_id = _get_single_assignee_chore_id( + coordinator, + completion_criteria=const.COMPLETION_CRITERIA_INDEPENDENT, + ) + + previous_chore = deepcopy(coordinator.chores_data[chore_id]) + old_assignee_id = previous_chore[const.DATA_CHORE_ASSIGNED_USER_IDS][0] + new_assignee_id = next( + user_id for user_id in coordinator.assignees_data if user_id != old_assignee_id + ) + + coordinator.chore_manager.update_chore( + chore_id, + {const.DATA_CHORE_ASSIGNED_USER_IDS: [new_assignee_id]}, + immediate_persist=True, + ) + current_chore = deepcopy(coordinator.chores_data[chore_id]) + + sync_context = coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation="updated", + previous_chore=previous_chore, + current_chore=current_chore, + ) + result = await coordinator.async_sync_chore_entities(sync_context) + await hass.async_block_till_done() + + assert result.buttons_created >= 1 + assert result.orphaned_assignee_entities_removed >= 1 + + old_sensor_entity_id = entity_registry.async_get_entity_id( + "sensor", + DOMAIN, + _sensor_unique_id(config_entry.entry_id, old_assignee_id, chore_id), + ) + new_sensor_entity_id = entity_registry.async_get_entity_id( + "sensor", + DOMAIN, + _sensor_unique_id(config_entry.entry_id, new_assignee_id, chore_id), + ) + + assert old_sensor_entity_id is None + assert new_sensor_entity_id is not None + + +async def test_runtime_sync_rename_replaces_cached_chore_entities( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + scenario_full: SetupResult, +) -> None: + """Runtime sync should replace rename-sensitive chore entities.""" + coordinator = scenario_full.coordinator + config_entry = scenario_full.config_entry + chore_id = _get_single_assignee_chore_id( + coordinator, + completion_criteria=const.COMPLETION_CRITERIA_INDEPENDENT, + ) + previous_chore = deepcopy(coordinator.chores_data[chore_id]) + assignee_id = previous_chore[const.DATA_CHORE_ASSIGNED_USER_IDS][0] + new_name = "Runtime Sync Renamed Chore" + + coordinator.chore_manager.update_chore( + chore_id, + {const.DATA_CHORE_NAME: new_name}, + immediate_persist=True, + ) + current_chore = deepcopy(coordinator.chores_data[chore_id]) + + sync_context = coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation="updated", + previous_chore=previous_chore, + current_chore=current_chore, + ) + result = await coordinator.async_sync_chore_entities(sync_context) + await hass.async_block_till_done() + + assert result.sensors_created >= 1 + + sensor_entity_id = entity_registry.async_get_entity_id( + "sensor", + DOMAIN, + _sensor_unique_id(config_entry.entry_id, assignee_id, chore_id), + ) + assert sensor_entity_id is not None + + sensor_state = hass.states.get(sensor_entity_id) + assert sensor_state is not None + assert sensor_state.attributes[const.ATTR_CHORE_NAME] == new_name + + +async def test_runtime_sync_shared_transition_adds_and_removes_shared_sensor( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + scenario_full: SetupResult, +) -> None: + """Runtime sync should add and remove the shared chore sensor on criteria changes.""" + coordinator = scenario_full.coordinator + config_entry = scenario_full.config_entry + chore_id = _get_single_assignee_chore_id(coordinator) + + previous_chore = deepcopy(coordinator.chores_data[chore_id]) + coordinator.chore_manager.update_chore( + chore_id, + {const.DATA_CHORE_COMPLETION_CRITERIA: const.COMPLETION_CRITERIA_SHARED}, + immediate_persist=True, + ) + current_chore = deepcopy(coordinator.chores_data[chore_id]) + + sync_context = coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation="updated", + previous_chore=previous_chore, + current_chore=current_chore, + ) + result = await coordinator.async_sync_chore_entities(sync_context) + await hass.async_block_till_done() + + shared_sensor_entity_id = entity_registry.async_get_entity_id( + "sensor", + DOMAIN, + _shared_sensor_unique_id(config_entry.entry_id, chore_id), + ) + assert shared_sensor_entity_id is not None + + previous_chore = deepcopy(coordinator.chores_data[chore_id]) + coordinator.chore_manager.update_chore( + chore_id, + {const.DATA_CHORE_COMPLETION_CRITERIA: const.COMPLETION_CRITERIA_INDEPENDENT}, + immediate_persist=True, + ) + current_chore = deepcopy(coordinator.chores_data[chore_id]) + + sync_context = coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation="updated", + previous_chore=previous_chore, + current_chore=current_chore, + ) + result = await coordinator.async_sync_chore_entities(sync_context) + await hass.async_block_till_done() + + shared_sensor_entity_id = entity_registry.async_get_entity_id( + "sensor", + DOMAIN, + _shared_sensor_unique_id(config_entry.entry_id, chore_id), + ) + assert shared_sensor_entity_id is None diff --git a/tests/test_dashboard_helper_density_stress.py b/tests/test_dashboard_helper_density_stress.py new file mode 100644 index 0000000..8c3196e --- /dev/null +++ b/tests/test_dashboard_helper_density_stress.py @@ -0,0 +1,130 @@ +"""Opt-in dashboard helper density stress tests. + +These tests are skipped by default. Enable them with: + + CHOREOPS_RUN_STRESS=1 pytest tests/test_dashboard_helper_density_stress.py -s + +The scenarios under test use the standard Stårblüm family and assign the same +number of independent chores to each assignee so per-user dashboard helper size +can be compared directly at higher densities. +""" + +from __future__ import annotations + +import json +import os +from typing import TYPE_CHECKING + +import pytest + +from custom_components.choreops import const +from tests.helpers.setup import setup_from_yaml + +if TYPE_CHECKING: + from homeassistant.core import HomeAssistant + +RUN_STRESS = os.environ.get("CHOREOPS_RUN_STRESS") == "1" +RECORDER_LIMIT_BYTES = 16 * 1024 +SCENARIO_COUNTS = (40, 50, 60, 70, 80, 90, 100, 120) +ASSIGNEE_SLUGS = ("zoe", "max", "lila") + +pytestmark = [ + pytest.mark.slow, + pytest.mark.stress, + pytest.mark.skipif( + not RUN_STRESS, + reason="Set CHOREOPS_RUN_STRESS=1 to run dense dashboard helper stress tests", + ), +] + + +def _get_helper_size(hass: HomeAssistant, assignee_slug: str) -> int: + """Return the JSON-serialized dashboard helper attribute size in bytes.""" + entity_id = f"sensor.{assignee_slug}_choreops_ui_dashboard_helper" + helper_state = hass.states.get(entity_id) + assert helper_state is not None, f"Dashboard helper not found: {entity_id}" + return len(json.dumps(helper_state.attributes).encode("utf-8")) + + +def _get_helper_chores( + hass: HomeAssistant, assignee_slug: str +) -> list[dict[str, object]]: + """Return merged chores from the main helper and any chore shard helpers.""" + helper_entity_id = f"sensor.{assignee_slug}_choreops_ui_dashboard_helper" + helper_state = hass.states.get(helper_entity_id) + assert helper_state is not None, f"Dashboard helper not found: {helper_entity_id}" + + merged_chores = list(helper_state.attributes.get("chores", [])) + dashboard_helpers = helper_state.attributes.get("dashboard_helpers", {}) + chore_helper_eids = dashboard_helpers.get(const.ATTR_CHORE_HELPER_EIDS, []) + + for shard_entity_id in chore_helper_eids: + shard_state = hass.states.get(shard_entity_id) + assert shard_state is not None, ( + f"Chore shard helper not found: {shard_entity_id}" + ) + merged_chores.extend(shard_state.attributes.get("chores", [])) + + return merged_chores + + +@pytest.mark.parametrize("chores_per_assignee", SCENARIO_COUNTS) +async def test_dashboard_helper_size_under_recorder_limit( + hass: HomeAssistant, + mock_hass_users: dict[str, object], + chores_per_assignee: int, +) -> None: + """Validate dense scenarios stay under the recorder attribute limit.""" + scenario_path = ( + f"tests/scenarios/scenario_density_starblum_{chores_per_assignee}.yaml" + ) + await setup_from_yaml(hass, mock_hass_users, scenario_path) + + helper_sizes = { + assignee_slug: _get_helper_size(hass, assignee_slug) + for assignee_slug in ASSIGNEE_SLUGS + } + oversize_helpers: dict[str, int] = {} + + for assignee_slug, helper_size in helper_sizes.items(): + print( + f"dashboard helper size | chores_per_assignee={chores_per_assignee} " + f"| assignee={assignee_slug} | bytes={helper_size}" + ) + if helper_size >= RECORDER_LIMIT_BYTES: + oversize_helpers[assignee_slug] = helper_size + + assert not oversize_helpers, ( + f"Dashboard helpers exceeded {RECORDER_LIMIT_BYTES} bytes for " + f"{chores_per_assignee} chores per assignee: {oversize_helpers}" + ) + + +@pytest.mark.parametrize("chores_per_assignee", SCENARIO_COUNTS) +async def test_dense_scenario_claim_chore_stays_operational( + hass: HomeAssistant, + mock_hass_users: dict[str, object], + chores_per_assignee: int, +) -> None: + """Verify a representative claim path still works under dense load.""" + scenario_path = ( + f"tests/scenarios/scenario_density_starblum_{chores_per_assignee}.yaml" + ) + setup_result = await setup_from_yaml(hass, mock_hass_users, scenario_path) + coordinator = setup_result.coordinator + zoe_id = setup_result.assignee_ids["Zoë"] + first_chore_id = setup_result.chore_ids["Zoë Dense Chore 001"] + + await coordinator.ui_manager.async_reconcile_chore_shards_for_users([zoe_id]) + await coordinator.async_request_refresh() + await hass.async_block_till_done() + + await coordinator.chore_manager.claim_chore(zoe_id, first_chore_id, "Zoë") + await coordinator.async_request_refresh() + await hass.async_block_till_done() + + chores = _get_helper_chores(hass, "zoe") + claimed_chore = next( + chore for chore in chores if chore.get("name") == "Zoë Dense Chore 001" + ) + assert claimed_chore["state"] in {"claimed", "completed", "waiting"} diff --git a/tests/test_dashboard_helper_sharding.py b/tests/test_dashboard_helper_sharding.py new file mode 100644 index 0000000..98a806d --- /dev/null +++ b/tests/test_dashboard_helper_sharding.py @@ -0,0 +1,877 @@ +"""Focused tests for dashboard helper chore sharding.""" + +from __future__ import annotations + +from copy import deepcopy +from typing import TYPE_CHECKING, Any +from uuid import uuid4 + +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry +import pytest + +from custom_components.choreops import const +from custom_components.choreops.sensor import ( + AssigneeDashboardChoreShardSensor, + build_chore_shard_plan, +) +from tests.helpers.setup import SetupResult, setup_from_yaml + +if TYPE_CHECKING: + from homeassistant.core import HomeAssistant + + +@pytest.fixture +async def scenario_density_40( + hass: HomeAssistant, + mock_hass_users: dict[str, Any], +) -> SetupResult: + """Load the 40-chores-per-user density scenario.""" + return await setup_from_yaml( + hass, + mock_hass_users, + "tests/scenarios/scenario_density_starblum_40.yaml", + ) + + +@pytest.fixture +async def scenario_density_100( + hass: HomeAssistant, + mock_hass_users: dict[str, Any], +) -> SetupResult: + """Load the 100-chores-per-user density scenario.""" + return await setup_from_yaml( + hass, + mock_hass_users, + "tests/scenarios/scenario_density_starblum_100.yaml", + ) + + +@pytest.fixture +async def scenario_density_80( + hass: HomeAssistant, + mock_hass_users: dict[str, Any], +) -> SetupResult: + """Load the 80-chores-per-user density scenario.""" + return await setup_from_yaml( + hass, + mock_hass_users, + "tests/scenarios/scenario_density_starblum_80.yaml", + ) + + +@pytest.fixture +async def scenario_density_120( + hass: HomeAssistant, + mock_hass_users: dict[str, Any], +) -> SetupResult: + """Load the 120-chores-per-user density scenario.""" + return await setup_from_yaml( + hass, + mock_hass_users, + "tests/scenarios/scenario_density_starblum_120.yaml", + ) + + +def _get_dashboard_helper_state( + hass: HomeAssistant, + setup_result: SetupResult, + assignee_name: str, +): + """Return the dashboard helper state for one assignee slug.""" + assignee_id = setup_result.assignee_ids[assignee_name] + unique_id = ( + f"{setup_result.config_entry.entry_id}_{assignee_id}" + f"{const.SENSOR_KC_UID_SUFFIX_UI_DASHBOARD_HELPER}" + ) + entity_id = async_get_entity_registry(hass).async_get_entity_id( + "sensor", const.DOMAIN, unique_id + ) + helper_state = hass.states.get(entity_id) if entity_id is not None else None + available_dashboard_states = sorted( + state.entity_id + for state in hass.states.async_all() + if state.entity_id.startswith("sensor.") and "dashboard" in state.entity_id + ) + assert helper_state is not None, available_dashboard_states + return helper_state + + +def _get_shard_entity_ids(helper_state) -> list[str]: + """Return ordered chore shard helper entity IDs from one dashboard helper.""" + dashboard_helpers = helper_state.attributes.get("dashboard_helpers", {}) + return list(dashboard_helpers.get(const.ATTR_CHORE_HELPER_EIDS, [])) + + +def _get_direct_chore_list_state( + hass: HomeAssistant, + assignee_slug: str, + shard_index: int = 1, +): + """Return one chore list shard state by its direct entity ID.""" + entity_id = f"sensor.{assignee_slug}_choreops_ui_dashboard_chore_list_{shard_index}" + chore_list_state = hass.states.get(entity_id) + assert chore_list_state is not None, { + "missing": entity_id, + "available_dashboard_states": sorted( + state.entity_id + for state in hass.states.async_all() + if state.entity_id.startswith("sensor.") and "dashboard" in state.entity_id + ), + } + return chore_list_state + + +def _merge_helper_chores(hass: HomeAssistant, helper_state) -> list[dict[str, Any]]: + """Return merged chores from one dashboard helper and any shard helpers.""" + merged_chores = list(helper_state.attributes.get("chores", [])) + for shard_eid in _get_shard_entity_ids(helper_state): + shard_state = hass.states.get(shard_eid) + assert shard_state is not None, shard_eid + merged_chores.extend(shard_state.attributes.get("chores", [])) + return merged_chores + + +def _get_user_plan(setup_result: SetupResult, assignee_name: str): + """Return the current runtime shard plan for one assignee.""" + assignee_id = setup_result.assignee_ids[assignee_name] + return setup_result.coordinator.ui_manager.get_helper_shard_plan( + assignee_id, + const.HELPER_SHARD_FAMILY_CHORES, + ) + + +async def _sync_updated_chore( + setup_result: SetupResult, + chore_id: str, + updates: dict[str, Any], +) -> None: + """Apply one chore update and run the runtime sync path.""" + previous_chore = deepcopy(setup_result.coordinator.chores_data[chore_id]) + current_chore = setup_result.coordinator.chore_manager.update_chore( + chore_id, updates + ) + sync_context = setup_result.coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation="updated", + previous_chore=previous_chore, + current_chore=current_chore, + ) + await setup_result.coordinator.async_sync_chore_entities(sync_context) + + +async def _sync_deleted_chores( + setup_result: SetupResult, + chore_ids: list[str], +) -> None: + """Delete multiple chores and run one final runtime sync pass.""" + last_previous_chore: dict[str, Any] | None = None + last_chore_id: str | None = None + + for chore_id in chore_ids: + last_previous_chore = deepcopy(setup_result.coordinator.chores_data[chore_id]) + last_chore_id = chore_id + setup_result.coordinator.chore_manager.delete_chore(chore_id) + + assert last_previous_chore is not None and last_chore_id is not None + sync_context = setup_result.coordinator.chore_manager.build_entity_sync_context( + last_chore_id, + mutation="deleted", + previous_chore=last_previous_chore, + current_chore=None, + ) + await setup_result.coordinator.async_sync_chore_entities(sync_context) + + +async def _sync_created_chore( + setup_result: SetupResult, + chore_data: dict[str, Any], +) -> str: + """Create one chore and run the runtime sync path.""" + created_chore = setup_result.coordinator.chore_manager.create_chore( + chore_data, + internal_id=str(chore_data[const.DATA_CHORE_INTERNAL_ID]), + prebuilt=True, + ) + chore_id = str(created_chore[const.DATA_CHORE_INTERNAL_ID]) + sync_context = setup_result.coordinator.chore_manager.build_entity_sync_context( + chore_id, + mutation="created", + previous_chore=None, + current_chore=created_chore, + ) + await setup_result.coordinator.async_sync_chore_entities(sync_context) + return chore_id + + +def _build_plan_for_user(setup_result: SetupResult, assignee_name: str, previous_plan): + """Compute the current chore shard plan directly from coordinator data.""" + assignee_id = setup_result.assignee_ids[assignee_name] + return build_chore_shard_plan( + setup_result.coordinator.hass, + setup_result.coordinator, + setup_result.config_entry, + assignee_id, + assignee_name, + previous_plan=previous_plan, + ) + + +async def _trim_user_to_first_inline_plan( + setup_result: SetupResult, + assignee_name: str, +) -> list[dict[str, Any]]: + """Delete chores until the user reaches the first inline plan, returning deleted chores.""" + deleted_chores: list[dict[str, Any]] = [] + user_id = setup_result.assignee_ids[assignee_name] + plan = _get_user_plan(setup_result, assignee_name) + assert plan is not None + + while plan.mode != const.HELPER_SHARD_MODE_INLINE: + chore_id = next( + current_chore_id + for current_chore_id, chore_info in setup_result.coordinator.chores_data.items() + if user_id in chore_info.get(const.DATA_CHORE_ASSIGNED_USER_IDS, []) + ) + deleted_chore = deepcopy(setup_result.coordinator.chores_data[chore_id]) + deleted_chores.append(deleted_chore) + await _sync_deleted_chores(setup_result, [chore_id]) + plan = _build_plan_for_user(setup_result, assignee_name, previous_plan=plan) + setup_result.coordinator.ui_manager.set_helper_shard_plan( + user_id, + const.HELPER_SHARD_FAMILY_CHORES, + plan, + ) + + return deleted_chores + + +async def _restore_user_to_highest_inline_plan( + setup_result: SetupResult, + assignee_name: str, + deleted_chores: list[dict[str, Any]], +) -> None: + """Restore chores until one more would switch the user back to sharded mode.""" + user_id = setup_result.assignee_ids[assignee_name] + plan = _get_user_plan(setup_result, assignee_name) + assert plan is not None and plan.mode == const.HELPER_SHARD_MODE_INLINE + + while deleted_chores: + chore_data = deleted_chores.pop() + setup_result.coordinator.chore_manager.create_chore( + deepcopy(chore_data), + internal_id=str(chore_data[const.DATA_CHORE_INTERNAL_ID]), + prebuilt=True, + ) + candidate_plan = _build_plan_for_user( + setup_result, + assignee_name, + previous_plan=plan, + ) + if candidate_plan.mode == const.HELPER_SHARD_MODE_SHARDED: + setup_result.coordinator.chore_manager.delete_chore( + str(chore_data[const.DATA_CHORE_INTERNAL_ID]) + ) + break + + plan = candidate_plan + setup_result.coordinator.ui_manager.set_helper_shard_plan( + user_id, + const.HELPER_SHARD_FAMILY_CHORES, + plan, + ) + + +class TestDashboardHelperSharding: + """Validate inline and sharded helper modes around the density threshold.""" + + @staticmethod + def _get_density_setup_result( + scenario_density_80: SetupResult, + scenario_density_120: SetupResult, + chores_per_user: int, + ) -> SetupResult: + """Return the setup result for one accepted high-density scenario.""" + scenario_map = { + 80: scenario_density_80, + 120: scenario_density_120, + } + return scenario_map[chores_per_user] + + async def test_end_to_end_setup_and_reload_expose_chore_list_1_attributes( + self, + hass: HomeAssistant, + scenario_density_100: SetupResult, + ) -> None: + """Normal setup and reload should expose live chore_list_1 shard attributes.""" + await scenario_density_100.coordinator.async_request_refresh() + await hass.async_block_till_done() + + helper_state = _get_dashboard_helper_state(hass, scenario_density_100, "Zoë") + chore_helper_eids = _get_shard_entity_ids(helper_state) + + assert chore_helper_eids + assert chore_helper_eids[0] == "sensor.zoe_choreops_ui_dashboard_chore_list_1" + + chore_list_1_state = _get_direct_chore_list_state(hass, "zoe", 1) + assert chore_list_1_state.state == "available" + assert chore_list_1_state.attributes["friendly_name"].endswith( + "UI Dashboard Chore List 1" + ) + assert ( + chore_list_1_state.attributes[const.ATTR_PURPOSE] + == const.TRANS_KEY_PURPOSE_DASHBOARD_CHORE_SHARD_HELPER + ) + assert chore_list_1_state.attributes[const.ATTR_SHARD_INDEX] == 1 + assert chore_list_1_state.attributes[const.ATTR_SHARD_COUNT] >= 1 + assert ( + chore_list_1_state.attributes[const.ATTR_HELPER_CONTRACT_VERSION] + == const.HELPER_CONTRACT_VERSION_V1 + ) + assert isinstance(chore_list_1_state.attributes.get("chores", []), list) + assert chore_list_1_state.attributes.get("chores", []) + assert all( + const.DATA_CHORE_NAME in chore + for chore in chore_list_1_state.attributes["chores"] + ) + + await hass.config_entries.async_reload( + scenario_density_100.config_entry.entry_id + ) + await hass.async_block_till_done() + reloaded_entry = hass.config_entries.async_get_entry( + scenario_density_100.config_entry.entry_id + ) + assert reloaded_entry is not None + reloaded_coordinator = reloaded_entry.runtime_data + await reloaded_coordinator.async_request_refresh() + await hass.async_block_till_done() + + helper_state_after_reload = _get_dashboard_helper_state( + hass, scenario_density_100, "Zoë" + ) + chore_helper_eids_after_reload = _get_shard_entity_ids( + helper_state_after_reload + ) + assert chore_helper_eids_after_reload + assert ( + chore_helper_eids_after_reload[0] + == "sensor.zoe_choreops_ui_dashboard_chore_list_1" + ) + + chore_list_1_state_after_reload = _get_direct_chore_list_state(hass, "zoe", 1) + assert chore_list_1_state_after_reload.state == "available" + assert chore_list_1_state_after_reload.attributes[const.ATTR_SHARD_INDEX] == 1 + assert isinstance( + chore_list_1_state_after_reload.attributes.get("chores", []), + list, + ) + assert chore_list_1_state_after_reload.attributes.get("chores", []) + + async def test_inline_mode_keeps_chores_on_main_helper( + self, + hass: HomeAssistant, + scenario_density_40: SetupResult, + ) -> None: + """40 chores per user should stay inline with no shard pointers.""" + await scenario_density_40.coordinator.async_request_refresh() + await hass.async_block_till_done() + helper_state = _get_dashboard_helper_state(hass, scenario_density_40, "Zoë") + + assert const.ATTR_CHORES_BY_LABEL not in helper_state.attributes + assert ( + helper_state.attributes["dashboard_helpers"][const.ATTR_CHORE_HELPER_EIDS] + == [] + ) + assert ( + helper_state.attributes[const.ATTR_SHARD_RUNTIME]["mode"] + == const.HELPER_SHARD_MODE_INLINE + ) + assert len(helper_state.attributes["chores"]) == 40 + + async def test_sharded_mode_moves_chores_to_companion_helpers( + self, + hass: HomeAssistant, + scenario_density_100: SetupResult, + ) -> None: + """100 chores per user should expose chore shard helper pointers.""" + await scenario_density_100.coordinator.ui_manager.async_reconcile_chore_shards_for_users( + [scenario_density_100.assignee_ids["Zoë"]] + ) + await scenario_density_100.coordinator.async_request_refresh() + await hass.async_block_till_done() + helper_state = _get_dashboard_helper_state(hass, scenario_density_100, "Zoë") + + assert const.ATTR_CHORES_BY_LABEL not in helper_state.attributes + assert helper_state.attributes["chores"] == [] + + shard_runtime = helper_state.attributes[const.ATTR_SHARD_RUNTIME] + assert shard_runtime["mode"] == const.HELPER_SHARD_MODE_SHARDED + + chore_helper_eids = helper_state.attributes["dashboard_helpers"][ + const.ATTR_CHORE_HELPER_EIDS + ] + assert chore_helper_eids + assert len(chore_helper_eids) == shard_runtime["expected_shard_count"] + + merged_chores: list[dict[str, Any]] = [] + for expected_index, shard_eid in enumerate(chore_helper_eids, start=1): + shard_state = hass.states.get(shard_eid) + available_shard_states = sorted( + state.entity_id + for state in hass.states.async_all() + if "chore_shard" in state.entity_id + ) + registry_entries = sorted( + entry.entity_id + for entry in er.async_entries_for_config_entry( + async_get_entity_registry(hass), + scenario_density_100.config_entry.entry_id, + ) + if "chore_shard" in entry.entity_id + ) + assert shard_state is not None, { + "states": available_shard_states, + "registry": registry_entries, + } + assert shard_state.attributes["friendly_name"].endswith( + f"UI Dashboard Chore List {expected_index}" + ) + assert "shard" not in shard_state.attributes["friendly_name"].lower() + assert ( + shard_state.attributes[const.ATTR_PURPOSE] + == const.TRANS_KEY_PURPOSE_DASHBOARD_CHORE_SHARD_HELPER + ) + assert shard_state.attributes[const.ATTR_SHARD_INDEX] == expected_index + assert ( + shard_state.attributes[const.ATTR_SHARD_COUNT] + == shard_runtime["expected_shard_count"] + ) + assert ( + shard_state.attributes[const.ATTR_HELPER_CONTRACT_VERSION] + == const.HELPER_CONTRACT_VERSION_V1 + ) + assert shard_eid.endswith(f"_ui_dashboard_chore_list_{expected_index}") + merged_chores.extend(shard_state.attributes["chores"]) + + assert len(merged_chores) == 100 + assert all("_chore_id" not in chore for chore in merged_chores) + + async def test_dashboard_helper_shared_attributes_resolve_registered_chore_lists( + self, + hass: HomeAssistant, + scenario_density_100: SetupResult, + ) -> None: + """Main helper should expose registered chore list helper IDs in dashboard_helpers.""" + await scenario_density_100.coordinator.ui_manager.async_reconcile_chore_shards_for_users( + [scenario_density_100.assignee_ids["Zoë"]] + ) + await scenario_density_100.coordinator.async_request_refresh() + await hass.async_block_till_done() + + helper_state = _get_dashboard_helper_state(hass, scenario_density_100, "Zoë") + dashboard_helpers = helper_state.attributes.get("dashboard_helpers", {}) + chore_helper_eids = list( + dashboard_helpers.get(const.ATTR_CHORE_HELPER_EIDS, []) + ) + + assert chore_helper_eids + assert dashboard_helpers.get("translation_sensor_eid") is not None + assert dashboard_helpers.get("date_helper_eid") is not None + assert dashboard_helpers.get("chore_select_eid") is not None + + for expected_index, chore_helper_eid in enumerate(chore_helper_eids, start=1): + chore_list_state = hass.states.get(chore_helper_eid) + assert chore_list_state is not None, chore_helper_eid + assert chore_list_state.attributes["friendly_name"].endswith( + f"UI Dashboard Chore List {expected_index}" + ) + assert chore_list_state.attributes[const.ATTR_SHARD_INDEX] == expected_index + assert chore_list_state.attributes[const.ATTR_SHARD_COUNT] == len( + chore_helper_eids + ) + assert isinstance(chore_list_state.attributes.get("chores", []), list) + + @pytest.mark.parametrize("chores_per_user", [80, 120]) + async def test_acceptance_high_density_stays_sharded_and_complete( + self, + hass: HomeAssistant, + scenario_density_80: SetupResult, + scenario_density_120: SetupResult, + chores_per_user: int, + ) -> None: + """High-density accepted scenarios should stay complete and under the recorder ceiling.""" + setup_result = self._get_density_setup_result( + scenario_density_80, + scenario_density_120, + chores_per_user, + ) + + await ( + setup_result.coordinator.ui_manager.async_reconcile_chore_shards_for_users( + [setup_result.assignee_ids["Zoë"]] + ) + ) + await setup_result.coordinator.async_request_refresh() + await hass.async_block_till_done() + + helper_state = _get_dashboard_helper_state(hass, setup_result, "Zoë") + shard_runtime = helper_state.attributes[const.ATTR_SHARD_RUNTIME] + + assert shard_runtime["mode"] == const.HELPER_SHARD_MODE_SHARDED + assert shard_runtime["expected_shard_count"] >= 1 + assert shard_runtime["last_accepted_serialized_size"] < 16 * 1024 + + merged_chores = _merge_helper_chores(hass, helper_state) + assert len(merged_chores) == chores_per_user + + async def test_density_80_claim_path_stays_operational( + self, + hass: HomeAssistant, + scenario_density_80: SetupResult, + ) -> None: + """A representative claim path should still work at 80 chores per user.""" + zoe_id = scenario_density_80.assignee_ids["Zoë"] + first_chore_id = scenario_density_80.chore_ids["Zoë Dense Chore 001"] + + await scenario_density_80.coordinator.ui_manager.async_reconcile_chore_shards_for_users( + [zoe_id] + ) + await scenario_density_80.coordinator.async_request_refresh() + await hass.async_block_till_done() + + await scenario_density_80.coordinator.chore_manager.claim_chore( + zoe_id, + first_chore_id, + "Zoë", + ) + await scenario_density_80.coordinator.async_request_refresh() + await hass.async_block_till_done() + + helper_state = _get_dashboard_helper_state(hass, scenario_density_80, "Zoë") + chores = _merge_helper_chores(hass, helper_state) + claimed_chore = next( + chore for chore in chores if chore.get("name") == "Zoë Dense Chore 001" + ) + + assert claimed_chore["state"] in {"claimed", "completed", "waiting"} + + async def test_reload_reconstructs_shard_helpers_without_unavailable_entities( + self, + hass: HomeAssistant, + scenario_density_120: SetupResult, + ) -> None: + """Reload should reconstruct shard-backed helpers with stable entity IDs.""" + helper_state_before = _get_dashboard_helper_state( + hass, scenario_density_120, "Zoë" + ) + shard_entity_ids_before = _get_shard_entity_ids(helper_state_before) + assert shard_entity_ids_before + + await hass.config_entries.async_reload( + scenario_density_120.config_entry.entry_id + ) + await hass.async_block_till_done() + + helper_state_after = _get_dashboard_helper_state( + hass, scenario_density_120, "Zoë" + ) + shard_entity_ids_after = _get_shard_entity_ids(helper_state_after) + assert shard_entity_ids_after == shard_entity_ids_before, { + "before": shard_entity_ids_before, + "after": shard_entity_ids_after, + "runtime": helper_state_after.attributes.get(const.ATTR_SHARD_RUNTIME), + "dashboard_helpers": helper_state_after.attributes.get("dashboard_helpers"), + "registry": [ + entry.entity_id + for entry in er.async_entries_for_config_entry( + async_get_entity_registry(hass), + scenario_density_120.config_entry.entry_id, + ) + if "ui_dashboard_chore_list" in entry.entity_id + ], + } + assert ( + helper_state_after.attributes[const.ATTR_SHARD_RUNTIME]["mode"] + == const.HELPER_SHARD_MODE_SHARDED + ) + assert len(_merge_helper_chores(hass, helper_state_after)) == 120 + + for shard_entity_id in shard_entity_ids_after: + shard_state = hass.states.get(shard_entity_id) + assert shard_state is not None + assert shard_state.state != "unavailable" + + async def test_reconcile_recreates_missing_live_shards_from_registry_entries( + self, + hass: HomeAssistant, + scenario_density_100: SetupResult, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """Reconcile should recreate shard sensors when only registry entries remain.""" + helper_state = _get_dashboard_helper_state(hass, scenario_density_100, "Zoë") + shard_entity_ids = _get_shard_entity_ids(helper_state) + assert shard_entity_ids + + for shard_entity_id in shard_entity_ids: + hass.states.async_remove(shard_entity_id) + await hass.async_block_till_done() + + captured_sensors: list[AssigneeDashboardChoreShardSensor] = [] + + def _capture_entities( + entities: list[AssigneeDashboardChoreShardSensor], + ) -> None: + captured_sensors.extend(entities) + + monkeypatch.setattr( + scenario_density_100.coordinator.ui_manager, + "_sensor_add_entities_callback", + _capture_entities, + ) + + await scenario_density_100.coordinator.ui_manager.async_reconcile_chore_shards_for_users( + [scenario_density_100.assignee_ids["Zoë"]] + ) + + plan = _get_user_plan(scenario_density_100, "Zoë") + assert plan is not None + assert len(captured_sensors) == plan.expected_shard_count + assert [sensor.unique_id for sensor in captured_sensors] == [ + scenario_density_100.coordinator.ui_manager.get_chore_shard_unique_id( + scenario_density_100.assignee_ids["Zoë"], + shard_index, + ) + for shard_index in range(1, plan.expected_shard_count + 1) + ] + + async def test_small_edit_keeps_shard_mode_and_inline_transition_cleans_orphans( + self, + hass: HomeAssistant, + scenario_density_100: SetupResult, + ) -> None: + """Ordinary edits should not flap shard mode, but shrinking far enough should clean up shard helpers.""" + helper_state_before = _get_dashboard_helper_state( + hass, scenario_density_100, "Zoë" + ) + shard_entity_ids_before = _get_shard_entity_ids(helper_state_before) + assert shard_entity_ids_before + + await _sync_updated_chore( + scenario_density_100, + scenario_density_100.chore_ids["Zoë Dense Chore 001"], + { + const.DATA_CHORE_NAME: "Zoë Dense Chore 001 Retitled", + const.DATA_CHORE_ICON: "mdi:star-four-points-outline", + }, + ) + await hass.async_block_till_done() + + helper_state_after_edit = _get_dashboard_helper_state( + hass, scenario_density_100, "Zoë" + ) + shard_entity_ids_after_edit = _get_shard_entity_ids(helper_state_after_edit) + assert ( + helper_state_after_edit.attributes[const.ATTR_SHARD_RUNTIME]["mode"] + == const.HELPER_SHARD_MODE_SHARDED + ) + assert shard_entity_ids_after_edit, { + "before": shard_entity_ids_before, + "after": shard_entity_ids_after_edit, + "runtime": helper_state_after_edit.attributes.get(const.ATTR_SHARD_RUNTIME), + "dashboard_helpers": helper_state_after_edit.attributes.get( + "dashboard_helpers" + ), + "registry": [ + entry.entity_id + for entry in er.async_entries_for_config_entry( + async_get_entity_registry(hass), + scenario_density_100.config_entry.entry_id, + ) + if "ui_dashboard_chore_list" in entry.entity_id + ], + } + assert set(shard_entity_ids_before).issubset(shard_entity_ids_after_edit), { + "before": shard_entity_ids_before, + "after": shard_entity_ids_after_edit, + "runtime": helper_state_after_edit.attributes.get(const.ATTR_SHARD_RUNTIME), + "dashboard_helpers": helper_state_after_edit.attributes.get( + "dashboard_helpers" + ), + } + assert len(_merge_helper_chores(hass, helper_state_after_edit)) == 100 + + chores_to_delete = [ + scenario_density_100.chore_ids[f"Zoë Dense Chore {index:03d}"] + for index in range(41, 101) + ] + await _sync_deleted_chores(scenario_density_100, chores_to_delete) + await hass.async_block_till_done() + + helper_state_inline = _get_dashboard_helper_state( + hass, scenario_density_100, "Zoë" + ) + assert ( + helper_state_inline.attributes[const.ATTR_SHARD_RUNTIME]["mode"] + == const.HELPER_SHARD_MODE_INLINE + ) + assert _get_shard_entity_ids(helper_state_inline) == [] + assert len(helper_state_inline.attributes["chores"]) == 40 + + remaining_registry_entries = [ + entry.entity_id + for entry in er.async_entries_for_config_entry( + async_get_entity_registry(hass), + scenario_density_100.config_entry.entry_id, + ) + if "zoe_choreops_dashboard_chore_shard" in entry.entity_id + ] + assert remaining_registry_entries == [] + + async def test_cross_user_transfer_reconciles_both_users_at_threshold_edge( + self, + hass: HomeAssistant, + scenario_density_100: SetupResult, + ) -> None: + """One reassignment should reconcile both users when shard pressure moves between them.""" + await scenario_density_100.coordinator.async_request_refresh() + await hass.async_block_till_done() + + await _trim_user_to_first_inline_plan( + scenario_density_100, + "Zoë", + ) + deleted_max_chores = await _trim_user_to_first_inline_plan( + scenario_density_100, + "Max!", + ) + await _restore_user_to_highest_inline_plan( + scenario_density_100, + "Max!", + deleted_max_chores, + ) + zoe_plan_before = _get_user_plan(scenario_density_100, "Zoë") + max_plan_before = _get_user_plan(scenario_density_100, "Max!") + assert ( + max_plan_before is not None + and max_plan_before.mode == const.HELPER_SHARD_MODE_INLINE + ) + assert ( + zoe_plan_before is not None + and zoe_plan_before.mode == const.HELPER_SHARD_MODE_INLINE + ) + + template_chore = next( + deepcopy(chore_info) + for chore_info in scenario_density_100.coordinator.chores_data.values() + if chore_info.get(const.DATA_CHORE_ASSIGNED_USER_IDS) + == [scenario_density_100.assignee_ids["Zoë"]] + ) + swing_chore_name = f"Zoë Threshold Swing {'X' * 640}" + swing_chore = deepcopy(template_chore) + swing_chore[const.DATA_CHORE_INTERNAL_ID] = str(uuid4()) + swing_chore[const.DATA_CHORE_NAME] = swing_chore_name + swing_chore[const.DATA_CHORE_LABELS] = [ + f"threshold-label-{index}-{'Y' * 192}" for index in range(1, 13) + ] + swing_chore[const.DATA_CHORE_ASSIGNED_USER_IDS] = [ + scenario_density_100.assignee_ids["Zoë"] + ] + + transfer_chore_id = await _sync_created_chore( + scenario_density_100, + swing_chore, + ) + await hass.async_block_till_done() + + zoe_helper_state_before = _get_dashboard_helper_state( + hass, scenario_density_100, "Zoë" + ) + assert ( + zoe_helper_state_before.attributes[const.ATTR_SHARD_RUNTIME]["mode"] + == const.HELPER_SHARD_MODE_SHARDED + ) + + transfer_chore = scenario_density_100.coordinator.chores_data[transfer_chore_id] + await _sync_updated_chore( + scenario_density_100, + transfer_chore_id, + { + const.DATA_CHORE_NAME: transfer_chore[const.DATA_CHORE_NAME], + const.DATA_CHORE_ASSIGNED_USER_IDS: [ + scenario_density_100.assignee_ids["Max!"] + ], + }, + ) + await hass.async_block_till_done() + + zoe_helper_state = _get_dashboard_helper_state( + hass, scenario_density_100, "Zoë" + ) + max_helper_state = _get_dashboard_helper_state( + hass, scenario_density_100, "Max!" + ) + + zoe_runtime_after = zoe_helper_state.attributes[const.ATTR_SHARD_RUNTIME] + max_runtime_after = max_helper_state.attributes[const.ATTR_SHARD_RUNTIME] + + assert ( + zoe_helper_state.attributes[const.ATTR_SHARD_RUNTIME]["mode"] + == const.HELPER_SHARD_MODE_INLINE + ) + assert ( + max_helper_state.attributes[const.ATTR_SHARD_RUNTIME]["mode"] + == const.HELPER_SHARD_MODE_SHARDED + ) + assert _get_shard_entity_ids(zoe_helper_state) == [] + assert ( + zoe_runtime_after["last_accepted_serialized_size"] + <= const.HELPER_SHARD_EXIT_BYTES + ) + assert ( + len(_get_shard_entity_ids(max_helper_state)) + == max_runtime_after["expected_shard_count"] + ) + assert max_runtime_after["last_accepted_serialized_size"] < ( + max_plan_before.last_accepted_serialized_size + ) + assert max_runtime_after["expected_shard_count"] > 0 + + async def test_main_and_shard_diagnostics_stay_minimal_and_consistent( + self, + hass: HomeAssistant, + scenario_density_100: SetupResult, + ) -> None: + """Diagnostics should stay on the main helper while shard helpers keep the minimal contract.""" + await scenario_density_100.coordinator.ui_manager.async_reconcile_chore_shards_for_users( + [scenario_density_100.assignee_ids["Zoë"]] + ) + await scenario_density_100.coordinator.async_request_refresh() + await hass.async_block_till_done() + + helper_state = _get_dashboard_helper_state(hass, scenario_density_100, "Zoë") + shard_runtime = helper_state.attributes[const.ATTR_SHARD_RUNTIME] + + assert set(shard_runtime) == { + "family", + "mode", + "expected_shard_count", + "last_accepted_serialized_size", + "last_reconciliation_outcome", + } + + shard_entity_ids = _get_shard_entity_ids(helper_state) + assert shard_runtime["expected_shard_count"] == len(shard_entity_ids) + + for expected_index, shard_entity_id in enumerate(shard_entity_ids, start=1): + shard_state = hass.states.get(shard_entity_id) + assert shard_state is not None + assert set(shard_state.attributes) == { + const.ATTR_PURPOSE, + const.ATTR_SHARD_INDEX, + const.ATTR_SHARD_COUNT, + const.ATTR_HELPER_CONTRACT_VERSION, + "chores", + "friendly_name", + } + assert shard_state.attributes[const.ATTR_SHARD_INDEX] == expected_index diff --git a/tests/test_dashboard_template_render_smoke.py b/tests/test_dashboard_template_render_smoke.py index e6e85a4..f4114b6 100644 --- a/tests/test_dashboard_template_render_smoke.py +++ b/tests/test_dashboard_template_render_smoke.py @@ -241,6 +241,7 @@ def _base_admin_chore_management_states( selected_dashboard_helper: str, chore_selector_state: str, chore_items: list[dict[str, object]], + chore_shard_items: list[list[dict[str, object]]] | None = None, selected_user_ui_control: dict[str, object] | None = None, chore_sensor_state: str = "pending", chore_sensor_attributes: dict[str, object] | None = None, @@ -249,6 +250,11 @@ def _base_admin_chore_management_states( selected_user_ui_control = selected_user_ui_control or {} chore_sensor_attributes = chore_sensor_attributes or {} + chore_shard_items = chore_shard_items or [] + chore_helper_eids = [ + f"sensor.alice_dashboard_chore_list_{index}" + for index in range(1, len(chore_shard_items) + 1) + ] mock_states = { "select.choreops_admin_target": _MockState( entity_id="select.choreops_admin_target", @@ -281,12 +287,14 @@ def _base_admin_chore_management_states( attributes={ "purpose": "purpose_dashboard_helper", "integration_entry_id": "entry-123", + "dashboard_lookup_key": "entry-123:user-alice", "user_name": "Alice", "user_id": "user-alice", "ui_control": selected_user_ui_control, "dashboard_helpers": { "translation_sensor_eid": "sensor.translations", "chore_select_eid": "select.alice_chores", + "chore_helper_eids": chore_helper_eids, }, "core_sensors": {}, "chores": chore_items, @@ -324,7 +332,21 @@ def _base_admin_chore_management_states( ), } - for chore_item in chore_items: + for chore_helper_eid, shard_items in zip( + chore_helper_eids, chore_shard_items, strict=False + ): + mock_states[chore_helper_eid] = _MockState( + entity_id=chore_helper_eid, + state="ok", + attributes={"chores": shard_items}, + name=chore_helper_eid, + ) + + for chore_item in chore_items + [ + shard_chore_item + for shard_group in chore_shard_items + for shard_chore_item in shard_group + ]: chore_entity_id = str(chore_item["eid"]) mock_states[chore_entity_id] = _MockState( entity_id=chore_entity_id, @@ -921,6 +943,86 @@ def test_admin_chore_management_shows_detail_for_selected_chore() -> None: assert parsed[2]["name"] == "Wash Dishes" +def test_admin_chore_management_resolves_selected_shard_backed_chore() -> None: + """Shared admin chore management resolves selected chores from shard helpers.""" + + chores_template = _get_admin_filter_template( + "admin-shared-v1", "===== CHORE MANAGEMENT =====" + ) + mock_states = _base_admin_chore_management_states( + selected_user_name="Alice", + selected_dashboard_helper="sensor.alice_dashboard_helper", + chore_selector_state="Wash Dishes", + chore_items=[], + chore_shard_items=[ + [ + { + "name": "Wash Dishes", + "eid": "sensor.alice_chore_wash_dishes", + "state": "pending", + } + ] + ], + selected_user_ui_control={ + "admin-shared": {"chore-management": {"header-collapse": False}} + }, + ) + env, integration_entities, states, state_attr = _build_runtime_template_env( + mock_states + ) + + output = env.from_string(chores_template).render( + integration_entities=integration_entities, + states=states, + state_attr=state_attr, + ) + parsed = yaml.safe_load(f"[{output}]") + + assert isinstance(parsed, list) + assert len(parsed) == 3 + assert parsed[2]["entity"] == "sensor.alice_chore_wash_dishes" + assert parsed[2]["name"] == "Wash Dishes" + + +def test_admin_peruser_chore_management_resolves_selected_shard_backed_chore() -> None: + """Per-user admin chore management resolves selected chores from shard helpers.""" + + chores_template = _get_admin_peruser_filter_template("===== CHORE MANAGEMENT =====") + mock_states = _base_admin_chore_management_states( + selected_user_name="Alice", + selected_dashboard_helper="sensor.alice_dashboard_helper", + chore_selector_state="Wash Dishes", + chore_items=[], + chore_shard_items=[ + [ + { + "name": "Wash Dishes", + "eid": "sensor.alice_chore_wash_dishes", + "state": "pending", + } + ] + ], + selected_user_ui_control={ + "admin-peruser": {"chore-management": {"header-collapse": False}} + }, + ) + env, integration_entities, states, state_attr = _build_runtime_template_env( + mock_states + ) + + output = env.from_string(chores_template).render( + integration_entities=integration_entities, + states=states, + state_attr=state_attr, + ) + parsed = yaml.safe_load(f"[{output}]") + + assert isinstance(parsed, list) + assert len(parsed) == 3 + assert parsed[2]["entity"] == "sensor.alice_chore_wash_dishes" + assert parsed[2]["name"] == "Wash Dishes" + + def test_admin_chore_management_collapsed_selected_chore_keeps_tinted_header() -> None: """Collapsed shared admin chore management keeps the tinted header for context.""" @@ -1307,6 +1409,26 @@ def test_shared_chore_engine_fragment_contains_ui_control_contract() -> None: assert "'/exclude_completed'" in template_str assert "'/exclude_blocked'" in template_str assert "'/sort_within_groups'" in template_str + assert "chore_helper_eids" in template_str + assert "state_attr(helper_eid, 'chores')" in template_str + assert "chores_by_label" not in template_str + + +def test_shared_chore_engine_group_render_uses_translation_sensor_reference() -> None: + """Shared group render should avoid duplicating the full UI label map per row.""" + template_str = _read_template("shared/chore_engine/group_render_v1.yaml") + + assert "'translation_sensor_eid': translation_sensor" in template_str + assert "'ui_labels': ui_labels" not in template_str + + +def test_shared_chore_engine_prepare_groups_rebuilds_labels_from_rows() -> None: + """Shared grouping fragment should rebuild labels from merged chore rows.""" + template_str = _read_template("shared/chore_engine/prepare_groups_v1.yaml") + + assert "label_group_candidates" in template_str + assert "ns.discovered_labels" in template_str + assert "chores_by_label_raw" not in template_str def test_user_gamification_premier_template_renders_with_button_card_templates() -> ( diff --git a/tests/test_options_flow_daily_multi.py b/tests/test_options_flow_daily_multi.py index 4d48cbb..c39fa65 100644 --- a/tests/test_options_flow_daily_multi.py +++ b/tests/test_options_flow_daily_multi.py @@ -196,6 +196,65 @@ async def test_of_03_helper_form_saves_times( assert chore is not None assert chore.get(DATA_CHORE_DAILY_MULTI_TIMES) == "08:00|17:00" + @pytest.mark.asyncio + async def test_of_03_helper_completion_does_not_mark_deferred_reload( + self, + hass: HomeAssistant, + scenario_minimal: SetupResult, + ) -> None: + """Completing the DAILY_MULTI helper should not mark chore deferred reload.""" + config_entry = scenario_minimal.config_entry + coordinator = scenario_minimal.coordinator + assignee_names = [k["name"] for k in coordinator.assignees_data.values()] + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + flow_id = result.get("flow_id") + result = await hass.config_entries.options.async_configure( + flow_id, # type: ignore[arg-type] + user_input={OPTIONS_FLOW_INPUT_MENU_SELECTION: OPTIONS_FLOW_CHORES}, + ) + result = await hass.config_entries.options.async_configure( + flow_id, # type: ignore[arg-type] + user_input={OPTIONS_FLOW_INPUT_MANAGE_ACTION: OPTIONS_FLOW_ACTIONS_ADD}, + ) + + due_date = datetime.now(UTC) + timedelta(hours=1) + with ( + patch.object( + coordinator.notification_manager, "notify_assignee", new=AsyncMock() + ), + patch( + "custom_components.choreops.options_flow.ChoreOpsOptionsFlowHandler._mark_reload_needed" + ) as reload_needed, + patch.object( + hass.config_entries, "async_reload", new=AsyncMock() + ) as reload_entry, + ): + result = await hass.config_entries.options.async_configure( + flow_id, # type: ignore[arg-type] + user_input={ + CFOF_CHORES_INPUT_NAME: "Daily Multi No Deferred Reload", + CFOF_CHORES_INPUT_DEFAULT_POINTS: 10, + CFOF_CHORES_INPUT_ICON: "mdi:check", + CFOF_CHORES_INPUT_DESCRIPTION: "", + CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: assignee_names[:1], + CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY_MULTI, + CFOF_CHORES_INPUT_COMPLETION_CRITERIA: COMPLETION_CRITERIA_SHARED, + CFOF_CHORES_INPUT_APPROVAL_RESET_TYPE: APPROVAL_RESET_UPON_COMPLETION, + CFOF_CHORES_INPUT_DUE_DATE: due_date, + }, + ) + assert result.get("step_id") == OPTIONS_FLOW_STEP_CHORES_DAILY_MULTI + + result = await hass.config_entries.options.async_configure( + flow_id, # type: ignore[arg-type] + user_input={CFOF_CHORES_INPUT_DAILY_MULTI_TIMES: "08:00|17:00"}, + ) + + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + reload_needed.assert_not_called() + reload_entry.assert_not_awaited() + @pytest.mark.asyncio async def test_of_04_helper_form_validates_format( self, diff --git a/tests/test_options_flow_entity_crud.py b/tests/test_options_flow_entity_crud.py index 5c0c8aa..c625846 100644 --- a/tests/test_options_flow_entity_crud.py +++ b/tests/test_options_flow_entity_crud.py @@ -8,13 +8,14 @@ import datetime from typing import Any -from unittest.mock import patch +from unittest.mock import AsyncMock, patch from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType import pytest from pytest_homeassistant_custom_component.common import MockConfigEntry +from custom_components.choreops import const as chore_const from custom_components.choreops.helpers.flow_helpers import ( CHORE_SECTION_ADVANCED_CONFIGURATIONS, CHORE_SECTION_ROOT_FORM, @@ -41,6 +42,8 @@ CFOF_CHORES_INPUT_NAME, CFOF_CHORES_INPUT_OVERDUE_HANDLING_TYPE, CFOF_CHORES_INPUT_RECURRING_FREQUENCY, + CFOF_SYSTEM_INPUT_POINTS_ICON, + CFOF_SYSTEM_INPUT_POINTS_LABEL, COMPLETION_CRITERIA_INDEPENDENT, COMPLETION_CRITERIA_ROTATION_SIMPLE, CONF_POINTS_ICON, @@ -53,9 +56,11 @@ OPTIONS_FLOW_ACHIEVEMENTS, OPTIONS_FLOW_ACTIONS_ADD, OPTIONS_FLOW_ACTIONS_BACK, + OPTIONS_FLOW_ACTIONS_EDIT, OPTIONS_FLOW_BADGES, OPTIONS_FLOW_BONUSES, OPTIONS_FLOW_CHORES, + OPTIONS_FLOW_INPUT_ENTITY_NAME, OPTIONS_FLOW_INPUT_MANAGE_ACTION, OPTIONS_FLOW_INPUT_MENU_SELECTION, OPTIONS_FLOW_PENALTIES, @@ -239,6 +244,36 @@ async def test_options_flow_back_to_menu( assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT +async def test_system_settings_points_still_reload_integration( + hass: HomeAssistant, + init_integration: MockConfigEntry, +) -> None: + """Test sanctioned system-settings edits still use config-entry reload.""" + result = await hass.config_entries.options.async_init(init_integration.entry_id) + flow_id = result.get("flow_id") + + result = await hass.config_entries.options.async_configure( + flow_id, + user_input={OPTIONS_FLOW_INPUT_MENU_SELECTION: chore_const.OPTIONS_FLOW_POINTS}, + ) + assert result.get("step_id") == chore_const.OPTIONS_FLOW_STEP_MANAGE_POINTS + + with patch.object( + hass.config_entries, "async_reload", new=AsyncMock() + ) as reload_entry: + result = await hass.config_entries.options.async_configure( + flow_id, + user_input={ + CFOF_SYSTEM_INPUT_POINTS_LABEL: "Stars", + CFOF_SYSTEM_INPUT_POINTS_ICON: "mdi:star-four-points", + chore_const.CFOF_SYSTEM_INPUT_DEFAULT_CHORE_POINTS: 2.5, + }, + ) + + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + reload_entry.assert_awaited_once_with(init_integration.entry_id) + + # ========================================================================= # Entity Add Tests (using FlowTestHelper converters) # ========================================================================= @@ -359,6 +394,336 @@ async def test_add_chore_via_options_flow( assert "Test Chore" in chore_names +async def test_add_chore_via_options_flow_uses_runtime_sync( + hass: HomeAssistant, + init_integration_with_coordinator: SetupResult, +) -> None: + """Test chore add uses runtime sync and does not mark deferred reload.""" + config_entry = init_integration_with_coordinator.config_entry + coordinator = init_integration_with_coordinator.coordinator + assignee_name = next(iter(c["name"] for c in coordinator.assignees_data.values())) + + form_data = FlowTestHelper.build_chore_form_data( + { + "name": "Runtime Sync Options Chore", + "points": 12, + "icon": "mdi:broom", + "type": "daily", + "assigned_to": [assignee_name], + "auto_approve": False, + "completion_criteria": "independent", + } + ) + + with ( + patch.object( + coordinator, + "async_sync_chore_entities", + new=AsyncMock(), + ) as mock_sync, + patch( + "custom_components.choreops.options_flow.ChoreOpsOptionsFlowHandler._mark_reload_needed" + ) as reload_needed, + ): + result = await FlowTestHelper.add_entity_via_options_flow( + hass, + config_entry.entry_id, + OPTIONS_FLOW_CHORES, + OPTIONS_FLOW_STEP_ADD_CHORE, + form_data, + ) + + assert result.get("type") == FlowResultType.FORM + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + mock_sync.assert_awaited_once() + reload_needed.assert_not_called() + + +async def test_edit_chore_via_options_flow_uses_runtime_sync( + hass: HomeAssistant, + init_integration_with_coordinator: SetupResult, +) -> None: + """Test chore edit uses runtime sync and does not mark deferred reload.""" + config_entry = init_integration_with_coordinator.config_entry + coordinator = init_integration_with_coordinator.coordinator + assignee_name = next(iter(c["name"] for c in coordinator.assignees_data.values())) + + await FlowTestHelper.add_entity_via_options_flow( + hass, + config_entry.entry_id, + OPTIONS_FLOW_CHORES, + OPTIONS_FLOW_STEP_ADD_CHORE, + FlowTestHelper.build_chore_form_data( + { + "name": "Options Flow Edit Target", + "points": 10, + "icon": "mdi:broom", + "type": "daily", + "assigned_to": [assignee_name], + "auto_approve": False, + "completion_criteria": "independent", + } + ), + ) + + with ( + patch.object( + coordinator, + "async_sync_chore_entities", + new=AsyncMock(), + ) as mock_sync, + patch( + "custom_components.choreops.options_flow.ChoreOpsOptionsFlowHandler._mark_reload_needed" + ) as reload_needed, + ): + result = await FlowTestHelper.edit_entity_via_options_flow( + hass, + config_entry.entry_id, + OPTIONS_FLOW_CHORES, + "Options Flow Edit Target", + FlowTestHelper.build_chore_form_data( + { + "name": "Options Flow Edit Target Renamed", + "points": 44, + "icon": "mdi:broom", + "type": "daily", + "assigned_to": [assignee_name], + "auto_approve": False, + "completion_criteria": "independent", + } + ), + ) + + assert result.get("type") == FlowResultType.FORM + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + mock_sync.assert_awaited_once() + reload_needed.assert_not_called() + + +async def test_edit_chore_sparse_payload_preserves_representative_optional_patterns( + hass: HomeAssistant, + init_integration_with_coordinator: SetupResult, +) -> None: + """Sparse chore edit should preserve representative omitted optional fields.""" + config_entry = init_integration_with_coordinator.config_entry + coordinator = init_integration_with_coordinator.coordinator + assignee_name = next(iter(c["name"] for c in coordinator.assignees_data.values())) + + add_result = await FlowTestHelper.add_entity_via_options_flow( + hass, + config_entry.entry_id, + OPTIONS_FLOW_CHORES, + OPTIONS_FLOW_STEP_ADD_CHORE, + { + chore_const.CFOF_CHORES_INPUT_NAME: "Sparse Pattern Preserve", + chore_const.CFOF_CHORES_INPUT_DEFAULT_POINTS: 10, + chore_const.CFOF_CHORES_INPUT_ICON: "mdi:broom", + chore_const.CFOF_CHORES_INPUT_DESCRIPTION: "before", + chore_const.CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: [assignee_name], + chore_const.CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + chore_const.CFOF_CHORES_INPUT_COMPLETION_CRITERIA: chore_const.COMPLETION_CRITERIA_SHARED, + chore_const.CFOF_CHORES_INPUT_DUE_DATE: datetime.datetime.now(datetime.UTC) + + datetime.timedelta(days=3), + chore_const.CFOF_CHORES_INPUT_APPLICABLE_DAYS: ["mon", "wed"], + chore_const.CFOF_CHORES_INPUT_DUE_WINDOW_OFFSET: "4h", + chore_const.CFOF_CHORES_INPUT_DUE_REMINDER_OFFSET: "30m", + chore_const.CFOF_CHORES_INPUT_CLAIM_LOCK_UNTIL_WINDOW: True, + chore_const.CFOF_CHORES_INPUT_AUTO_APPROVE: True, + chore_const.CFOF_CHORES_INPUT_NOTIFICATIONS: [ + chore_const.DATA_CHORE_NOTIFY_ON_CLAIM, + chore_const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW, + ], + }, + ) + assert add_result.get("step_id") == OPTIONS_FLOW_STEP_INIT + + menu_result = await FlowTestHelper.navigate_to_entity_menu( + hass, config_entry.entry_id, OPTIONS_FLOW_CHORES + ) + edit_action_result = await hass.config_entries.options.async_configure( + menu_result["flow_id"], + user_input={OPTIONS_FLOW_INPUT_MANAGE_ACTION: OPTIONS_FLOW_ACTIONS_EDIT}, + ) + select_result = await hass.config_entries.options.async_configure( + edit_action_result["flow_id"], + user_input={OPTIONS_FLOW_INPUT_ENTITY_NAME: "Sparse Pattern Preserve"}, + ) + result = await hass.config_entries.options.async_configure( + select_result["flow_id"], + user_input={ + CHORE_SECTION_ROOT_FORM: { + chore_const.CFOF_CHORES_INPUT_NAME: "Sparse Pattern Preserve", + chore_const.CFOF_CHORES_INPUT_DEFAULT_POINTS: 10, + chore_const.CFOF_CHORES_INPUT_ICON: "mdi:broom", + chore_const.CFOF_CHORES_INPUT_DESCRIPTION: "after", + chore_const.CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: [assignee_name], + chore_const.CFOF_CHORES_INPUT_COMPLETION_CRITERIA: chore_const.COMPLETION_CRITERIA_SHARED, + }, + CHORE_SECTION_SCHEDULE: { + chore_const.CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + chore_const.CFOF_CHORES_INPUT_CLAIM_LOCK_UNTIL_WINDOW: True, + }, + CHORE_SECTION_ADVANCED_CONFIGURATIONS: { + chore_const.CFOF_CHORES_INPUT_APPROVAL_RESET_TYPE: APPROVAL_RESET_UPON_COMPLETION, + chore_const.CFOF_CHORES_INPUT_OVERDUE_HANDLING_TYPE: OVERDUE_HANDLING_AT_DUE_DATE, + chore_const.CFOF_CHORES_INPUT_AUTO_APPROVE: True, + chore_const.CFOF_CHORES_INPUT_SHOW_ON_CALENDAR: True, + }, + }, + ) + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + + stored_chore = next( + chore + for chore in coordinator.chores_data.values() + if chore["name"] == "Sparse Pattern Preserve" + ) + assert stored_chore.get(chore_const.DATA_CHORE_DUE_WINDOW_OFFSET) == "4h" + assert stored_chore.get(chore_const.DATA_CHORE_DUE_REMINDER_OFFSET) == "30m" + assert stored_chore.get(chore_const.DATA_CHORE_NOTIFY_ON_CLAIM) is True + assert stored_chore.get(chore_const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW) is True + assert stored_chore.get(chore_const.DATA_CHORE_APPLICABLE_DAYS) == [0, 2] + + +async def test_edit_chore_sparse_payload_explicit_clear_stays_distinct( + hass: HomeAssistant, + init_integration_with_coordinator: SetupResult, +) -> None: + """Explicit clear_due_date should still clear while omitted fields preserve.""" + config_entry = init_integration_with_coordinator.config_entry + coordinator = init_integration_with_coordinator.coordinator + assignee_name = next(iter(c["name"] for c in coordinator.assignees_data.values())) + + add_result = await FlowTestHelper.add_entity_via_options_flow( + hass, + config_entry.entry_id, + OPTIONS_FLOW_CHORES, + OPTIONS_FLOW_STEP_ADD_CHORE, + { + chore_const.CFOF_CHORES_INPUT_NAME: "Sparse Clear Control", + chore_const.CFOF_CHORES_INPUT_DEFAULT_POINTS: 10, + chore_const.CFOF_CHORES_INPUT_ICON: "mdi:broom", + chore_const.CFOF_CHORES_INPUT_DESCRIPTION: "before", + chore_const.CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: [assignee_name], + chore_const.CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + chore_const.CFOF_CHORES_INPUT_COMPLETION_CRITERIA: chore_const.COMPLETION_CRITERIA_SHARED, + chore_const.CFOF_CHORES_INPUT_DUE_DATE: datetime.datetime.now(datetime.UTC) + + datetime.timedelta(days=2), + chore_const.CFOF_CHORES_INPUT_DUE_WINDOW_OFFSET: "2h", + chore_const.CFOF_CHORES_INPUT_CLAIM_LOCK_UNTIL_WINDOW: True, + chore_const.CFOF_CHORES_INPUT_AUTO_APPROVE: True, + }, + ) + assert add_result.get("step_id") == OPTIONS_FLOW_STEP_INIT + + menu_result = await FlowTestHelper.navigate_to_entity_menu( + hass, config_entry.entry_id, OPTIONS_FLOW_CHORES + ) + edit_action_result = await hass.config_entries.options.async_configure( + menu_result["flow_id"], + user_input={OPTIONS_FLOW_INPUT_MANAGE_ACTION: OPTIONS_FLOW_ACTIONS_EDIT}, + ) + select_result = await hass.config_entries.options.async_configure( + edit_action_result["flow_id"], + user_input={OPTIONS_FLOW_INPUT_ENTITY_NAME: "Sparse Clear Control"}, + ) + result = await hass.config_entries.options.async_configure( + select_result["flow_id"], + user_input={ + CHORE_SECTION_ROOT_FORM: { + chore_const.CFOF_CHORES_INPUT_NAME: "Sparse Clear Control", + chore_const.CFOF_CHORES_INPUT_DEFAULT_POINTS: 10, + chore_const.CFOF_CHORES_INPUT_ICON: "mdi:broom", + chore_const.CFOF_CHORES_INPUT_DESCRIPTION: "after", + chore_const.CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: [assignee_name], + chore_const.CFOF_CHORES_INPUT_COMPLETION_CRITERIA: chore_const.COMPLETION_CRITERIA_SHARED, + }, + CHORE_SECTION_SCHEDULE: { + chore_const.CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + chore_const.CFOF_CHORES_INPUT_CLAIM_LOCK_UNTIL_WINDOW: True, + chore_const.CFOF_CHORES_INPUT_CLEAR_DUE_DATE: True, + }, + CHORE_SECTION_ADVANCED_CONFIGURATIONS: { + chore_const.CFOF_CHORES_INPUT_APPROVAL_RESET_TYPE: APPROVAL_RESET_UPON_COMPLETION, + chore_const.CFOF_CHORES_INPUT_OVERDUE_HANDLING_TYPE: OVERDUE_HANDLING_AT_DUE_DATE, + chore_const.CFOF_CHORES_INPUT_AUTO_APPROVE: True, + chore_const.CFOF_CHORES_INPUT_SHOW_ON_CALENDAR: True, + }, + }, + ) + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + + stored_chore = next( + chore + for chore in coordinator.chores_data.values() + if chore["name"] == "Sparse Clear Control" + ) + assert stored_chore.get(chore_const.DATA_CHORE_DUE_DATE) is None + assert stored_chore.get(chore_const.DATA_CHORE_DUE_WINDOW_OFFSET) == "2h" + + +async def test_delete_chore_via_options_flow_uses_runtime_sync( + hass: HomeAssistant, + init_integration_with_coordinator: SetupResult, +) -> None: + """Test chore delete uses runtime sync and does not mark deferred reload.""" + config_entry = init_integration_with_coordinator.config_entry + coordinator = init_integration_with_coordinator.coordinator + assignee_name = next(iter(c["name"] for c in coordinator.assignees_data.values())) + + await FlowTestHelper.add_entity_via_options_flow( + hass, + config_entry.entry_id, + OPTIONS_FLOW_CHORES, + OPTIONS_FLOW_STEP_ADD_CHORE, + FlowTestHelper.build_chore_form_data( + { + "name": "Options Flow Delete Target", + "points": 10, + "icon": "mdi:broom", + "type": "daily", + "assigned_to": [assignee_name], + "auto_approve": False, + "completion_criteria": "independent", + } + ), + ) + + menu_result = await FlowTestHelper.navigate_to_entity_menu( + hass, config_entry.entry_id, OPTIONS_FLOW_CHORES + ) + delete_action_result = await hass.config_entries.options.async_configure( + menu_result["flow_id"], + user_input={ + OPTIONS_FLOW_INPUT_MANAGE_ACTION: chore_const.OPTIONS_FLOW_ACTIONS_DELETE + }, + ) + select_result = await hass.config_entries.options.async_configure( + delete_action_result["flow_id"], + user_input={OPTIONS_FLOW_INPUT_ENTITY_NAME: "Options Flow Delete Target"}, + ) + + with ( + patch.object( + coordinator, + "async_sync_chore_entities", + new=AsyncMock(), + ) as mock_sync, + patch( + "custom_components.choreops.options_flow.ChoreOpsOptionsFlowHandler._mark_reload_needed" + ) as reload_needed, + ): + result = await hass.config_entries.options.async_configure( + select_result["flow_id"], + user_input={}, + ) + + assert result.get("type") == FlowResultType.FORM + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + mock_sync.assert_awaited_once() + reload_needed.assert_not_called() + + async def test_add_reward_via_options_flow( hass: HomeAssistant, init_integration_with_coordinator: SetupResult, @@ -675,7 +1040,7 @@ async def test_add_entities_from_minimal_scenario( # Verify flow succeeded (returns to init) assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT - # After options flow add, integration reloads - get fresh coordinator + # Non-chore options-flow add paths may still reload, so re-read coordinator here. coordinator = config_entry.runtime_data assignee_names = [p["name"] for p in coordinator.assignees_data.values()] assert "New Scenario User" in assignee_names @@ -693,7 +1058,7 @@ async def test_add_entities_from_minimal_scenario( # Verify flow succeeded (returns to init) assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT - # Get fresh coordinator again after reload + # Re-read coordinator again because non-chore options flow can still reload. coordinator = config_entry.runtime_data reward_names = [r["name"] for r in coordinator.rewards_data.values()] assert "New Scenario Reward" in reward_names diff --git a/tests/test_options_flow_per_kid_helper.py b/tests/test_options_flow_per_kid_helper.py index 427503d..338ffb5 100644 --- a/tests/test_options_flow_per_kid_helper.py +++ b/tests/test_options_flow_per_kid_helper.py @@ -15,7 +15,7 @@ from datetime import UTC from typing import Any -from unittest.mock import patch +from unittest.mock import AsyncMock, patch from homeassistant.config_entries import ConfigFlowResult from homeassistant.core import HomeAssistant @@ -173,6 +173,59 @@ async def navigate_to_edit_chore( return result +async def create_shared_chore_for_sparse_edit( + hass: HomeAssistant, + entry_id: str, + *, + assignee_name: str, + chore_name: str, + due_date: Any, + due_window_offset: str = "3h", + due_reminder_offset: str = "20m", + applicable_days: list[str] | None = None, + notifications: list[str] | None = None, +) -> None: + """Create a shared chore with non-default optional values for sparse-edit tests.""" + result = await navigate_to_add_chore(hass, entry_id) + + user_input: dict[str, Any] = { + CFOF_CHORES_INPUT_NAME: chore_name, + CFOF_CHORES_INPUT_DEFAULT_POINTS: 12.0, + CFOF_CHORES_INPUT_ICON: "mdi:shield-lock", + CFOF_CHORES_INPUT_DESCRIPTION: "before", + CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: [assignee_name], + CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + CFOF_CHORES_INPUT_COMPLETION_CRITERIA: COMPLETION_CRITERIA_SHARED, + CFOF_CHORES_INPUT_DUE_DATE: due_date, + const.CFOF_CHORES_INPUT_DUE_WINDOW_OFFSET: due_window_offset, + const.CFOF_CHORES_INPUT_DUE_REMINDER_OFFSET: due_reminder_offset, + const.CFOF_CHORES_INPUT_CLAIM_LOCK_UNTIL_WINDOW: True, + const.CFOF_CHORES_INPUT_AUTO_APPROVE: True, + } + + if applicable_days is not None: + user_input[CFOF_CHORES_INPUT_APPLICABLE_DAYS] = applicable_days + if notifications is not None: + user_input[const.CFOF_CHORES_INPUT_NOTIFICATIONS] = notifications + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input=user_input, + ) + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + + +def get_stored_chore_by_name( + setup_result: SetupResult, chore_name: str +) -> dict[str, Any]: + """Return a stored chore item by name from a setup result coordinator.""" + return next( + chore + for chore in setup_result.coordinator.chores_data.values() + if chore.get("name") == chore_name + ) + + # ========================================================================= # PKH-01: Add INDEPENDENT chore with 2 assignees (no template) # ========================================================================= @@ -337,6 +390,61 @@ async def test_pkh02_add_independent_2assignees_with_template_date( assert len(date_values) == 2 assert date_values[0] == date_values[1], "All assignees should have same date" + async def test_pkh02_helper_completion_does_not_mark_deferred_reload( + self, + hass: HomeAssistant, + scenario_shared: SetupResult, + ) -> None: + """Completing the per-assignee helper should not mark chore deferred reload.""" + from datetime import datetime, timedelta + + config_entry = scenario_shared.config_entry + coordinator = scenario_shared.coordinator + assignee_names = [k["name"] for k in coordinator.assignees_data.values()] + assigned_assignees = assignee_names[:2] + future_date = datetime.now(UTC) + timedelta(days=7) + + with ( + patch( + "custom_components.choreops.options_flow.ChoreOpsOptionsFlowHandler._mark_reload_needed" + ) as reload_needed, + patch.object( + hass.config_entries, "async_reload", new=AsyncMock() + ) as reload_entry, + ): + result = await navigate_to_add_chore(hass, config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CFOF_CHORES_INPUT_NAME: "PKH02 No Reload Helper", + CFOF_CHORES_INPUT_DEFAULT_POINTS: 20.0, + CFOF_CHORES_INPUT_ICON: "mdi:calendar", + CFOF_CHORES_INPUT_DESCRIPTION: "No deferred reload", + CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: assigned_assignees, + CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + CFOF_CHORES_INPUT_COMPLETION_CRITERIA: COMPLETION_CRITERIA_INDEPENDENT, + CFOF_CHORES_INPUT_DUE_DATE: future_date, + }, + ) + assert ( + result.get("step_id") == OPTIONS_FLOW_STEP_EDIT_CHORE_PER_USER_DETAILS + ) + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CFOF_CHORES_INPUT_APPLY_TEMPLATE_TO_ALL: True, + **{ + f"applicable_days_{assignee_name}": ["mon", "tue"] + for assignee_name in assigned_assignees + }, + }, + ) + + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + reload_needed.assert_not_called() + reload_entry.assert_not_awaited() + async def test_pkh03_add_independent_2assignees_with_template_days( self, hass: HomeAssistant, @@ -1394,6 +1502,239 @@ async def test_esv06_edit_partial_section_payload_preserves_schedule_fields( assert edited.get(const.DATA_CHORE_NOTIFY_ON_CLAIM) is True assert edited.get(const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW) is True + async def test_esv06a_sparse_edit_preserves_schedule_collection_field( + self, + hass: HomeAssistant, + scenario_shared: SetupResult, + ) -> None: + """Sparse edit should preserve stored applicable_days when omitted.""" + from datetime import datetime, timedelta + + config_entry = scenario_shared.config_entry + assignee_name = next( + iter(k["name"] for k in scenario_shared.coordinator.assignees_data.values()) + ) + chore_name = "ESV06A Preserve Applicable Days" + + await create_shared_chore_for_sparse_edit( + hass, + config_entry.entry_id, + assignee_name=assignee_name, + chore_name=chore_name, + due_date=datetime.now(UTC) + timedelta(days=3), + applicable_days=["mon", "wed", "fri"], + notifications=[ + const.DATA_CHORE_NOTIFY_ON_CLAIM, + const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW, + ], + ) + + result = await navigate_to_edit_chore(hass, config_entry.entry_id, chore_name) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + fh.CHORE_SECTION_ROOT_FORM: { + CFOF_CHORES_INPUT_NAME: chore_name, + CFOF_CHORES_INPUT_DEFAULT_POINTS: 12.0, + CFOF_CHORES_INPUT_ICON: "mdi:shield-lock", + CFOF_CHORES_INPUT_DESCRIPTION: "after", + CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: [assignee_name], + CFOF_CHORES_INPUT_COMPLETION_CRITERIA: COMPLETION_CRITERIA_SHARED, + }, + fh.CHORE_SECTION_SCHEDULE: { + CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + const.CFOF_CHORES_INPUT_CLAIM_LOCK_UNTIL_WINDOW: True, + }, + fh.CHORE_SECTION_ADVANCED_CONFIGURATIONS: { + const.CFOF_CHORES_INPUT_APPROVAL_RESET_TYPE: const.DEFAULT_APPROVAL_RESET_TYPE, + const.CFOF_CHORES_INPUT_OVERDUE_HANDLING_TYPE: const.DEFAULT_OVERDUE_HANDLING_TYPE, + const.CFOF_CHORES_INPUT_AUTO_APPROVE: True, + const.CFOF_CHORES_INPUT_SHOW_ON_CALENDAR: True, + }, + }, + ) + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + + edited = get_stored_chore_by_name(scenario_shared, chore_name) + assert edited.get(const.DATA_CHORE_APPLICABLE_DAYS) == [0, 2, 4] + + async def test_esv06b_sparse_edit_preserves_advanced_notification_fields( + self, + hass: HomeAssistant, + scenario_shared: SetupResult, + ) -> None: + """Sparse edit should preserve advanced notification fan-out when omitted.""" + from datetime import datetime, timedelta + + config_entry = scenario_shared.config_entry + assignee_name = next( + iter(k["name"] for k in scenario_shared.coordinator.assignees_data.values()) + ) + chore_name = "ESV06B Preserve Notifications" + + await create_shared_chore_for_sparse_edit( + hass, + config_entry.entry_id, + assignee_name=assignee_name, + chore_name=chore_name, + due_date=datetime.now(UTC) + timedelta(days=3), + notifications=[ + const.DATA_CHORE_NOTIFY_ON_CLAIM, + const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW, + ], + ) + + result = await navigate_to_edit_chore(hass, config_entry.entry_id, chore_name) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + fh.CHORE_SECTION_ROOT_FORM: { + CFOF_CHORES_INPUT_NAME: chore_name, + CFOF_CHORES_INPUT_DEFAULT_POINTS: 12.0, + CFOF_CHORES_INPUT_ICON: "mdi:shield-lock", + CFOF_CHORES_INPUT_DESCRIPTION: "after", + CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: [assignee_name], + CFOF_CHORES_INPUT_COMPLETION_CRITERIA: COMPLETION_CRITERIA_SHARED, + }, + fh.CHORE_SECTION_SCHEDULE: { + CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + const.CFOF_CHORES_INPUT_CLAIM_LOCK_UNTIL_WINDOW: True, + const.CFOF_CHORES_INPUT_DUE_WINDOW_OFFSET: "3h", + }, + fh.CHORE_SECTION_ADVANCED_CONFIGURATIONS: { + const.CFOF_CHORES_INPUT_APPROVAL_RESET_TYPE: const.DEFAULT_APPROVAL_RESET_TYPE, + const.CFOF_CHORES_INPUT_OVERDUE_HANDLING_TYPE: const.DEFAULT_OVERDUE_HANDLING_TYPE, + const.CFOF_CHORES_INPUT_AUTO_APPROVE: True, + const.CFOF_CHORES_INPUT_SHOW_ON_CALENDAR: True, + }, + }, + ) + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + + edited = get_stored_chore_by_name(scenario_shared, chore_name) + assert edited.get(const.DATA_CHORE_DUE_REMINDER_OFFSET) == "20m" + assert edited.get(const.DATA_CHORE_NOTIFY_ON_CLAIM) is True + assert edited.get(const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW) is True + + async def test_esv06c_sparse_edit_explicit_overwrite_updates_stored_value( + self, + hass: HomeAssistant, + scenario_shared: SetupResult, + ) -> None: + """Sparse edit should still apply explicit non-default updates.""" + from datetime import datetime, timedelta + + config_entry = scenario_shared.config_entry + assignee_name = next( + iter(k["name"] for k in scenario_shared.coordinator.assignees_data.values()) + ) + chore_name = "ESV06C Explicit Overwrite" + + await create_shared_chore_for_sparse_edit( + hass, + config_entry.entry_id, + assignee_name=assignee_name, + chore_name=chore_name, + due_date=datetime.now(UTC) + timedelta(days=3), + notifications=[ + const.DATA_CHORE_NOTIFY_ON_CLAIM, + const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW, + ], + ) + + result = await navigate_to_edit_chore(hass, config_entry.entry_id, chore_name) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + fh.CHORE_SECTION_ROOT_FORM: { + CFOF_CHORES_INPUT_NAME: chore_name, + CFOF_CHORES_INPUT_DEFAULT_POINTS: 12.0, + CFOF_CHORES_INPUT_ICON: "mdi:shield-lock", + CFOF_CHORES_INPUT_DESCRIPTION: "after", + CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: [assignee_name], + CFOF_CHORES_INPUT_COMPLETION_CRITERIA: COMPLETION_CRITERIA_SHARED, + }, + fh.CHORE_SECTION_SCHEDULE: { + CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + const.CFOF_CHORES_INPUT_CLAIM_LOCK_UNTIL_WINDOW: True, + const.CFOF_CHORES_INPUT_DUE_WINDOW_OFFSET: "6h", + }, + fh.CHORE_SECTION_ADVANCED_CONFIGURATIONS: { + const.CFOF_CHORES_INPUT_APPROVAL_RESET_TYPE: const.DEFAULT_APPROVAL_RESET_TYPE, + const.CFOF_CHORES_INPUT_OVERDUE_HANDLING_TYPE: const.DEFAULT_OVERDUE_HANDLING_TYPE, + const.CFOF_CHORES_INPUT_AUTO_APPROVE: True, + const.CFOF_CHORES_INPUT_SHOW_ON_CALENDAR: True, + const.CFOF_CHORES_INPUT_DUE_REMINDER_OFFSET: "45m", + const.CFOF_CHORES_INPUT_NOTIFICATIONS: [ + const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW, + ], + }, + }, + ) + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + + edited = get_stored_chore_by_name(scenario_shared, chore_name) + assert edited.get(const.DATA_CHORE_DUE_WINDOW_OFFSET) == "6h" + assert edited.get(const.DATA_CHORE_DUE_REMINDER_OFFSET) == "45m" + assert edited.get(const.DATA_CHORE_NOTIFY_ON_CLAIM) is False + assert edited.get(const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW) is True + + async def test_esv06d_explicit_clear_due_date_remains_distinct_from_omission( + self, + hass: HomeAssistant, + scenario_shared: SetupResult, + ) -> None: + """Explicit clear_due_date should clear stored due date in the full edit flow.""" + from datetime import datetime, timedelta + + config_entry = scenario_shared.config_entry + assignee_name = next( + iter(k["name"] for k in scenario_shared.coordinator.assignees_data.values()) + ) + chore_name = "ESV06D Clear Due Date" + + await create_shared_chore_for_sparse_edit( + hass, + config_entry.entry_id, + assignee_name=assignee_name, + chore_name=chore_name, + due_date=datetime.now(UTC) + timedelta(days=3), + notifications=[ + const.DATA_CHORE_NOTIFY_ON_CLAIM, + const.DATA_CHORE_NOTIFY_ON_DUE_WINDOW, + ], + ) + + result = await navigate_to_edit_chore(hass, config_entry.entry_id, chore_name) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + fh.CHORE_SECTION_ROOT_FORM: { + CFOF_CHORES_INPUT_NAME: chore_name, + CFOF_CHORES_INPUT_DEFAULT_POINTS: 12.0, + CFOF_CHORES_INPUT_ICON: "mdi:shield-lock", + CFOF_CHORES_INPUT_DESCRIPTION: "after", + CFOF_CHORES_INPUT_ASSIGNED_USER_IDS: [assignee_name], + CFOF_CHORES_INPUT_COMPLETION_CRITERIA: COMPLETION_CRITERIA_SHARED, + }, + fh.CHORE_SECTION_SCHEDULE: { + CFOF_CHORES_INPUT_RECURRING_FREQUENCY: FREQUENCY_DAILY, + const.CFOF_CHORES_INPUT_CLAIM_LOCK_UNTIL_WINDOW: True, + const.CFOF_CHORES_INPUT_CLEAR_DUE_DATE: True, + }, + fh.CHORE_SECTION_ADVANCED_CONFIGURATIONS: { + const.CFOF_CHORES_INPUT_APPROVAL_RESET_TYPE: const.DEFAULT_APPROVAL_RESET_TYPE, + const.CFOF_CHORES_INPUT_OVERDUE_HANDLING_TYPE: const.DEFAULT_OVERDUE_HANDLING_TYPE, + const.CFOF_CHORES_INPUT_AUTO_APPROVE: True, + const.CFOF_CHORES_INPUT_SHOW_ON_CALENDAR: True, + }, + }, + ) + assert result.get("step_id") == OPTIONS_FLOW_STEP_INIT + + edited = get_stored_chore_by_name(scenario_shared, chore_name) + assert edited.get(const.DATA_CHORE_DUE_DATE) is None + async def test_esv07_transform_clear_due_date_explicitly_clears_due_date( self, ) -> None: diff --git a/utils/bundle_codebase.py b/utils/bundle_codebase.py index f9efe63..8d91a93 100644 --- a/utils/bundle_codebase.py +++ b/utils/bundle_codebase.py @@ -1,4 +1,5 @@ # ruff: noqa: INP001 +import argparse from datetime import date import os from pathlib import Path @@ -9,10 +10,131 @@ # Dynamically find paths based on where this script lives SCRIPT_DIR = Path(__file__).resolve().parent # This is always choreops/utils/ WORKSPACE_ROOT = (SCRIPT_DIR / ".." / "..").resolve() # This is the workspace root +CHOREOPS_REPO_ROOT = (SCRIPT_DIR / "..").resolve() + +BACKEND_TRANSLATION_DIRS = ( + Path("choreops/custom_components/choreops/translations"), + Path("choreops/custom_components/choreops/translations_custom"), +) + +BACKEND_MANAGER_DIR = Path("choreops/custom_components/choreops/managers") + +BACKEND_VENDORED_DASHBOARD_DIR = Path("choreops/custom_components/choreops/dashboards") + +BACKEND_TEST_DIR = Path("choreops/tests") + +BACKEND_TRANSLATION_SOURCE_DIRS = ( + CHOREOPS_REPO_ROOT / "custom_components/choreops/translations", + CHOREOPS_REPO_ROOT / "custom_components/choreops/translations_custom", +) + + +def _should_skip_dir( + root: Path, dir_name: str, excluded_dirs: tuple[Path, ...] +) -> bool: + """Return whether a directory should be skipped during os.walk.""" + if dir_name.startswith(".") or dir_name in ("__pycache__", "exports"): + return True + + candidate = (root / dir_name).relative_to(WORKSPACE_ROOT) + return any( + candidate == excluded_dir or excluded_dir in candidate.parents + for excluded_dir in excluded_dirs + ) + + +def _write_file_to_bundle(outfile, file_path: Path) -> None: + """Write one file into the bundle output with a path header.""" + rel_path = file_path.relative_to(WORKSPACE_ROOT) + + outfile.write(f"\n\n{'=' * 80}\n") + outfile.write(f"FILE PATH: {rel_path}\n") + outfile.write(f"{'=' * 80}\n\n") + + try: + with file_path.open(encoding="utf-8") as infile: + outfile.write(infile.read()) + except Exception as err: + outfile.write(f"[Error reading file: {err}]\n") + print(f"❌ Skipped {rel_path} due to read error.") # noqa: T201 + + +def _resolve_backend_translation_files(language_selector: str) -> list[Path]: + """Return backend translation files for one language or for all languages.""" + translation_files: list[Path] = [] + + for translation_dir in BACKEND_TRANSLATION_SOURCE_DIRS: + if not translation_dir.exists(): + continue + + for file_path in sorted(translation_dir.glob("*.json")): + stem = file_path.stem + language_code = stem.split("_", maxsplit=1)[0] + + if language_selector not in ("all", language_code): + continue + + translation_files.append(file_path) + + return translation_files + + +def bundle_backend_translations(language_selector: str) -> None: + """Bundle backend translation files into a dedicated export.""" + translation_files = _resolve_backend_translation_files(language_selector) + + if not translation_files: + print( # noqa: T201 + "⚠️ Warning: no backend translation files matched the requested language selector." + ) + return + + export_dir = SCRIPT_DIR / "exports" + export_dir.mkdir(parents=True, exist_ok=True) + + dated_filename = ( + f"gem_choreops_backend_translations_{language_selector}_{CURRENT_DATE}.txt" + ) + output_path = export_dir / dated_filename + + print( # noqa: T201 + "📦 Bundling backend translations into " + f"'choreops/utils/exports/{dated_filename}' using selector '{language_selector}'..." + ) + + with output_path.open("w", encoding="utf-8") as outfile: + for file_path in translation_files: + _write_file_to_bundle(outfile, file_path) + + +def parse_args() -> argparse.Namespace: + """Parse command-line options for bundle export behavior.""" + parser = argparse.ArgumentParser( + description="Bundle ChoreOps codebase exports for external import tools." + ) + parser.add_argument( + "--backend-translations", + default="en", + metavar="LANG", + help=( + "Backend translation export selector: default 'en', use 'all' for every " + "language, or a specific two-letter code such as 'fr' or 'de'." + ), + ) + parser.add_argument( + "--include-tests", + action="store_true", + help="Also create a separate backend tests export. Tests are excluded by default.", + ) + return parser.parse_args() def bundle_for_gem( - source_folder_name: str, output_filename: str, allowed_extensions: tuple[str, ...] + source_folder_name: str, + output_filename: str, + allowed_extensions: tuple[str, ...], + *, + excluded_dirs: tuple[Path, ...] = (), ) -> None: """Walks a directory and concatenates files into a single LLM-friendly document.""" source_dir = WORKSPACE_ROOT / source_folder_name @@ -41,40 +163,67 @@ def bundle_for_gem( root = Path(root_str) # Skip hidden dirs, Python caches, and our new central 'exports' directory! - dirs[:] = [ - d - for d in dirs - if not d.startswith(".") and d not in ("__pycache__", "exports") - ] + dirs[:] = [d for d in dirs if not _should_skip_dir(root, d, excluded_dirs)] for file in files: if file.endswith(allowed_extensions): file_path = root / file rel_path = file_path.relative_to(WORKSPACE_ROOT) - outfile.write(f"\n\n{'=' * 80}\n") - outfile.write(f"FILE PATH: {rel_path}\n") - outfile.write(f"{'=' * 80}\n\n") + if any( + rel_path == excluded_dir or excluded_dir in rel_path.parents + for excluded_dir in excluded_dirs + ): + continue - try: - with file_path.open(encoding="utf-8") as infile: - outfile.write(infile.read()) - except Exception as e: - outfile.write(f"[Error reading file: {e}]\n") - print(f"❌ Skipped {rel_path} due to read error.") # noqa: T201 + _write_file_to_bundle(outfile, file_path) # --- EXECUTION --- if __name__ == "__main__": + args = parse_args() + backend_translation_selector = args.backend_translations.strip().lower() + + if backend_translation_selector != "all" and len(backend_translation_selector) != 2: + msg = ( + "--backend-translations must be 'all' or a specific two-letter " + "language code such as 'en', 'fr', or 'de'." + ) + raise SystemExit(msg) + print(f"🚀 Starting Knowledge Base Bundler in Workspace: {WORKSPACE_ROOT}\n") # noqa: T201 - # 1. Bundle the Core Integration + # 1. Bundle the default backend integration surface bundle_for_gem( - source_folder_name="choreops", + source_folder_name="choreops/custom_components/choreops", output_filename="gem_choreops_backend.txt", allowed_extensions=(".py", ".yaml", ".json"), + excluded_dirs=( + BACKEND_MANAGER_DIR, + BACKEND_VENDORED_DASHBOARD_DIR, + *BACKEND_TRANSLATION_DIRS, + ), ) + # 1a. Bundle backend managers separately + bundle_for_gem( + source_folder_name="choreops/custom_components/choreops/managers", + output_filename="gem_choreops_backend_managers.txt", + allowed_extensions=(".py", ".yaml", ".json"), + ) + + # 1b. Bundle backend translations separately + bundle_backend_translations(backend_translation_selector) + + # 1c. Bundle backend tests separately when requested + if args.include_tests: + bundle_for_gem( + source_folder_name="choreops/tests", + output_filename="gem_choreops_backend_tests.txt", + allowed_extensions=(".py", ".yaml", ".json"), + excluded_dirs=(BACKEND_TEST_DIR / "__pycache__",), + ) + # 2. Bundle the Dashboards bundle_for_gem( source_folder_name="choreops-dashboards", diff --git a/utils/generate_dense_test_scenarios.py b/utils/generate_dense_test_scenarios.py new file mode 100644 index 0000000..65f0cd7 --- /dev/null +++ b/utils/generate_dense_test_scenarios.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Generate dense ChoreOps test scenarios for stress and manual validation.""" + +from __future__ import annotations + +import argparse +from pathlib import Path +from typing import Final + +import yaml + +OUTPUT_DIR: Final = Path("/workspaces/choreops/tests/scenarios") +DEFAULT_COUNTS: Final[tuple[int, ...]] = (40, 50, 60, 70, 80, 90, 100, 120) + +ASSIGNEES: Final[tuple[dict[str, str], ...]] = ( + {"name": "Zoë", "ha_user": "assignee1", "dashboard_language": "en"}, + {"name": "Max!", "ha_user": "assignee2", "dashboard_language": "en"}, + {"name": "Lila", "ha_user": "assignee3", "dashboard_language": "en"}, +) + +APPROVERS: Final[tuple[dict[str, str], ...]] = ( + {"name": "Môm Astrid Stårblüm", "ha_user": "approver1"}, + {"name": "Dad Leo Stårblüm", "ha_user": "approver2"}, +) + +LABELS: Final[tuple[str, ...]] = ( + "Kitchen", + "Bedroom", + "Bathroom", + "Laundry", + "School", + "Outdoor", +) + +ICONS: Final[tuple[str, ...]] = ( + "mdi:broom", + "mdi:spray-bottle", + "mdi:trash-can-outline", + "mdi:book-open-page-variant", + "mdi:washing-machine", + "mdi:shovel", +) + + +class _ScenarioDumper(yaml.SafeDumper): + """YAML dumper that keeps generated scenarios fully expanded.""" + + def ignore_aliases(self, data: object) -> bool: + """Disable anchors/aliases for readability of generated fixtures.""" + return True + + +def _build_chore_name(assignee_name: str, chore_index: int) -> str: + """Build a stable unique chore name for generated scenarios.""" + return f"{assignee_name} Dense Chore {chore_index:03d}" + + +def build_dense_scenario(chores_per_assignee: int) -> dict[str, object]: + """Build a dense scenario with the standard Stårblüm family. + + Each assignee receives the same number of independent chores so the + resulting dashboard helper size is directly comparable per user. + """ + assignee_names = [assignee["name"] for assignee in ASSIGNEES] + chores: list[dict[str, object]] = [] + + for assignee_index, assignee in enumerate(ASSIGNEES): + assignee_name = assignee["name"] + for chore_index in range(1, chores_per_assignee + 1): + label_index = (chore_index - 1) % len(LABELS) + secondary_label_index = (label_index + assignee_index + 1) % len(LABELS) + icon = ICONS[(chore_index - 1) % len(ICONS)] + points = float(5 + ((chore_index - 1) % 6) * 5) + + chores.append( + { + "name": _build_chore_name(assignee_name, chore_index), + "assigned_to": [assignee_name], + "points": points, + "icon": icon, + "description": ( + f"Generated dense dashboard chore {chore_index} for " + f"{assignee_name}" + ), + "completion_criteria": "independent", + "recurring_frequency": "daily", + "auto_approve": True, + "show_on_calendar": False, + "labels": [ + LABELS[label_index], + LABELS[secondary_label_index], + ], + } + ) + + return { + "system": { + "points_label": "Star Points", + "points_icon": "mdi:star", + }, + "assignees": list(ASSIGNEES), + "approvers": [ + { + **approver, + "assignees": assignee_names, + "mobile_notify_service": "", + } + for approver in APPROVERS + ], + "chores": chores, + } + + +def write_scenario_file(output_dir: Path, chores_per_assignee: int) -> Path: + """Generate and write one dense scenario YAML file.""" + scenario = build_dense_scenario(chores_per_assignee) + output_path = output_dir / f"scenario_density_starblum_{chores_per_assignee}.yaml" + yaml_text = yaml.dump( + scenario, + allow_unicode=True, + default_flow_style=False, + sort_keys=False, + width=88, + Dumper=_ScenarioDumper, + ) + output_path.write_text(yaml_text, encoding="utf-8") + return output_path + + +def parse_args() -> argparse.Namespace: + """Parse CLI arguments.""" + parser = argparse.ArgumentParser( + description="Generate dense ChoreOps scenarios for stress testing" + ) + parser.add_argument( + "counts", + nargs="*", + type=int, + default=list(DEFAULT_COUNTS), + help="Chores per assignee to generate. Default: 40 50 60 70 80 90 100 120", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=OUTPUT_DIR, + help=f"Directory to write scenarios into. Default: {OUTPUT_DIR}", + ) + return parser.parse_args() + + +def main() -> None: + """Generate the requested dense scenario files.""" + args = parse_args() + output_dir: Path = args.output_dir + output_dir.mkdir(parents=True, exist_ok=True) + + for count in args.counts: + if count <= 0: + raise ValueError(f"Count must be positive: {count}") + output_path = write_scenario_file(output_dir, count) + print(f"Wrote {output_path}") + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock index 4a4b60d..d8035fa 100644 --- a/uv.lock +++ b/uv.lock @@ -4,5 +4,5 @@ requires-python = ">=3.13" [[package]] name = "choreops" -version = "1.0.7" +version = "1.0.8" source = { editable = "." }