feat: weekend-aware scheduler with configurable cycle times

- auto_cycle.py: _scheduler_loop now distinguishes weekday (interval_hours)
  from weekend (weekend_cycle_times UTC slots or sleep until Monday);
  _parse_weekend_times() and _next_weekend_slot() helpers;
  get_status() exposes weekend_cycle_enabled + weekend_cycle_times
- cycle.py: CycleConfigRequest adds weekend_cycle_enabled + weekend_cycle_times;
  update_cycle_config validates HH:MM format and persists to config DB
- Config.tsx: weekend scheduling section with enable toggle + time picker
  (06:00/08:00/12:00/18:00/22:00/00:00 UTC presets, multi-select);
  weekendEnabled + weekendTimes state synced from cycle status

Default: enabled with 08:00 + 22:00 UTC (covers news scan + Globex open Sunday)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-21 20:00:06 +02:00
parent d4bc4e6624
commit 319ac35a26
3 changed files with 145 additions and 11 deletions

View File

@@ -432,6 +432,8 @@ export default function Config() {
const [minScore, setMinScore] = useState(0)
const [retentionDays, setRetentionDays] = useState(90)
const [maturityThreshold, setMaturityThreshold] = useState(35)
const [weekendEnabled, setWeekendEnabled] = useState(true)
const [weekendTimes, setWeekendTimes] = useState<string[]>(['08:00', '22:00'])
useEffect(() => {
if (cs) {
setCycleEnabled(cs.enabled ?? false)
@@ -441,6 +443,9 @@ export default function Config() {
setMinScore(cs.min_score_threshold ?? 0)
setRetentionDays(cs.journal_retention_days ?? 90)
setMaturityThreshold(cs.maturity_threshold_pct ?? 35)
setWeekendEnabled(cs.weekend_cycle_enabled ?? true)
const times = (cs.weekend_cycle_times || '08:00,22:00').split(',').map((t: string) => t.trim()).filter(Boolean)
setWeekendTimes(times)
}
}, [cs])
@@ -823,6 +828,49 @@ export default function Config() {
</div>
</div>
{/* Weekend scheduling */}
<div className="border border-slate-700/50 rounded-lg p-3 mb-4 bg-dark-700/30">
<div className="flex items-center justify-between mb-3">
<div>
<span className="text-xs font-semibold text-slate-300">Cycles weekend</span>
<p className="text-[10px] text-slate-500 mt-0.5">Sam/Dim Globex ouvre dim ~18h UTC. Heures en UTC.</p>
</div>
<button
onClick={() => setWeekendEnabled(!weekendEnabled)}
className={clsx('px-3 py-1.5 rounded border text-xs font-semibold transition-all', {
'bg-amber-600/20 border-amber-500/50 text-amber-300': weekendEnabled,
'bg-dark-700 border-slate-700 text-slate-500 hover:border-slate-500': !weekendEnabled,
})}>
{weekendEnabled ? '✓ Activé' : '○ Désactivé'}
</button>
</div>
{weekendEnabled && (
<div>
<label className="text-[10px] text-slate-500 mb-1.5 block">Horaires UTC (cliquer pour activer/désactiver)</label>
<div className="flex gap-1.5 flex-wrap">
{['06:00', '08:00', '12:00', '18:00', '22:00', '00:00'].map(t => {
const active = weekendTimes.includes(t)
return (
<button key={t}
onClick={() => setWeekendTimes(prev =>
active ? prev.filter(x => x !== t) : [...prev, t].sort()
)}
className={clsx('px-2.5 py-1 rounded text-xs font-mono transition-colors', {
'bg-amber-600 text-white': active,
'bg-dark-700 text-slate-500 hover:text-slate-300 border border-slate-700': !active,
})}>
{t}
</button>
)
})}
</div>
{weekendTimes.length === 0 && (
<p className="text-[10px] text-red-400 mt-1">Sélectionner au moins un horaire</p>
)}
</div>
)}
</div>
{cs?.last_cycle && (
<div className="card bg-dark-700/50 mb-2 text-xs space-y-2">
<div className="flex items-center gap-4 flex-wrap">
@@ -847,7 +895,17 @@ export default function Config() {
<div className="flex items-center gap-3">
<button
onClick={() => updateCycleConfig(
{ enabled: cycleEnabled, interval_hours: cycleHours, similarity_threshold: cycleSimilarity, min_ev_threshold: minEv, min_score_threshold: minScore, journal_retention_days: retentionDays, maturity_threshold_pct: maturityThreshold },
{
enabled: cycleEnabled,
interval_hours: cycleHours,
similarity_threshold: cycleSimilarity,
min_ev_threshold: minEv,
min_score_threshold: minScore,
journal_retention_days: retentionDays,
maturity_threshold_pct: maturityThreshold,
weekend_cycle_enabled: weekendEnabled,
weekend_cycle_times: weekendTimes.length > 0 ? weekendTimes.join(',') : '08:00,22:00',
},
{ onSuccess: () => { refetchCycle(); setSavedMsg('Auto-cycle configuré'); setTimeout(() => setSavedMsg(''), 2000) } }
)}
disabled={savingCycle}