You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
"""In-process daily scheduler for proactive search (CLAUDE.md-compatible, no daemon)."""
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from loguru import logger
|
|
|
|
from gimme_job.proactive.engine import ProactiveEngine
|
|
|
|
|
|
def next_run_at(
|
|
now: datetime,
|
|
daily_time: str,
|
|
weekly_day: int,
|
|
weekly_time: str,
|
|
) -> tuple[datetime, str]:
|
|
"""Compute the next (datetime, mode) to run, from a timezone-aware now.
|
|
|
|
daily_time: "HH:MM" — daily pass
|
|
weekly_day: weekday int (0=Mon..6=Sun) — weekly deep scan
|
|
weekly_time: "HH:MM" — weekly deep scan time (same day)
|
|
"""
|
|
hh, mm = (int(x) for x in daily_time.split(":"))
|
|
daily_base = now.replace(hour=hh, minute=mm, second=0, microsecond=0)
|
|
|
|
whh, wmm = (int(x) for x in weekly_time.split(":"))
|
|
days_ahead = (weekly_day - now.weekday()) % 7
|
|
weekly_base = (now + timedelta(days=days_ahead)).replace(
|
|
hour=whh, minute=wmm, second=0, microsecond=0
|
|
)
|
|
|
|
candidates = []
|
|
if daily_base >= now:
|
|
candidates.append((daily_base, "daily"))
|
|
if weekly_base >= now:
|
|
candidates.append((weekly_base, "weekly"))
|
|
# fallbacks: tomorrow
|
|
candidates.append((daily_base + timedelta(days=1), "daily"))
|
|
candidates.append((weekly_base + timedelta(days=7), "weekly"))
|
|
|
|
return min(candidates, key=lambda c: c[0])
|
|
|
|
|
|
def run_forever(
|
|
global_config,
|
|
daily_time: str = "06:00",
|
|
weekly_day: int = 6,
|
|
weekly_time: str = "08:00",
|
|
timezone: str = "America/Phoenix",
|
|
dry_run: bool = False,
|
|
limit_queries: int | None = None,
|
|
) -> None:
|
|
"""Loop: sleep until next scheduled time, then run the engine. Ctrl+C to stop."""
|
|
tz = ZoneInfo(timezone)
|
|
engine = ProactiveEngine(global_config=global_config)
|
|
run_count = 0
|
|
|
|
logger.info(
|
|
f"Proactive scheduler: daily {daily_time} ({timezone}), "
|
|
f"weekly day={weekly_day} at {weekly_time} — Ctrl+C to stop"
|
|
)
|
|
|
|
try:
|
|
while True:
|
|
now = datetime.now(tz)
|
|
next_dt, mode = next_run_at(now, daily_time, weekly_day, weekly_time)
|
|
wait_secs = (next_dt - now).total_seconds()
|
|
logger.info(
|
|
f"Next {mode} run at {next_dt.strftime('%Y-%m-%d %H:%M %Z')} "
|
|
f"(in {wait_secs / 3600:.1f}h)"
|
|
)
|
|
time.sleep(max(1, wait_secs))
|
|
|
|
run_count += 1
|
|
logger.info(f"── Proactive run #{run_count} ({mode}) ──")
|
|
try:
|
|
result = engine.run(
|
|
mode=mode, dry_run=dry_run, limit_queries=limit_queries
|
|
)
|
|
logger.info(
|
|
f"Run done: NEW={result.new_count} REOPENED={result.reopened_count} "
|
|
f"STILL_OPEN={result.still_open_count} VERIFY={result.verify_count}"
|
|
)
|
|
if result.report_path:
|
|
from gimme_job.proactive.html_report import terminal_link
|
|
|
|
logger.info(
|
|
f"Report: {terminal_link(result.report_path, result.report_path)}"
|
|
)
|
|
if result.role_audit_path:
|
|
from gimme_job.proactive.html_report import terminal_link
|
|
|
|
logger.info(
|
|
f"Role audit: "
|
|
f"{terminal_link(result.role_audit_path, result.role_audit_path)}"
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Run #{run_count} failed: {e}")
|
|
except KeyboardInterrupt:
|
|
logger.info("Proactive scheduler stopped.") |