Beta/core/tasks.py

167 lines
8 KiB
Python
Raw Permalink Normal View History

import asyncio
import datetime
import logging
import time
import os
import discord
logger = logging.getLogger('Beta')
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()
auto_join = os.environ.get("AUTO_JOIN_VOICE", "true").lower() in ("true", "1", "yes")
if not auto_join:
logger.info("Auto-join vocal désactivé (AUTO_JOIN_VOICE=false)")
return
while not self.bot.is_closed():
for guild in self.bot.guilds:
try:
await self.bot.voice_manager.evaluate_and_connect(guild)
except Exception as e:
logger.debug(f"Voice evaluation skipped: {e}")
await asyncio.sleep(30)
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
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())