Files
patrickandClaude Sonnet 5.5 347f55cf7c
test / pytest (3.11) (push) Canceled after 0s
test / pytest (3.13) (push) Canceled after 0s
feat: Phase 6c baseline, ack, host alarms, outage notice, send limit, privacy mode, UI link
Includes atomic migrations, per-rule reminder semantics, first-run helper script
and the note on the undecided UI chat idea.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 03:06:23 +01:00

54 lines
2.1 KiB
Python

import asyncio
from dataclasses import replace
from typing import Callable, Iterable
from ..models import (KIND_FLAPPING, KIND_PROBLEM, KIND_RECOVERY, KIND_REMINDER, SUPPRESS_NOT_REPORTED,
SUPPRESS_RECOVERY_OFF, Event, Key)
REPORTED_KINDS = (KIND_PROBLEM, KIND_FLAPPING, KIND_REMINDER)
ReportCounts = Callable[[str, str], tuple[int, int]]
class ReportGate:
"""Merkt pro Service, ob das laufende Problem tatsächlich gesendet wurde (Grundlage für Recovery)."""
def __init__(self, counts: ReportCounts):
self._counts = counts
self._reported: dict[Key, bool] = {}
def reset(self, reported: Iterable[Key]) -> None:
self._reported = {key: True for key in reported}
async def apply(self, event: Event, recovery_enabled: bool) -> Event:
if event.kind != KIND_RECOVERY or event.suppressed:
return event
if not recovery_enabled:
return replace(event, suppressed=True, suppress_reason=SUPPRESS_RECOVERY_OFF)
if not await self._was_reported((event.host, event.service)):
return replace(event, suppressed=True, suppress_reason=SUPPRESS_NOT_REPORTED)
return event
async def track(self, event: Event, sent: bool) -> None:
key = (event.host, event.service)
if event.kind == KIND_RECOVERY:
self._reported.pop(key, None)
elif event.kind in REPORTED_KINDS:
before = await self._sent_before(key)
self._reported[key] = before or sent
async def _sent_before(self, key: Key) -> bool:
known = self._reported.get(key)
if known is not None:
return known
_total, sent = await asyncio.to_thread(self._counts, *key)
return sent > 0
async def _was_reported(self, key: Key) -> bool:
known = self._reported.get(key)
if known is not None:
return known
total, sent = await asyncio.to_thread(self._counts, *key)
# Ohne jede Historie (z. B. durch Retention gelöscht) wird die Recovery nicht verschluckt.
return total == 0 or sent > 0