New apps Added

This commit is contained in:
2026-07-08 10:43:39 -04:00
parent 3b1f4bbd75
commit fefc2c8b5c
1114 changed files with 406637 additions and 154 deletions
@@ -0,0 +1,577 @@
"""Constants for the Maintenance Supporter integration."""
from __future__ import annotations
import re
from enum import StrEnum
from homeassistant.const import Platform
DOMAIN = "maintenance_supporter"
def slugify_object_name(name: str) -> str:
"""Convert an object name to a safe slug for use in unique IDs.
Replaces any non-alphanumeric character with underscore, collapses
consecutive underscores, and strips leading/trailing underscores.
"""
return re.sub(r"_+", "_", re.sub(r"[^a-z0-9]", "_", name.lower())).strip("_")
PLATFORMS: list[Platform] = [
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.CALENDAR,
Platform.TODO,
]
# --- Unique IDs ---
GLOBAL_UNIQUE_ID = "maintenance_supporter_global"
# --- Defaults ---
DEFAULT_WARNING_DAYS = 7
DEFAULT_INTERVAL_DAYS = 30
DEFAULT_UPDATE_INTERVAL_MINUTES = 5
DEFAULT_MAX_HISTORY_ENTRIES = 500
DEFAULT_SNOOZE_DURATION_HOURS = 4
DEFAULT_MAX_NOTIFICATIONS_PER_DAY = 0 # 0 = unlimited
# --- Adaptive Scheduling Defaults ---
DEFAULT_ADAPTIVE_EWA_ALPHA = 0.3
DEFAULT_ADAPTIVE_MIN_INTERVAL = 7
DEFAULT_ADAPTIVE_MAX_INTERVAL = 365
DEFAULT_ADAPTIVE_MIN_COMPLETIONS = 3 # Before showing suggestions
DEFAULT_ADAPTIVE_WEIBULL_MIN = 5 # Before fitting Weibull
DEFAULT_ADAPTIVE_RELIABILITY_TARGET = 0.9 # 90% for Weibull
# --- Seasonal Scheduling Defaults ---
DEFAULT_SEASONAL_MIN_DATA = 6 # Min intervals across all months before seasonal adjustment
DEFAULT_SEASONAL_FACTOR_MIN = 0.3 # Floor: never less than 30% of base interval
DEFAULT_SEASONAL_FACTOR_MAX = 3.0 # Ceiling: never more than 300% of base interval
# --- Hemisphere-aware Season Mapping ---
NORTHERN_SEASONS: dict[str, list[int]] = {
"spring": [3, 4, 5],
"summer": [6, 7, 8],
"fall": [9, 10, 11],
"winter": [12, 1, 2],
}
SOUTHERN_SEASONS: dict[str, list[int]] = {
"spring": [9, 10, 11],
"summer": [12, 1, 2],
"fall": [3, 4, 5],
"winter": [6, 7, 8],
}
# --- Config Keys: Global ---
CONF_DEFAULT_WARNING_DAYS = "default_warning_days"
CONF_NOTIFICATIONS_ENABLED = "notifications_enabled"
CONF_NOTIFY_SERVICE = "notify_service"
CONF_PANEL_ENABLED = "panel_enabled"
# v2.10.4 (#69 follow-up): the sidebar panel is the integration's hub — the
# auto-dashboard's "Open Maintenance panel" button, QR codes, and notifications
# all link to /maintenance-supporter. With the panel off those links 404, so it
# now defaults ON. An explicit opt-out (panel_enabled: false in options) is
# still honoured. Read this default everywhere instead of inlining the literal.
DEFAULT_PANEL_ENABLED = True
CONF_PANEL_TITLE = "panel_title"
# --- Config Keys: Advanced Feature Visibility ---
CONF_ADVANCED_ADAPTIVE = "advanced_adaptive_visible"
CONF_ADVANCED_PREDICTIONS = "advanced_predictions_visible"
CONF_ADVANCED_SEASONAL = "advanced_seasonal_visible"
CONF_ADVANCED_ENVIRONMENTAL = "advanced_environmental_visible"
CONF_ADVANCED_BUDGET = "advanced_budget_visible"
CONF_ADVANCED_GROUPS = "advanced_groups_visible"
CONF_ADVANCED_CHECKLISTS = "advanced_checklists_visible"
CONF_ADVANCED_SCHEDULE_TIME = "advanced_schedule_time_visible"
# v1.3.0: gates the per-task on_complete_action + quick_complete_defaults
# UI sections in the task dialog, plus the quick_complete chip in the
# Print-QR generator. Default OFF — beginners aren't overwhelmed; data
# still persists if a user toggles the flag off again.
CONF_ADVANCED_COMPLETION_ACTIONS = "advanced_completion_actions_visible"
# Panel-access overrides: HA user IDs who get the full admin panel despite
# not being HA admins. Empty list means only admins see the full panel.
# The allowlist grants create/edit/delete ONLY when CONF_OPERATOR_WRITE_ENABLED
# is also on; with it off (the default) listed users get read-only access.
CONF_ADMIN_PANEL_USER_IDS = "admin_panel_user_ids"
# v2.8.4: master switch for operator write delegation. Default OFF → only HA
# admins may create/edit/delete and the panel-access allowlist is read-only.
# When True, allowlisted non-admins additionally gain full content CRUD. Only
# admins can flip it (panel Settings tab / config-flow Panel Access step, both
# admin-gated), preserving the escalation boundary in helpers/permissions.py.
CONF_OPERATOR_WRITE_ENABLED = "operator_write_enabled"
# (#67): objects-table column configuration for the panel All-Objects view
# (table mode). Global setting holding the ordered list of object columns to
# show. Selectable from KNOWN_OBJECT_TABLE_COLUMNS only — known object fields,
# never arbitrary state attributes.
CONF_OBJECTS_TABLE_COLUMNS = "objects_table_columns"
# v2.21: template-gallery curation — ids of built-in templates the admin has
# hidden from the "From template" pickers (panel gallery + config flow). The
# templates stay functional (direct WS calls still work); they are only
# removed from the pickers so a growing catalog never clutters the UI.
CONF_DISABLED_TEMPLATE_IDS = "disabled_template_ids"
# Every column key the objects table can render. The Settings UI offers exactly
# these; the WS update handler drops anything outside this set.
KNOWN_OBJECT_TABLE_COLUMNS = [
"name",
"manufacturer",
"model",
"serial_number",
"installation_date",
"warranty_expiry",
"area_id",
"documentation_url",
"notes",
"task_count",
"actions",
]
# Default column set + order when the user hasn't customised it. Mirrors the
# locked design: Name · Manufacturer · Model · Serial · Installed · Warranty ·
# Area · Tasks · link (documentation_url + notes are opt-in extras).
DEFAULT_OBJECTS_TABLE_COLUMNS = [
"name",
"manufacturer",
"model",
"serial_number",
"installation_date",
"warranty_expiry",
"area_id",
"task_count",
"actions",
]
# --- Vacation mode (v1.2.0) ---
# When active, suppresses notifications for non-exempt tasks across the
# vacation window plus an N-day buffer (so a task that comes due the day
# of return doesn't immediately fire).
CONF_VACATION_ENABLED = "vacation_enabled"
CONF_VACATION_START = "vacation_start" # ISO date "YYYY-MM-DD"
CONF_VACATION_END = "vacation_end" # ISO date
CONF_VACATION_BUFFER_DAYS = "vacation_buffer_days"
CONF_VACATION_EXEMPT_TASK_IDS = "vacation_exempt_task_ids"
DEFAULT_VACATION_BUFFER_DAYS = 3
# --- Config Keys: Notification Per-Status ---
CONF_NOTIFY_DUE_SOON_ENABLED = "notify_due_soon_enabled"
CONF_NOTIFY_DUE_SOON_INTERVAL = "notify_due_soon_interval_hours"
CONF_NOTIFY_OVERDUE_ENABLED = "notify_overdue_enabled"
CONF_NOTIFY_OVERDUE_INTERVAL = "notify_overdue_interval_hours"
CONF_NOTIFY_TRIGGERED_ENABLED = "notify_triggered_enabled"
CONF_NOTIFY_TRIGGERED_INTERVAL = "notify_triggered_interval_hours"
# --- Config Keys: Notification Quiet Hours ---
CONF_QUIET_HOURS_ENABLED = "quiet_hours_enabled"
CONF_QUIET_HOURS_START = "quiet_hours_start"
CONF_QUIET_HOURS_END = "quiet_hours_end"
# Shared HH:MM or HH:MM:SS time pattern (023 h, 059 m/s) — the single source
# for validating quiet-hours times in BOTH the options flow and the WS handler.
TIME_HHMMSS_PATTERN = re.compile(r"^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$")
# --- Config Keys: Notification Limits ---
CONF_MAX_NOTIFICATIONS_PER_DAY = "max_notifications_per_day"
# --- Config Keys: Notification Bundling ---
CONF_NOTIFICATION_BUNDLING_ENABLED = "notification_bundling_enabled"
CONF_NOTIFICATION_BUNDLE_THRESHOLD = "notification_bundle_threshold"
# --- Config Keys: Notification Title (v1.4.0 #44) ---
# How to format the notification title.
# "default" — generic per-status title (e.g. "Maintenance overdue!"). Backwards-compatible.
# "object_name" — use the object name as the title (helps when phone stacks notifications).
# "task_name" — use the task name as the title.
CONF_NOTIFICATION_TITLE_STYLE = "notification_title_style"
NOTIFICATION_TITLE_STYLES = ("default", "object_name", "task_name")
# --- Config Keys: Notification Actions ---
CONF_ACTION_COMPLETE_ENABLED = "action_complete_enabled"
CONF_ACTION_SKIP_ENABLED = "action_skip_enabled"
CONF_ACTION_SNOOZE_ENABLED = "action_snooze_enabled"
CONF_SNOOZE_DURATION_HOURS = "snooze_duration_hours"
# v2.15.0: opt-in weekly digest — a single Monday-morning summary notification.
CONF_WEEKLY_DIGEST_ENABLED = "weekly_digest_enabled"
CONF_WARRANTY_REMINDER_ENABLED = "warranty_reminder_enabled"
CONF_WARRANTY_REMINDER_DAYS = "warranty_reminder_days"
DEFAULT_WARRANTY_REMINDER_DAYS = 30
# Multiple lead-time reminders: list of days-before-due at which an extra
# reminder fires (e.g. [14, 3, 0]). Empty = feature off (single warning_days
# threshold behaviour unchanged). Checked by a daily tick, not the
# status-change path.
CONF_REMINDER_LEAD_DAYS = "reminder_lead_days"
MAX_REMINDER_LEADS = 10
# --- Panel ---
PANEL_NAME = "maintenance-supporter"
PANEL_TITLE = "Maintenance"
PANEL_ICON = "mdi:wrench-clock"
PANEL_URL = "/maintenance_supporter_panel"
CARD_URL = "/maintenance_supporter_card"
# v2.3.4 (issue #52): strategy is now a multi-file split bundle. The entry
# URL has to live UNDER a directory prefix so the entry's relative
# ./chunks/X.js dynamic imports resolve to /<prefix>/chunks/X.js. Pre-2.3.4
# users had a flat URL — old dashboards still load fine because the strategy
# is identified by ``custom:maintenance-supporter`` (a logical name resolved
# via window.customStrategies), not by URL.
STRATEGY_DIR_URL = "/maintenance_supporter_strategy"
STRATEGY_URL = f"{STRATEGY_DIR_URL}/maintenance-dashboard-strategy.js"
STRATEGY_CHUNKS_URL = f"{STRATEGY_DIR_URL}/chunks"
# v2.8.1 (strategy timeout fix): a tiny zero-import shim is what we register as
# the frontend extra-module-url. It defines the dashboard-strategy element
# synchronously (winning HA's 5 s whenDefined race under heavy plugin load) and
# lazy-imports the full STRATEGY_URL bundle on first use. The bundle's own
# relative ./chunks/* imports still resolve because it is served under
# STRATEGY_DIR_URL. The shim has no relative imports, so a flat URL is fine.
STRATEGY_SHIM_URL = "/maintenance_supporter_strategy_shim.js"
CALENDAR_CARD_URL = "/maintenance_supporter_calendar_card"
# Runtime-loaded UI translations (frontend/locales/<lang>.json), served as a
# directory so the panel/card fetch the active language on demand. Mirrors
# LOCALES_BASE in frontend-src/styles.ts: only EN is bundled into the JS (as the
# fallback); the other languages live here, so a translation edit needs no
# frontend bundle rebuild.
LOCALES_URL = "/maintenance_supporter_locales"
# v2.21: vendored third-party assets (pdf.js for the work sheet's inline
# manual excerpt) — served statically, loaded ONLY by the work-sheet tab so
# the panel bundle stays lean.
VENDOR_URL = "/maintenance_supporter_vendor"
# --- Config Keys: Object ---
CONF_OBJECT = "object"
CONF_OBJECT_NAME = "name"
CONF_OBJECT_AREA = "area_id"
CONF_OBJECT_MANUFACTURER = "manufacturer"
CONF_OBJECT_MODEL = "model"
CONF_OBJECT_SERIAL_NUMBER = "serial_number"
CONF_OBJECT_INSTALLATION_DATE = "installation_date"
# (#67): per-object warranty expiry date (ISO YYYY-MM-DD) for asset tracking
CONF_OBJECT_WARRANTY_EXPIRY = "warranty_expiry"
# v1.4.0 (#43): per-object link to PDF manual / vendor page
CONF_OBJECT_DOCUMENTATION_URL = "documentation_url"
# v1.4.10 (#46): per-object free-form notes (part numbers, procedures, etc.)
CONF_OBJECT_NOTES = "notes"
# --- Config Keys: Task ---
CONF_TASKS = "tasks"
CONF_TASK_NAME = "name"
CONF_TASK_TYPE = "type"
CONF_TASK_ENABLED = "enabled"
CONF_TASK_INTERVAL_DAYS = "interval_days"
CONF_TASK_WARNING_DAYS = "warning_days"
CONF_TASK_LAST_PERFORMED = "last_performed"
CONF_TASK_SCHEDULE_TYPE = "schedule_type"
CONF_TASK_INTERVAL_UNIT = "interval_unit"
CONF_TASK_DUE_DATE = "due_date"
CONF_TASK_NOTES = "notes"
CONF_TASK_DOCUMENTATION_URL = "documentation_url"
CONF_TASK_ICON = "custom_icon"
CONF_TASK_NFC_TAG = "nfc_tag_id"
# v2.20 (#83): display unit for `reading`-type tasks ("kWh", "m³", ...).
CONF_TASK_READING_UNIT = "reading_unit"
MAX_READING_UNIT_LENGTH = 32
CONF_TASK_INTERVAL_ANCHOR = "interval_anchor"
CONF_TASK_SCHEDULE_TIME = "schedule_time"
CONF_TASK_PRIORITY = "priority"
# Config-flow-only field: comma-separated labels text, parsed into the
# persisted ``labels`` list by the task-base handler.
CONF_TASK_LABELS_TEXT = "labels_text"
CONF_TASK_ASSIGNEE_POOL = "assignee_pool"
CONF_TASK_ROTATION_STRATEGY = "rotation_strategy"
# Rotation strategies for shared tasks: advance the "currently responsible"
# pointer on each completion. None/"" = no rotation (single-assignee behaviour).
ROTATION_STRATEGIES = ("round_robin", "least_completed", "random")
MAX_ASSIGNEE_POOL = 25
# --- Config Keys: User Assignment ---
CONF_RESPONSIBLE_USER_ID = "responsible_user_id"
# --- Config Keys: Trigger ---
CONF_TRIGGER_CONFIG = "trigger_config"
CONF_TRIGGER_TYPE = "trigger_type"
CONF_TRIGGER_ENTITY = "trigger_entity"
CONF_TRIGGER_ENTITY_IDS = "entity_ids"
CONF_TRIGGER_ENTITY_LOGIC = "entity_logic"
CONF_TRIGGER_STATE = "_trigger_state"
CONF_COMPOUND_LOGIC = "compound_logic"
CONF_COMPOUND_CONDITIONS = "conditions"
DEFAULT_ENTITY_LOGIC = "any"
CONF_TRIGGER_ATTRIBUTE = "trigger_attribute"
CONF_TRIGGER_ABOVE = "trigger_above"
CONF_TRIGGER_BELOW = "trigger_below"
CONF_TRIGGER_FOR_MINUTES = "trigger_for_minutes"
CONF_TRIGGER_TARGET_VALUE = "trigger_target_value"
CONF_TRIGGER_DELTA_MODE = "trigger_delta_mode"
CONF_TRIGGER_BASELINE_VALUE = "trigger_baseline_value"
CONF_TRIGGER_FROM_STATE = "trigger_from_state"
CONF_TRIGGER_TO_STATE = "trigger_to_state"
CONF_TRIGGER_TARGET_CHANGES = "trigger_target_changes"
CONF_TRIGGER_RUNTIME_HOURS = "trigger_runtime_hours"
CONF_TRIGGER_ON_STATES = "trigger_on_states"
# --- Config Keys: Adaptive Scheduling ---
CONF_ADAPTIVE_CONFIG = "adaptive_config"
CONF_ADAPTIVE_ENABLED = "adaptive_enabled"
CONF_ADAPTIVE_EWA_ALPHA = "ewa_alpha"
CONF_ADAPTIVE_MIN_INTERVAL = "min_interval_days"
CONF_ADAPTIVE_MAX_INTERVAL = "max_interval_days"
# --- Config Keys: Seasonal Scheduling ---
CONF_SEASONAL_ENABLED = "seasonal_enabled"
CONF_SEASONAL_OVERRIDES = "seasonal_overrides"
CONF_SEASONAL_HEMISPHERE = "seasonal_hemisphere"
# --- Sensor Prediction Defaults (Phase 3) ---
DEFAULT_DEGRADATION_LOOKBACK_DAYS = 30 # Days of recorder data for slope computation
DEFAULT_DEGRADATION_MIN_POINTS = 10 # Min hourly data points to compute regression
DEFAULT_DEGRADATION_SIGNIFICANCE = 0.05 # Min |slope|/mean ratio for rising/falling
# --- Environmental Adjustment Defaults (Phase 3) ---
DEFAULT_ENVIRONMENTAL_LOOKBACK_DAYS = 90 # Days of env data for correlation
DEFAULT_ENVIRONMENTAL_CORRELATION_MIN = 0.3 # Min |r| to apply adjustment
DEFAULT_ENVIRONMENTAL_FACTOR_MIN = 0.5 # Floor for env adjustment
DEFAULT_ENVIRONMENTAL_FACTOR_MAX = 2.0 # Ceiling for env adjustment
DEFAULT_ENVIRONMENTAL_MIN_COMPLETIONS = 3 # Min completions with env data
# --- Config Keys: Sensor Prediction (Phase 3) ---
CONF_SENSOR_PREDICTION_ENABLED = "sensor_prediction_enabled"
CONF_ENVIRONMENTAL_ENTITY = "environmental_entity"
CONF_ENVIRONMENTAL_ATTRIBUTE = "environmental_attribute"
# --- Budget ---
CONF_BUDGET_MONTHLY = "budget_monthly"
CONF_BUDGET_YEARLY = "budget_yearly"
CONF_BUDGET_ALERTS_ENABLED = "budget_alerts_enabled"
CONF_BUDGET_ALERT_THRESHOLD = "budget_alert_threshold"
CONF_BUDGET_CURRENCY = "budget_currency"
BUDGET_CURRENCIES: dict[str, str] = {
"EUR": "",
"USD": "$",
"GBP": "£",
"JPY": "¥",
"CHF": "Fr",
"CAD": "C$",
"AUD": "A$",
"CNY": "¥",
"INR": "",
"BRL": "R$",
"CZK": "",
"PLN": "",
"RUB": "",
"SEK": "kr",
"NOK": "kr",
"DKK": "kr",
"UAH": "",
}
# Default currency when the user hasn't chosen one. Its symbol is derived from
# BUDGET_CURRENCIES so the two never disagree.
DEFAULT_BUDGET_CURRENCY = "EUR"
# --- Archive & Retention (v2.10.0) ---
# Global int settings (panel-managed via WS, like objects_table_columns):
# archive_oneoff_days — auto-archive a completed one-off this many days
# after completion. 0 = disabled (manual only).
# delete_archived_oneoff_days — auto-delete an AUTO-archived one-off this many
# days after it was archived. 0 = never. Manual
# archives are NEVER auto-deleted (delete stays explicit).
CONF_ARCHIVE_ONEOFF_DAYS = "archive_oneoff_days"
CONF_DELETE_ARCHIVED_ONEOFF_DAYS = "delete_archived_oneoff_days"
DEFAULT_ARCHIVE_ONEOFF_DAYS = 14
DEFAULT_DELETE_ARCHIVED_ONEOFF_DAYS = 0 # 0 = never
# Per-task/object `archived_reason` values (stored alongside `archived_at`).
# Drives two policies: only AUTO is eligible for auto-delete; only OBJECT is
# undone by an object unarchive cascade (MANUAL/AUTO survive it).
ARCHIVE_REASON_MANUAL = "manual"
ARCHIVE_REASON_AUTO = "auto"
ARCHIVE_REASON_OBJECT = "object"
# --- Groups ---
CONF_GROUPS = "groups"
# --- Checklist ---
CONF_CHECKLIST = "checklist"
# --- History ---
CONF_HISTORY = "history"
# --- Runtime keys (not persisted) ---
RUNTIME_TRIGGER_ACTIVE = "_trigger_active"
RUNTIME_TRIGGER_CURRENT_VALUE = "_trigger_current_value"
RUNTIME_TRIGGER_CHANGE_COUNT = "_trigger_change_count"
RUNTIME_TRIGGER_CURRENT_DELTA = "_trigger_current_delta"
# --- Event Types ---
EVENT_TRIGGER_ACTIVATED = f"{DOMAIN}_trigger_activated"
EVENT_TRIGGER_DEACTIVATED = f"{DOMAIN}_trigger_deactivated"
# v1.3.0: completion lifecycle events. Fired by coordinator after the
# state mutation has persisted. Power users wire HA automations on these;
# the integration's own action_listener also subscribes to the COMPLETED
# event to dispatch the per-task on_complete_action service-call.
EVENT_TASK_COMPLETED = f"{DOMAIN}_task_completed"
EVENT_TASK_SKIPPED = f"{DOMAIN}_task_skipped"
EVENT_TASK_RESET = f"{DOMAIN}_task_reset"
# --- Service Names ---
SERVICE_COMPLETE = "complete"
SERVICE_RESET = "reset"
SERVICE_SKIP = "skip"
SERVICE_EXPORT = "export_data"
SERVICE_ADD_OBJECT = "add_object"
SERVICE_ADD_TASK = "add_task"
SERVICE_UPDATE_TASK = "update_task"
SERVICE_DELETE_TASK = "delete_task"
SERVICE_LIST_TASKS = "list_tasks"
class MaintenanceStatus(StrEnum):
"""Status of a maintenance task."""
OK = "ok"
DUE_SOON = "due_soon"
OVERDUE = "overdue"
TRIGGERED = "triggered"
# v2.10.0: highest-precedence status. An archived task/object is retired but
# retained — it reads `archived`, fires nothing, and counts for nothing
# except budget/cost history (which is read from completion history, not
# status, so it is archive-agnostic by design).
ARCHIVED = "archived"
# v2.20 (journey N3): seasonal pause. Tasks of a paused OBJECT read
# `paused`: schedules freeze and nothing fires — but unlike archived the
# object stays a first-class citizen in every view. Resuming re-anchors
# recurring tasks like an object unarchive does. Object-wide only (no
# per-task pause); injected by the coordinator, below ARCHIVED in
# precedence.
PAUSED = "paused"
class MaintenanceTypeEnum(StrEnum):
"""Type/category of maintenance."""
CLEANING = "cleaning"
INSPECTION = "inspection"
REPLACEMENT = "replacement"
CALIBRATION = "calibration"
SERVICE = "service"
# Record a value / take a reading (meter readings, level checks, spot
# measurements) — requested in Discussion #83, generalised beyond meters.
READING = "reading"
CUSTOM = "custom"
class ScheduleType(StrEnum):
"""How a task is scheduled."""
TIME_BASED = "time_based"
SENSOR_BASED = "sensor_based"
MANUAL = "manual"
ONE_TIME = "one_time"
# Schedule kinds expressed by the FLAT recurrence fields (interval/due_date),
# as opposed to the nested calendar kinds (weekdays/nth_weekday/day_of_month)
# or a sensor trigger. Used to decide flat-vs-calendar handling on task edit.
FLAT_SCHEDULE_TYPES = frozenset({ScheduleType.TIME_BASED, ScheduleType.ONE_TIME, ScheduleType.MANUAL})
class TriggerType(StrEnum):
"""Type of sensor trigger."""
THRESHOLD = "threshold"
COUNTER = "counter"
STATE_CHANGE = "state_change"
RUNTIME = "runtime"
COMPOUND = "compound"
class TaskPriority(StrEnum):
"""Priority level of a maintenance task."""
LOW = "low"
NORMAL = "normal"
HIGH = "high"
# The default when a task doesn't specify a priority.
DEFAULT_TASK_PRIORITY = TaskPriority.NORMAL
class HistoryEntryType(StrEnum):
"""Type of history entry."""
COMPLETED = "completed"
SKIPPED = "skipped"
MISSED = "missed"
RESET = "reset"
TRIGGERED = "triggered"
TRIGGER_REMOVED = "trigger_removed"
TRIGGER_REPLACED = "trigger_replaced"
class MaintenanceFeedback(StrEnum):
"""Feedback from user about whether maintenance was needed."""
NEEDED = "needed"
NOT_NEEDED = "not_needed"
NOT_SURE = "not_sure"
class TriggerEntityState(StrEnum):
"""State of a trigger entity from the integration's perspective."""
AVAILABLE = "available"
UNAVAILABLE = "unavailable" # Entity exists but state is unavailable/unknown
MISSING = "missing" # Entity does not exist in state machine
STARTUP = "startup" # Grace period after HA start
# --- Dispatcher Signals ---
SIGNAL_TASK_RESET = f"{DOMAIN}_task_reset_{{entry_id}}_{{task_id}}"
SIGNAL_NEW_OBJECT_ENTRY = f"{DOMAIN}_new_object_entry"
SIGNAL_OBJECT_ENTRY_REMOVED = f"{DOMAIN}_object_entry_removed"
# Fired by the DocumentStore after any metadata/blob change so the storage
# sensor (and any other listener) can refresh without polling.
SIGNAL_DOCUMENTS_UPDATED = f"{DOMAIN}_documents_updated"
# --- Trigger Completion Cooldown ---
TRIGGER_COMPLETION_COOLDOWN_SECONDS = 600 # 10 minutes
# Household double-complete window: two people tapping Complete on the same
# task within this many seconds count as ONE real-world action (journey M1).
# Short on purpose — a deliberate complete → reset → complete-again correction
# flow must never be caught by it.
MANUAL_COMPLETION_DEDUP_SECONDS = 30
# --- Input Validation Limits ---
MAX_NAME_LENGTH = 200
MAX_TEXT_LENGTH = 2000 # notes, reason, feedback, description
MAX_URL_LENGTH = 2048
MAX_ICON_LENGTH = 100 # "mdi:icon-name"
MAX_META_LENGTH = 200 # manufacturer, model, user_id, area_id, etc.
MAX_PANEL_TITLE_LENGTH = 50 # sidebar panel title override
MAX_TYPE_LENGTH = 50 # task_type, schedule_type
MAX_CHECKLIST_ITEMS = 100
MAX_CHECKLIST_ITEM_LENGTH = 500
MAX_LABELS = 25 # cross-cutting tags per task
MAX_LABEL_LENGTH = 40 # single label/tag
MAX_GROUP_TASK_REFS = 200
MAX_ID_LENGTH = 64 # entry_id, task_id, group_id (uuid hex = 32)
MAX_DATE_LENGTH = 20 # ISO 8601 date strings (e.g. 2026-04-21)
MAX_ENTITY_ID_LENGTH = 255 # HA entity_id max
MAX_ENTITY_SLUG_LENGTH = 64 # task entity_slug
MAX_INTERVAL_DAYS = 3650 # 10 years — caps date arithmetic overflow
MAX_IMPORT_PAYLOAD_BYTES = 1_048_576 # 1 MB for csv_content / json_content
MAX_SCHEDULE_TIME_LENGTH = 5 # "HH:MM"
# --- Trigger Entity Availability ---
STARTUP_GRACE_PERIOD_SECONDS = 300 # 5 minutes
MISSING_ENTITY_THRESHOLD_REFRESHES = 6 # ~30 min at 5-min intervals