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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user