"""Jadwal skan berkala: jalankan get_voters.py pada jam SCAN_HOURS (waktu lokal). Menunggu hingga batas jam berikutnya dalam daftar `SCAN_HOURS` (default 0,8,16) lalu memanggil `get_voters.main()`. Loop tak pernah keluar — service dijaga hidup oleh restart_policy swarm. Gagal skan dicatat lalu lanjut ke batas berikutnya (tidak membunuh loop). `TZ` env menentukan jam lokal via `datetime.now()` (libc); image Dockerfile.scan sudah menyertakan tzdata. `SCAN_RUN_ON_START` (default 1): skan sekali saat container start setelah basis data siap — penting agar tabel voters langsung ada (dashboard ⊥ 500 sebelum batas jam pertama). """ import os import time from datetime import datetime, timedelta import db import get_voters def parse_hours(raw): """Daftar jam integer dari string CSV, diurutkan naik.""" hours = sorted({int(h) for h in raw.split(',') if h.strip()}) if not hours or not all(0 <= h <= 23 for h in hours): raise ValueError(f'SCAN_HOURS tak valid: {raw!r}') return hours def next_boundary(now, hours): """datetime berikutnya pada jam-jam `hours` yang lebih besar dari `now`.""" for h in hours: cand = now.replace(hour=h, minute=0, second=0, microsecond=0) if cand > now: return cand # semua jam hari ini sudah lewat → besok jam pertama nxt = now + timedelta(days=1) return nxt.replace(hour=hours[0], minute=0, second=0, microsecond=0) def _wait_db(timeout=120): """Tunggu basis data siap (mariadb baru boot) sebelum skan awal.""" start = time.monotonic() while time.monotonic() - start < timeout: try: db.queryone('SELECT 1') return except Exception: time.sleep(3) raise RuntimeError('basis data tak siap setelah menunggu') def _run_scan(label): print(f'[{label}] memulai skan...', flush=True) try: rc = get_voters.main() except Exception as exc: print(f'[Error] Skan gagal: {exc}', flush=True) return if rc != 0: print(f'[Peringatan] get_voters keluar dengan kode {rc}', flush=True) def main(): hours = parse_hours(os.getenv('SCAN_HOURS', '0,8,16')) run_on_start = os.getenv('SCAN_RUN_ON_START', '1').lower() not in ('0', 'false', 'no') print(f'Jadwal skan: jam {", ".join(str(h) for h in hours)} (waktu lokal); ' f'skan-awal: {"ya" if run_on_start else "tidak"}') if run_on_start: _wait_db() _run_scan('skan awal') while True: now = datetime.now() target = next_boundary(now, hours) wait = (target - now).total_seconds() print(f'{now.isoformat(timespec="seconds")} tidur {wait / 3600:.2f} jam ' f'sampai {target.isoformat(timespec="seconds")}', flush=True) time.sleep(max(wait, 0)) _run_scan('skan terjadwal') if __name__ == '__main__': main()