feat(notruf): Wiederauslöse-Sperre + grüner Hinweis; "Neues Gespräch" in die Kopfzeile
1) Notruf-Sperre (Cooldown) gegen Mehrfach-Alarme verwirrter/verängstigter
Senioren:
- Neue Einstellung emergency_cooldown_minutes (Default 5), zur Laufzeit im
Admin-Bereich änderbar (RUNTIME_SETTABLE), 0 = aus.
- record_manual_emergency unterdrückt innerhalb der Sperre jeden weiteren
Alarm (kein zweiter Versand, kein Log-Eintrag) und liefert status=cooldown
mit minutes_ago/remaining_seconds. Neuer Endpunkt GET /api/emergency/status
für die Anzeige nach einem Seiten-Neuladen. Store: last_manual_emergency_at.
- UI: Nach dem Alarm wird der Button grün, zeigt „✓" und pulsiert für die
Dauer der Sperre. Erneuter Druck öffnet einen grünen Hinweis-Dialog:
„Ich habe Deine Helfer vor <n> Minuten alarmiert. Wahrscheinlich ist Deine
Hilfe schon unterwegs." Der grüne Zustand übersteht ein Neuladen
(Status-Abruf beim Start).
2) „Neues Gespräch" als Symbol (Stift) zurück in die Kopfzeile — zwischen
Sprachauswahl und Menü; löst dieselbe Aktion aus wie der Menüeintrag.
Tests: neue test_emergency_cooldown.py (Status/Suppression/Endpoint); zwei
bestehende Notruf-Tests an die neue Interface-Methode bzw. die Sperre
angepasst. 250 Offline-Tests grün. Frontend via Headless-Chrome verifiziert
(Alarm->grün/pulsierend, Hinweis-Dialog, Reload-Persistenz, Header-Button).
Bump app.js v=50 -> v=51.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8a6f428c1b
commit
8c152ee77c
10 changed files with 290 additions and 22 deletions
|
|
@ -270,10 +270,60 @@ async def _post_webhook(url: str, payload: dict, cfg) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _cooldown_seconds(cfg) -> int:
|
||||
try:
|
||||
return max(0, int(getattr(cfg, "emergency_cooldown_minutes", 5)) * 60)
|
||||
except (TypeError, ValueError):
|
||||
return 300
|
||||
|
||||
|
||||
def emergency_cooldown_status(user, store, cfg=settings) -> dict:
|
||||
"""Aktueller Sperr-Status: ist nach dem letzten manuellen Notruf noch
|
||||
eine Wiederauslöse-Sperre aktiv, wie lange ist er her, wie viel bleibt.
|
||||
|
||||
Schützt verwirrte/verängstigte Senioren vor Mehrfach-Alarmen.
|
||||
"""
|
||||
total = _cooldown_seconds(cfg)
|
||||
last = store.last_manual_emergency_at(user.id) if total > 0 else None
|
||||
base = {"active": False, "cooldown_seconds": total, "remaining_seconds": 0, "minutes_ago": 0}
|
||||
if not last:
|
||||
return base
|
||||
try:
|
||||
last_dt = datetime.fromisoformat(last)
|
||||
except (ValueError, TypeError):
|
||||
return base
|
||||
elapsed = (datetime.now(timezone.utc) - last_dt).total_seconds()
|
||||
remaining = int(total - elapsed)
|
||||
return {
|
||||
"active": remaining > 0,
|
||||
"cooldown_seconds": total,
|
||||
"remaining_seconds": max(0, remaining),
|
||||
"minutes_ago": max(0, int(elapsed // 60)),
|
||||
}
|
||||
|
||||
|
||||
async def record_manual_emergency(user, store, language: str | None = None,
|
||||
geo=None, geo_error: str | None = None, device=None,
|
||||
cfg=settings) -> dict:
|
||||
"""Protokolliert einen bestätigten Notruf, benachrichtigt Kontakte und liefert den Hinweis."""
|
||||
"""Protokolliert einen bestätigten Notruf, benachrichtigt Kontakte und liefert den Hinweis.
|
||||
|
||||
Innerhalb der Wiederauslöse-Sperre (emergency_cooldown_minutes) wird KEIN
|
||||
neuer Alarm gesendet und kein Ereignis protokolliert — stattdessen liefert
|
||||
die Antwort status='cooldown' mit minutes_ago/remaining_seconds, damit das
|
||||
Frontend den beruhigenden Hinweis zeigen kann.
|
||||
"""
|
||||
cd = emergency_cooldown_status(user, store, cfg)
|
||||
if cd["active"]:
|
||||
metrics.inc("emergency_total", {"category": "manual", "source": "user_suppressed"})
|
||||
return {
|
||||
"category": "manual",
|
||||
"status": "cooldown",
|
||||
"cooldown_seconds": cd["cooldown_seconds"],
|
||||
"remaining_seconds": cd["remaining_seconds"],
|
||||
"minutes_ago": cd["minutes_ago"],
|
||||
"notice": notify_notice(language, True),
|
||||
}
|
||||
|
||||
# Bewusst minimaler Log-Eintrag — KEINE Adresse/Koordinaten (Admin-Log bleibt unkritisch).
|
||||
store.log_emergency(user.id, "manual", "Vom Nutzer ausgelöster Notruf")
|
||||
metrics.inc("emergency_total", {"category": "manual", "source": "user"})
|
||||
|
|
@ -302,6 +352,8 @@ async def record_manual_emergency(user, store, language: str | None = None,
|
|||
notified = email_sent or webhook_sent
|
||||
return {
|
||||
"category": "manual",
|
||||
"status": "alerted",
|
||||
"cooldown_seconds": cd["cooldown_seconds"],
|
||||
"notice": notify_notice(language, notified),
|
||||
"email_sent": email_sent,
|
||||
"webhook_sent": webhook_sent,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue