Files
databisnisid/distribute_loop.py

135 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Jadwal distribusi: jalankan distribute.py pada hari `DISTRIBUTE_WEEKDAY`
(satu atau CSV, mis. `wed,sat`) jam `DISTRIBUTE_HOUR`, plus satu one-off
`DISTRIBUTE_FIRST_RUN` (YYYY-MM-DD, default baked `2026-08-17` via config).
Contoh produksi: `DISTRIBUTE_FIRST_RUN=2026-08-17` (Senin) lalu
`DISTRIBUTE_WEEKDAY=wed,sat` → putaran pertama Senin 17 Agt 10:00, berikutnya
tiap Rabu & Sabtu 10:00.
Menunggu hingga batas terjadwal berikutnya (waktu lokal via `TZ`) lalu memanggil
`distribute.main()`. Loop tak pernah keluar — service dijaga hidup oleh
restart_policy swarm. Gagal distribusi dicatat lalu lanjut ke jadwal berikutnya
(⊥ membunuh loop). Tanpa `VEX_BP_PRIVATE_KEY` run nyata raise RuntimeError yang
jelas — ditangkap, dicetak, dicoba lagi di jadwal berikutnya.
Berbeda dengan scan: ⊥ ada "distribusi-awal" saat start (SCAN_RUN_ON_START) —
membayar di container start berisiko memakai snapshot basi; distribusi hanya
berjalan pada jadwal.
Scheduler helper (`next_boundary`, `_wait_db`) sengaja diduplikasi dari
`scan_loop.py` (bukan di-import) karena kedua image Docker terpisah:
`Dockerfile.dist` ⊥ membawa `scan_loop.py`/`get_voters.py`.
"""
import time
from datetime import datetime, timedelta
import config
import db
import distribute
WEEKDAYS = {
'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3,
'fri': 4, 'sat': 5, 'sun': 6,
}
def parse_weekday(value):
"""Nama hari (`sat`) atau angka 0-6 → weekday Python (Senin=0)."""
v = str(value).strip().lower()
if v in WEEKDAYS:
return WEEKDAYS[v]
if v.isdigit() and 0 <= int(v) <= 6:
return int(v)
raise ValueError(f'DISTRIBUTE_WEEKDAY tak valid: {value!r}')
def parse_weekdays(value):
"""Satu hari (`sat`) atau CSV beberapa hari (`wed,sat`) → daftar weekday
(int, Senin=0) unik terurut. V44: jadwal bisa multi-hari per minggu."""
if not value:
raise ValueError('DISTRIBUTE_WEEKDAY kosong')
days = sorted({parse_weekday(d) for d in str(value).split(',') if d.strip()})
if not days:
raise ValueError(f'DISTRIBUTE_WEEKDAY tak valid: {value!r}')
return days
def parse_first_run(value):
"""`YYYY-MM-DD` → date; kosong → None (⊥ one-off)."""
if not value:
return None
return datetime.strptime(str(value).strip(), '%Y-%m-%d').date()
def next_boundary(now, hour, weekdays, first_run=None):
"""datetime berikutnya utk distribusi.
Mingguan: tiap hari di `weekdays` (list int) jam `hour`. Bila `first_run`
diset, distribusi ⊥ jalan sebelum tanggal itu — hari-hari mingguan yang
jatuh sebelum launch dilewati; event = {one-off `first_run` (jika masih
depan)} ∪ {mingguan ≥ tanggal `first_run`}; pilih yang terdekat > `now`."""
if isinstance(weekdays, int):
weekdays = [weekdays]
start = None
if first_run is not None:
start = datetime(first_run.year, first_run.month, first_run.day,
hour=hour)
candidates = []
if start is not None and start >= now:
candidates.append(start)
for weekday in weekdays:
days_ahead = (weekday - now.weekday()) % 7
weekly = (now.replace(hour=hour, minute=0, second=0, microsecond=0)
+ timedelta(days=days_ahead))
if weekly <= now:
weekly += timedelta(days=7)
if start is None or weekly >= start:
candidates.append(weekly)
return min(candidates)
def _wait_db(timeout=120):
"""Tunggu basis data siap (mariadb baru boot) sebelum loop."""
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_distribute(label):
print(f'[{label}] memulai distribusi...', flush=True)
try:
distribute.main()
except Exception as exc:
print(f'[Error] Distribusi gagal: {exc}', flush=True)
def main():
hour = config.DISTRIBUTE_HOUR
if not 0 <= hour <= 23:
raise ValueError(f'DISTRIBUTE_HOUR tak valid: {hour!r}')
weekdays = parse_weekdays(config.DISTRIBUTE_WEEKDAY)
first_run = parse_first_run(config.DISTRIBUTE_FIRST_RUN)
hari = ' & '.join(list(WEEKDAYS)[d] for d in weekdays)
jadwal = (f'jam {hour} tiap hari {hari} (waktu lokal)'
+ (f', one-off {first_run.isoformat()}' if first_run else ''))
print(f'Jadwal distribusi: {jadwal}')
_wait_db()
while True:
now = datetime.now()
target = next_boundary(now, hour, weekdays, first_run)
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_distribute('distribusi terjadwal')
if __name__ == '__main__':
main()