2026-06-13 14:46:02 +00:00
import asyncio
import datetime
import logging
import time
import os
import discord
logger = logging . getLogger ( ' Superviseur ' )
class TaskManager :
""" Handles global background tasks, scheduled loops and reporting. """
def __init__ ( self , bot ) :
self . bot = bot
def start_all ( self ) :
""" Starts all background loops attached to the bot ' s event loop. """
self . bot . loop . create_task ( self . _introspection_report_loop ( ) )
self . bot . loop . create_task ( self . _voice_report_loop ( ) )
self . bot . loop . create_task ( self . _daily_report_check_loop ( ) )
self . bot . loop . create_task ( self . _gdpr_purge_loop ( ) )
self . bot . loop . create_task ( self . _voice_evaluation_loop ( ) )
# Start interviews for missing assets
for asset_id in self . bot . asset_ids :
if str ( asset_id ) not in self . bot . dispatcher . assets :
self . bot . loop . create_task ( self . bot . dispatcher . start_interview ( asset_id ) )
async def _voice_evaluation_loop ( self ) :
""" Periodic check for tactical voice connections. """
await self . bot . wait_until_ready ( )
while not self . bot . is_closed ( ) :
for guild in self . bot . guilds :
await self . bot . voice_manager . evaluate_and_connect ( guild )
await asyncio . sleep ( 30 ) # Reduit à 30s pour plus de réactivité en tâche de fond
async def _introspection_report_loop ( self ) :
""" Periodic broadcast of system health metrics. """
await self . bot . wait_until_ready ( )
while not self . bot . is_closed ( ) :
await asyncio . sleep ( 600 ) # Every 10 minutes
await self . _send_health_report ( )
async def _send_health_report ( self ) :
""" Build and send a clinical health report embed. """
total = self . bot . metrics [ " total_llm_requests " ]
success = self . bot . metrics [ " total_llm_success " ]
rate = ( success / total * 100 ) if total > 0 else 100
avg_time = ( self . bot . metrics [ " total_llm_time " ] / success ) if success > 0 else 0
status_report = (
f " **◈ INTELLIGENCE CORE** \n "
f " - Requests Processed : ` { total } ` \n "
f " - Reliability Rate : ` { rate : .1f } %` \n "
f " - Latency Heuristic : ` { avg_time : .2f } s` (Target: < 5s) \n \n "
f " **◈ TACTICAL OVERVIEW** \n "
f " - Voice Protocol : `READY` \n "
f " - Active Deployment : ` { self . bot . voice_manager . current_channel . name if self . bot . voice_manager . current_channel else ' NONE ' } ` \n "
)
await self . bot . introspection_log (
" SYSTEM STATUS REPORT " ,
status_report ,
discord . Color . dark_blue ( )
)
async def _daily_report_check_loop ( self ) :
""" Check for 21:30 and trigger the daily report using local timezone. """
await self . bot . wait_until_ready ( )
report_sent_today = False
while not self . bot . is_closed ( ) :
now = datetime . datetime . now ( self . bot . timezone )
if now . hour == 21 and now . minute == 30 :
if not report_sent_today :
await self . _send_daily_summary ( )
report_sent_today = True
else :
report_sent_today = False
await asyncio . sleep ( 60 )
async def _gdpr_purge_loop ( self ) :
""" Daily purge of old memory files (30 days) for GDPR compliance. """
2026-06-16 17:09:34 +00:00
from brain . memoire import purge_old_memories
2026-06-13 14:46:02 +00:00
while not self . bot . is_closed ( ) :
now = datetime . datetime . now ( self . bot . timezone )
if now . hour == 3 and now . minute == 0 :
logger . info ( " ◈ RGPD: Démarrage de la purge quotidienne... " )
deleted = await purge_old_memories ( days = 30 )
logger . info ( f " ◈ RGPD: Purge terminée. { deleted } fichiers supprimés. " )
await asyncio . sleep ( 65 )
await asyncio . sleep ( 30 )
async def _send_daily_summary ( self ) :
""" Generate and send a dense, highly detailed clinical report at 21:30. """
tasks = self . bot . dispatcher . tasks
tense_sessions = self . bot . metrics . get ( " daily_tense_sessions " , 0 )
asset_stats = { }
for t_id , t_data in tasks . items ( ) :
a_id = t_data . get ( " asset_id " )
if a_id not in asset_stats :
asset_name = " Inconnu "
if str ( a_id ) in self . bot . dispatcher . assets :
asset_name = self . bot . dispatcher . assets [ str ( a_id ) ] . get ( " name " , " Anonyme " )
asset_stats [ a_id ] = { " name " : asset_name , " accepted " : 0 , " missed " : 0 , " total " : 0 }
asset_stats [ a_id ] [ " total " ] + = 1
if t_data [ " status " ] == " ACCEPTED " :
asset_stats [ a_id ] [ " accepted " ] + = 1
elif t_data [ " status " ] == " PENDING " and t_data . get ( " deadline " , 0 ) < time . time ( ) :
asset_stats [ a_id ] [ " missed " ] + = 1
stats_lines = [ ]
for a_id , s in asset_stats . items ( ) :
stats_lines . append ( f " - ** { s [ ' name ' ] } ** : ` { s [ ' accepted ' ] } / { s [ ' total ' ] } ` acceptées, ` { s [ ' missed ' ] } ` manquées. " )
task_details = [ ]
for t_id , t_data in list ( tasks . items ( ) ) [ - 15 : ] :
status_emoji = " ✅ " if t_data [ " status " ] == " ACCEPTED " else " ❌ " if t_data . get ( " deadline " , 0 ) < time . time ( ) else " ⏳ "
asset_name = self . bot . dispatcher . assets . get ( str ( t_data [ ' asset_id ' ] ) , { } ) . get ( ' name ' , ' N/A ' )
task_details . append ( f " { status_emoji } ` { t_id } ` | Staff: { asset_name } | Urgence: { t_data [ ' importance ' ] } | Context: { t_data [ ' content ' ] [ : 50 ] } ... " )
summary = (
f " **◈ RAPPORT ANALYTIQUE DE FIN DE CYCLE ◈** \n "
f " Période : Dernières 24h | Heure : 21h30 \n \n "
f " **◈ BILAN GLOBAL DES OPÉRATIONS** \n "
f " - Volume total d ' actions de modération : ` { len ( tasks ) } ` \n "
f " - Sessions vocales sous tension : ` { tense_sessions } ` \n \n "
f " **◈ PERFORMANCE DES ASSETS** \n "
+ ( " \n " . join ( stats_lines ) if stats_lines else " Aucune activité de staff enregistrée. " ) + " \n \n "
f " **◈ LOG DÉTAILLÉ DES DERNIERS SIGNAUX** \n "
+ ( " \n " . join ( task_details ) if task_details else " Aucun signal capturé. " ) + " \n \n "
f " **◈ CONCLUSION HEURISTIQUE** \n "
f " Le système est nominal. " + ( " PRÉCONISATION : Vigilance accrue requise sur les membres suspects. " if tense_sessions > 0 else " État de l ' écosystème : Stable. " )
)
if summary and self . bot . admin_ids :
for admin_id in self . bot . admin_ids :
admin = self . bot . get_user ( admin_id ) or await self . bot . fetch_user ( admin_id )
if admin :
await self . bot . messaging . send_embed ( admin , " SYNTHÈSE CLINIQUE QUOTIDIENNE " , summary , color = discord . Color . dark_purple ( ) )
self . bot . metrics [ " daily_tense_sessions " ] = 0
self . bot . dispatcher . tasks = { k : v for k , v in tasks . items ( ) if v [ ' status ' ] == ' PENDING ' and v . get ( ' deadline ' , 0 ) > time . time ( ) }
# Le chemin de Active tasks doit être vers /data si on l'a déplacé.
# Pour l'instant, on sauvegarde dans data/
data_dir = os . path . join ( os . path . dirname ( __file__ ) , " .. " , " data " )
os . makedirs ( data_dir , exist_ok = True )
self . bot . dispatcher . _save_data ( self . bot . dispatcher . tasks , os . path . join ( data_dir , " active_tasks.json " ) )
async def _voice_report_loop ( self ) :
""" Periodic broadcast of active voice session summaries. """
await self . bot . wait_until_ready ( )
while not self . bot . is_closed ( ) :
await asyncio . sleep ( 900 ) # Every 15 minutes
if self . bot . voice_manager . is_monitoring and self . bot . voice_manager . session_buffer :
report = await self . bot . voice_manager . generate_session_report ( )
await self . bot . introspection_log ( " VOICE SESSION UPDATE (15m) " , report , discord . Color . blue ( ) )