|
| 1 | +"""Instrument stall alert checker.""" |
| 2 | + |
| 3 | +import logging |
| 4 | +from datetime import datetime, timedelta |
| 5 | + |
| 6 | +import pytz |
| 7 | + |
| 8 | +from shared.db.models import KrakenStatus, RawFile |
| 9 | + |
| 10 | +from .base_alert import BaseAlert |
| 11 | +from .config import ( |
| 12 | + INSTRUMENT_STALL_INSTRUMENT_IDS, |
| 13 | + INSTRUMENT_STALL_THRESHOLD_HOURS, |
| 14 | + Cases, |
| 15 | +) |
| 16 | + |
| 17 | + |
| 18 | +class InstrumentStallAlert(BaseAlert): |
| 19 | + """Check if no new file has been acquired for a configured time period.""" |
| 20 | + |
| 21 | + def __init__(self): |
| 22 | + """Initialize with empty alerted file names tracker.""" |
| 23 | + self._alerted_file_names: dict[str, str] = {} |
| 24 | + |
| 25 | + @property |
| 26 | + def name(self) -> str: |
| 27 | + """Return the case name for this alert type.""" |
| 28 | + return Cases.INSTRUMENT_STALL |
| 29 | + |
| 30 | + def _get_issues( |
| 31 | + self, status_objects: list[KrakenStatus] |
| 32 | + ) -> list[tuple[str, tuple[str, datetime]]]: |
| 33 | + """Check for instruments with no recent file acquisition.""" |
| 34 | + del status_objects |
| 35 | + |
| 36 | + if not INSTRUMENT_STALL_INSTRUMENT_IDS: |
| 37 | + return [] |
| 38 | + |
| 39 | + now = datetime.now(pytz.UTC) |
| 40 | + threshold = now - timedelta(hours=INSTRUMENT_STALL_THRESHOLD_HOURS) |
| 41 | + |
| 42 | + issues = [] |
| 43 | + for instrument_id in INSTRUMENT_STALL_INSTRUMENT_IDS: |
| 44 | + latest = ( |
| 45 | + RawFile.objects.filter(instrument_id=instrument_id) |
| 46 | + .order_by("-created_at") |
| 47 | + .only("original_name", "created_at") |
| 48 | + .first() |
| 49 | + ) |
| 50 | + |
| 51 | + if not latest: |
| 52 | + logging.debug(f"No raw files found for instrument {instrument_id}") |
| 53 | + continue |
| 54 | + |
| 55 | + created_at = latest.created_at |
| 56 | + if created_at.tzinfo is None: |
| 57 | + created_at = pytz.utc.localize(created_at) |
| 58 | + |
| 59 | + if created_at < threshold: |
| 60 | + if self._alerted_file_names.get(instrument_id) == latest.original_name: |
| 61 | + continue |
| 62 | + issues.append((instrument_id, (latest.original_name, created_at))) |
| 63 | + self._alerted_file_names[instrument_id] = latest.original_name |
| 64 | + else: |
| 65 | + self._alerted_file_names.pop(instrument_id, None) |
| 66 | + |
| 67 | + return issues |
| 68 | + |
| 69 | + def format_message(self, issues: list[tuple[str, tuple[str, datetime]]]) -> str: |
| 70 | + """Format no new file acquired message.""" |
| 71 | + now = datetime.now(pytz.UTC) |
| 72 | + lines = [] |
| 73 | + for instrument_id, (original_name, created_at) in issues: |
| 74 | + hours_ago = (now - created_at).total_seconds() / 3600 |
| 75 | + lines.append( |
| 76 | + f'- `{instrument_id}`: last file "{original_name}" ' |
| 77 | + f"acquired at {created_at.strftime('%Y-%m-%d %H:%M:%S')} UTC " |
| 78 | + f"({hours_ago:.1f} hours ago)" |
| 79 | + ) |
| 80 | + instruments_str = "\n".join(lines) |
| 81 | + return f"No new file acquired:\n{instruments_str}" |
0 commit comments