jadwal distribusi mingguan + banner BERITA jadwal berikutnya di dashboard (V37/V38, T43/T44)

This commit is contained in:
proitlab committed 2026-08-06 19:05:03 +07:00
1 parent b1e6e93796
commit 2f1f19c435
12 files changed
+344 -37

No files matched your search

+59 -20
View File
@@ -1,14 +1,18 @@
"""Jadwal distribusi harian: jalankan distribute.py pada jam DISTRIBUTE_HOUR.
"""Jadwal distribusi mingguan: jalankan distribute.py pada hari `DISTRIBUTE_WEEKDAY`
jam `DISTRIBUTE_HOUR`, plus satu one-off `DISTRIBUTE_FIRST_RUN` (YYYY-MM-DD).
Menunggu hingga batas `DISTRIBUTE_HOUR` 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 hari
berikutnya (⊥ membunuh loop). Tanpa `VEX_BP_PRIVATE_KEY` run nyata raise
RuntimeError yang jelas — ditangkap, dicetak, dicoba lagi besok.
Contoh produksi: `DISTRIBUTE_FIRST_RUN=2026-08-17` (Senin) lalu `DISTRIBUTE_WEEKDAY=sat`
→ putaran pertama Senin 17 Agt 10:00, berikutnya tiap 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 jam terjadwal.
berjalan pada jadwal.
Scheduler helper (`next_boundary`, `_wait_db`) sengaja diduplikasi dari
`scan_loop.py` (bukan di-import) karena kedua image Docker terpisah:
@@ -22,19 +26,50 @@ from datetime import datetime, timedelta
import db
import distribute
WEEKDAYS = {
'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3,
'fri': 4, 'sat': 5, 'sun': 6,
}
def next_boundary(now, hours):
"""datetime berikutnya pada jam-jam `hours` yang lebih besar dari `now`.
Sama dengan `scan_loop.next_boundary` (mirror agar image dist mandiri).
"""
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 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_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, weekday, first_run=None):
"""datetime berikutnya utk distribusi.
Mingguan: `weekday` jam `hour`. Bila `first_run` diset, distribusi ⊥ jalan
sebelum tanggal itu — Sabtu (atau hari mingguan) yang jatuh sebelum launch
dilewati; event = {one-off `first_run` (jika masih depan)} ∪ {mingguan ≥
tanggal `first_run`}; pilih yang terdekat > `now`."""
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 first_run is None:
return weekly
start = datetime(first_run.year, first_run.month, first_run.day, hour=hour)
candidates = []
if start >= now:
candidates.append(start)
if weekly >= start:
candidates.append(weekly)
return min(candidates)
def _wait_db(timeout=120):
@@ -61,11 +96,15 @@ def main():
hour = int(os.getenv('DISTRIBUTE_HOUR', '10'))
if not 0 <= hour <= 23:
raise ValueError(f'DISTRIBUTE_HOUR tak valid: {hour!r}')
print(f'Jadwal distribusi: jam {hour} (waktu lokal)')
weekday = parse_weekday(os.getenv('DISTRIBUTE_WEEKDAY', 'sat'))
first_run = parse_first_run(os.getenv('DISTRIBUTE_FIRST_RUN', ''))
jadwal = (f'jam {hour} tiap hari {list(WEEKDAYS)[weekday]} (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])
target = next_boundary(now, hour, weekday, 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)