feat(notifications): dedicate AI Failure Detection notification event (#1794)

Split Obico failure-detection dispatch out of the multiplexed
  on_printer_error event onto its own on_ai_failure_detection event so
  users can subscribe to AI alerts without also enabling HMS hardware-
  error pages, and so the discoverable label "AI Failure Detection" is
  what subscribes them rather than the unrelated "Printer Error" toggle.

  New column on notification_providers (default False, branched
  SQLite/Postgres migration), new notification_service.on_ai_failure_detection
  method, new ai_failure_detection template, obico_actions._notify swap.
  Frontend gets a summary badge, a toggle row with description, and ntfy
  priority surfacing. 14 new tests pin the routing + the regression guard
  ("Printer Error" alone must NOT receive AI notifications now). 11 locales
  covered.

  Existing providers keep working: HMS hardware errors continue to ride
  on_printer_error unchanged; users who want spaghetti alerts opt in via
  the new toggle.
This commit is contained in:
maziggy
2026-06-22 08:15:29 +02:00
parent 12b21ce0df
commit 4206d675eb
33 changed files with 579 additions and 23 deletions
@@ -38,6 +38,7 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal
const [onPrintProgress, setOnPrintProgress] = useState(provider?.on_print_progress ?? false);
const [onPrinterOffline, setOnPrinterOffline] = useState(provider?.on_printer_offline ?? false);
const [onPrinterError, setOnPrinterError] = useState(provider?.on_printer_error ?? false);
const [onAiFailureDetection, setOnAiFailureDetection] = useState(provider?.on_ai_failure_detection ?? false);
const [onFilamentLow, setOnFilamentLow] = useState(provider?.on_filament_low ?? false);
const [onMaintenanceDue, setOnMaintenanceDue] = useState(provider?.on_maintenance_due ?? false);
const [onStockReorderAlert, setOnStockReorderAlert] = useState(provider?.on_stock_reorder_alert ?? false);
@@ -167,6 +168,7 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal
on_print_progress: onPrintProgress,
on_printer_offline: onPrinterOffline,
on_printer_error: onPrinterError,
on_ai_failure_detection: onAiFailureDetection,
on_filament_low: onFilamentLow,
on_maintenance_due: onMaintenanceDue,
on_stock_reorder_alert: onStockReorderAlert,
@@ -547,6 +549,10 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal
<span className="text-sm text-white">{t('notifications.error')}</span>
<Toggle checked={onPrinterError} onChange={setOnPrinterError} />
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-white">{t('notifications.aiFailureDetection')}</span>
<Toggle checked={onAiFailureDetection} onChange={setOnAiFailureDetection} />
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-white">{t('notifications.lowFilament')}</span>
<Toggle checked={onFilamentLow} onChange={setOnFilamentLow} />
@@ -591,6 +597,7 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal
if (onFirstLayerComplete) enabledEvents.push({ key: 'on_first_layer_complete', label: t('notifications.firstLayerCompleteLabel') });
if (onPrinterOffline) enabledEvents.push({ key: 'on_printer_offline', label: t('notifications.offline') });
if (onPrinterError) enabledEvents.push({ key: 'on_printer_error', label: t('notifications.error') });
if (onAiFailureDetection) enabledEvents.push({ key: 'on_ai_failure_detection', label: t('notifications.aiFailureDetection') });
if (onFilamentLow) enabledEvents.push({ key: 'on_filament_low', label: t('notifications.lowFilament') });
if (onMaintenanceDue) enabledEvents.push({ key: 'on_maintenance_due', label: t('notifications.maintenance') });
if (onStockReorderAlert) enabledEvents.push({ key: 'on_stock_reorder_alert', label: t('notifications.stockReorderAlert') });