ritvikv03 Claude Sonnet 4.6 commited on
Commit
ee79fe2
Β·
1 Parent(s): 6fa571d

fix: start scheduler at module load so gunicorn workers run the scout

Browse files

Previously _scheduler_engine.start() was inside if __name__ == '__main__':
which gunicorn never enters (it imports the module as 'app', not '__main__').
The scheduler was dead on every HuggingFace deployment β€” no new signals.

Changes:
- app.py: move _scheduler_engine.start() + atexit.register(stop) to
module level, immediately after app.layout = _layout(). Runs under
both gunicorn and direct python execution. Remove duplicate start()
from __main__ block.
- core/scheduler.py: set next_run_time=datetime.now(timezone.utc) so
the first scout cycle fires immediately on startup instead of waiting
6 hours. Subsequent runs still follow the 6-hour interval.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +10 -2
  2. core/scheduler.py +4 -2
app.py CHANGED
@@ -19,6 +19,7 @@ Sponsor requirements implemented:
19
 
20
  from __future__ import annotations
21
 
 
22
  import hashlib
23
  import os
24
  import sys
@@ -1644,6 +1645,14 @@ def _layout() -> html.Div:
1644
 
1645
  app.layout = _layout()
1646
 
 
 
 
 
 
 
 
 
1647
 
1648
  # ─────────────────────────────────────────────────────────────
1649
  # Callbacks
@@ -2082,7 +2091,7 @@ def lens_search(topic: str | None, custom: str | None, _ns: int) -> html.Div:
2082
  if __name__ == "__main__":
2083
  _preflight()
2084
 
2085
- _scheduler_engine.start()
2086
  stats = _db_stats_cached()
2087
  log.info("App starting β€” Astra DB: %d signals, HuggingFace: %s",
2088
  stats["total"], "OK" if _HF_OK else "NO KEY")
@@ -2096,4 +2105,3 @@ if __name__ == "__main__":
2096
  # Use PORT env var (Hugging Face / Render) or default to 7860
2097
  port = int(os.environ.get("PORT", 7860))
2098
  app.run(debug=False, host="0.0.0.0", port=port)
2099
- _scheduler_engine.stop()
 
19
 
20
  from __future__ import annotations
21
 
22
+ import atexit
23
  import hashlib
24
  import os
25
  import sys
 
1645
 
1646
  app.layout = _layout()
1647
 
1648
+ # ── Start scheduler at module load (works under gunicorn and __main__) ──
1649
+ # Must be AFTER app.layout so Dash is fully initialised.
1650
+ # gunicorn imports this module directly β€” __main__ guard would skip it.
1651
+ _scheduler_engine.start()
1652
+ atexit.register(_scheduler_engine.stop)
1653
+ log.info("SchedulerEngine started at module load. Next scout: %s",
1654
+ HEALTH.get("next_run_utc"))
1655
+
1656
 
1657
  # ─────────────────────────────────────────────────────────────
1658
  # Callbacks
 
2091
  if __name__ == "__main__":
2092
  _preflight()
2093
 
2094
+ # Scheduler already started at module load β€” just log status
2095
  stats = _db_stats_cached()
2096
  log.info("App starting β€” Astra DB: %d signals, HuggingFace: %s",
2097
  stats["total"], "OK" if _HF_OK else "NO KEY")
 
2105
  # Use PORT env var (Hugging Face / Render) or default to 7860
2106
  port = int(os.environ.get("PORT", 7860))
2107
  app.run(debug=False, host="0.0.0.0", port=port)
 
core/scheduler.py CHANGED
@@ -186,14 +186,16 @@ class SchedulerEngine:
186
  timezone="UTC",
187
  )
188
 
189
- # Scout job β€” runs every N hours, NOT immediately on startup
 
 
190
  self._scheduler.add_job(
191
  _run_scout_cycle,
192
  trigger=IntervalTrigger(hours=self._interval_hours),
193
  id="scout",
194
  name="PESTEL Scout Cycle",
195
  replace_existing=True,
196
- next_run_time=None,
197
  )
198
 
199
  # Heartbeat β€” every 30 s
 
186
  timezone="UTC",
187
  )
188
 
189
+ # Scout job β€” fires immediately on first start, then every N hours.
190
+ # next_run_time=now() ensures the first cycle runs right away so the
191
+ # dashboard is populated without waiting 6 hours after deployment.
192
  self._scheduler.add_job(
193
  _run_scout_cycle,
194
  trigger=IntervalTrigger(hours=self._interval_hours),
195
  id="scout",
196
  name="PESTEL Scout Cycle",
197
  replace_existing=True,
198
+ next_run_time=datetime.now(timezone.utc),
199
  )
200
 
201
  # Heartbeat β€” every 30 s