159 lines
7.7 KiB
Python
159 lines
7.7 KiB
Python
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."""
|
|
from ia.memoire import purge_old_memories
|
|
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())
|