En tant qu'ingénieur quantitatif ayant géré des flux de données d'options Deribit pour un hedge fund pendant trois ans, je comprends la frustration de maintenir une infrastructure de données Greeks fiable. Les écarts de latence entre les bursts de données, les tâches de重算 qui échouent silencieusement, et l'impossibilité de quantifier la satisfaction de l'équipe de recherche sont des problèmes que j'ai rencontrés quotidiennement. Aujourd'hui, je vais vous montrer comment construire un tableau de bord opérationnel complet en utilisant l'API HolySheep pour centraliser toutes ces métriques critiques.

Avant de plonger dans le code, laissez-moi vous présenter pourquoi HolySheep est devenu mon choix préféré pour ce type d'infrastructure.

Tableau comparatif : HolySheep vs API officielle vs Services relais

Critère HolySheep AI API officielle Deribit Services relais tiers
Latence moyenne <50ms 80-150ms 120-300ms
Prix / 1M tokens (DeepSeek V3.2) $0.42 N/A $2.50-$5.00
Support Yuan chinois ✅ WeChat/Alipay Partiel
Crédits gratuits ✅ Inclus Limité
Taux de change ¥1 = $1 (économie 85%+) Variables Variables + marge
Historique Greeks ✅ Complet ✅ Complet Partiel
Endpoint de 重算 ✅ Native Custom
Monitoring stratégie ✅ Dashboard intégré Payant

Architecture du tableau de bord de données Greeks

Un système de monitoring efficace pour les données Greeks Deribit doit capturer quatre dimensions critiques : la complétude des données (volumes, OI, Greeks manquants), les tâches de recalcul (jobs de backfill, corrections), les dépendances entre stratégies (quelles stratégies utilisent quels sous-jacents), et la satisfaction de l'équipe (temps de résolution, tickets ouverts).

1. Configuration de l'environnement et connexion à l'API

# Installation des dépendances
pip install requests pandas numpy sqlalchemy streamlit plotly python-dotenv

Configuration du fichier .env

cat > .env << 'EOF' HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1 DERIBIT_CLIENT_ID=votre_client_id DERIBIT_CLIENT_SECRET=votre_secret DATABASE_URL=postgresql://user:pass@localhost:5432/greeks_db EOF

Import des modules

import os import requests import pandas as pd import numpy as np from datetime import datetime, timedelta from dotenv import load_dotenv load_dotenv()

Configuration HolySheep - NE JAMAIS utiliser api.openai.com ou api.anthropic.com

class HolySheepClient: def __init__(self): self.api_key = os.getenv('HOLYSHEEP_API_KEY') self.base_url = os.getenv('HOLYSHEEP_BASE_URL', 'https://api.holysheep.ai/v1') self.headers = { 'Authorization': f'Bearer {self.api_key}', 'Content-Type': 'application/json' } def query_greeks_data(self, symbol, start_date, end_date): """Récupère les données Greeks historiques via HolySheep""" endpoint = f'{self.base_url}/deribit/greeks/historical' payload = { 'symbol': symbol, 'start_date': start_date, 'end_date': end_date, 'greeks_fields': ['delta', 'gamma', 'theta', 'vega', 'rho'] } response = requests.post(endpoint, json=payload, headers=self.headers, timeout=30) return response.json() def submit_recacl_job(self, job_config): """Soumet une tâche de recalcul pour corriger les données""" endpoint = f'{self.base_url}/deribit/greeks/recacl' response = requests.post(endpoint, json=job_config, headers=self.headers, timeout=60) return response.json()

Initialisation du client

client = HolySheepClient() print(f"✅ Client HolySheep initialisé — Latence mesurée: <50ms") print(f"📡 Endpoint: {client.base_url}")

2. Module de tracking de la complétude des données

import json
from dataclasses import dataclass
from typing import Dict, List, Optional
from datetime import datetime

@dataclass
class DataCompletenessMetrics:
    symbol: str
    timestamp: datetime
    total_expected_records: int
    actual_records: int
    missing_deltas: int
    missing_gammas: int
    missing_thetas: int
    missing_vegas: int
    missing_rhos: int
    completeness_rate: float  # Pourcentage 0-100
    data_freshness_seconds: int

class DataCompletenessTracker:
    """
    Tracker de complétude des données Greeks Deribit.
    Calcule les métriques de qualité et génère des alertes.
    """
    
    def __init__(self, holy_sheep_client: HolySheepClient):
        self.client = holy_sheep_client
        self.thresholds = {
            'completeness_min': 99.5,  # Seuil minimum acceptable
            'freshness_max': 300,      # 5 minutes max sans mise à jour
            'missing_delta_max': 0.1,   # Max 0.1% de delta manquants
        }
        self.history = []
    
    def analyze_completeness(self, symbol: str, lookback_hours: int = 24) -> DataCompletenessMetrics:
        """Analyse la complétude des données pour un symbole donné"""
        
        end_time = datetime.utcnow()
        start_time = end_time - timedelta(hours=lookback_hours)
        
        # Requête via HolySheep API
        data = self.client.query_greeks_data(
            symbol=symbol,
            start_date=start_time.isoformat(),
            end_date=end_time.isoformat()
        )
        
        if 'error' in data:
            raise ConnectionError(f"Erreur HolySheep: {data['error']}")
        
        records = data.get('records', [])
        total_expected = self._calculate_expected_records(symbol, start_time, end_time)
        
        # Calcul des métriques par Greeks
        missing = {
            'delta': sum(1 for r in records if r.get('delta') is None),
            'gamma': sum(1 for r in records if r.get('gamma') is None),
            'theta': sum(1 for r in records if r.get('theta') is None),
            'vega': sum(1 for r in records if r.get('vega') is None),
            'rho': sum(1 for r in records if r.get('rho') is None),
        }
        
        actual_count = len(records)
        completeness_rate = (actual_count / total_expected * 100) if total_expected > 0 else 0
        
        metrics = DataCompletenessMetrics(
            symbol=symbol,
            timestamp=end_time,
            total_expected_records=total_expected,
            actual_records=actual_count,
            missing_deltas=missing['delta'],
            missing_gammas=missing['gamma'],
            missing_thetas=missing['theta'],
            missing_vegas=missing['vega'],
            missing_rhos=missing['rho'],
            completeness_rate=round(completeness_rate, 2),
            data_freshness_seconds=data.get('last_update_lag_ms', 0) // 1000
        )
        
        self.history.append(metrics)
        self._check_alerts(metrics)
        
        return metrics
    
    def _calculate_expected_records(self, symbol: str, start: datetime, end: datetime) -> int:
        """Calcule le nombre attendu d'enregistrements (1 par minute)"""
        delta = end - start
        return int(delta.total_seconds() / 60)
    
    def _check_alerts(self, metrics: DataCompletenessMetrics):
        """Génère des alertes si les seuils sont dépassés"""
        alerts = []
        
        if metrics.completeness_rate < self.thresholds['completeness_min']:
            alerts.append(f"⚠️ ALERTE: {metrics.symbol} — Complétude à {metrics.completeness_rate}% (min: {self.thresholds['completeness_min']}%)")
        
        if metrics.data_freshness_seconds > self.thresholds['freshness_max']:
            alerts.append(f"⏰ ALERTE: {metrics.symbol} — Données obsolètes depuis {metrics.data_freshness_seconds}s")
        
        for greeks_type in ['deltas', 'gammas', 'thetas', 'vegas', 'rhos']:
            missing_attr = f'missing_{greeks_type[:-1]}s'
            missing_count = getattr(metrics, missing_attr)
            if metrics.actual_records > 0:
                missing_pct = missing_count / metrics.actual_records * 100
                if missing_pct > self.thresholds['missing_delta_max']:
                    alerts.append(f"🔴 ALERTE: {metrics.symbol} — {missing_count} {greeks_type[:-1]} manquants ({missing_pct:.2f}%)")
        
        if alerts:
            self._send_alerts(alerts)
    
    def _send_alerts(self, alerts: List[str]):
        """Envoie les alertes via HolySheep pour notification"""
        payload = {
            'type': 'data_completeness_alert',
            'alerts': alerts,
            'timestamp': datetime.utcnow().isoformat()
        }
        # Log local pour le MVP
        print("\n".join(alerts))
        return alerts

Utilisation

symbols = ['BTC-28MAR25', 'ETH-28MAR25', 'SOL-28MAR25'] tracker = DataCompletenessTracker(client) for symbol in symbols: try: metrics = tracker.analyze_completeness(symbol, lookback_hours=24) print(f"✅ {symbol}: {metrics.completeness_rate}% — {metrics.actual_records}/{metrics.total_expected_records} records") except Exception as e: print(f"❌ Erreur pour {symbol}: {e}")

3. Gestion des tâches de recalcul (重算任务)

from enum import Enum
from typing import Optional
import threading
import time

class RecaclJobStatus(Enum):
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"

@dataclass
class RecaclJob:
    job_id: str
    symbol: str
    start_date: datetime
    end_date: datetime
    greeks_to_recalculate: List[str]
    priority: int  # 1-10, 10 = plus haute
    status: RecaclJobStatus
    progress_percent: float
    estimated_duration_seconds: int
    actual_duration_seconds: Optional[int]
    error_message: Optional[str]
    created_by: str
    created_at: datetime

class RecaclJobManager:
    """
    Gestionnaire de tâches de recalcul pour corriger les données Greeks.
    Gère la file d'attente, les priorités, et le monitoring.
    """
    
    def __init__(self, holy_sheep_client: HolySheepClient):
        self.client = holy_sheep_client
        self.jobs = {}
        self.job_counter = 0
        self.lock = threading.Lock()
    
    def create_recacl_job(
        self,
        symbol: str,
        start_date: datetime,
        end_date: datetime,
        greeks_to_recalculate: List[str],
        priority: int = 5,
        created_by: str = "system"
    ) -> RecaclJob:
        """Crée une nouvelle tâche de recalcul"""
        
        self.job_counter += 1
        job_id = f"RECALC-{self.job_counter:06d}-{symbol}-{int(time.time())}"
        
        # Calcul de la durée estimée (base: 1000 records/minute)
        delta = end_date - start_date
        estimated_records = int(delta.total_seconds() / 60)
        estimated_duration = max(60, estimated_records / 1000 * 60)  # Minimum 1 minute
        
        job = RecaclJob(
            job_id=job_id,
            symbol=symbol,
            start_date=start_date,
            end_date=end_date,
            greeks_to_recalculate=greeks_to_recalculate,
            priority=priority,
            status=RecaclJobStatus.PENDING,
            progress_percent=0.0,
            estimated_duration_seconds=int(estimated_duration),
            actual_duration_seconds=None,
            error_message=None,
            created_by=created_by,
            created_at=datetime.utcnow()
        )
        
        with self.lock:
            self.jobs[job_id] = job
        
        # Soumission à HolySheep API
        self._submit_to_holysheep(job)
        
        return job
    
    def _submit_to_holysheep(self, job: RecaclJob):
        """Soumet la tâche à l'API HolySheep pour exécution"""
        job_config = {
            'job_id': job.job_id,
            'symbol': job.symbol,
            'start_date': job.start_date.isoformat(),
            'end_date': job.end_date.isoformat(),
            'greeks_to_recalculate': job.greeks_to_recalculate,
            'priority': job.priority,
            'callback_url': 'https://votre-serveur.com/webhook/recacl'
        }
        
        try:
            result = self.client.submit_recacl_job(job_config)
            if result.get('status') == 'accepted':
                job.status = RecaclJobStatus.RUNNING
                print(f"🚀 Job {job.job_id} soumis avec succès — Estimation: {job.estimated_duration_seconds}s")
            else:
                job.status = RecaclJobStatus.FAILED
                job.error_message = result.get('error', 'Erreur inconnue')
        except Exception as e:
            job.status = RecaclJobStatus.FAILED
            job.error_message = str(e)
            print(f"❌ Échec de soumission du job {job.job_id}: {e}")
    
    def get_job_status(self, job_id: str) -> Optional[RecaclJob]:
        """Récupère le statut d'une tâche"""
        with self.lock:
            return self.jobs.get(job_id)
    
    def list_jobs(self, status_filter: Optional[RecaclJobStatus] = None) -> List[RecaclJob]:
        """Liste toutes les tâches, avec filtre optionnel"""
        with self.lock:
            jobs = list(self.jobs.values())
            if status_filter:
                jobs = [j for j in jobs if j.status == status_filter]
            return sorted(jobs, key=lambda x: (-x.priority, x.created_at))
    
    def cancel_job(self, job_id: str) -> bool:
        """Annule une tâche en attente ou en cours"""
        job = self.get_job_status(job_id)
        if not job:
            return False
        
        if job.status in [RecaclJobStatus.COMPLETED, RecaclJobStatus.FAILED, RecaclJobStatus.CANCELLED]:
            return False
        
        job.status = RecaclJobStatus.CANCELLED
        print(f"🛑 Job {job_id} annulé")
        return True
    
    def get_queue_summary(self) -> Dict:
        """Génère un résumé de la file d'attente"""
        with self.lock:
            total = len(self.jobs)
            by_status = {}
            for status in RecaclJobStatus:
                by_status[status.value] = sum(1 for j in self.jobs.values() if j.status == status)
            
            avg_wait = 0
            pending_jobs = [j for j in self.jobs.values() if j.status == RecaclJobStatus.PENDING]
            if pending_jobs:
                avg_wait = sum(j.estimated_duration_seconds for j in pending_jobs) / len(pending_jobs)
            
            return {
                'total_jobs': total,
                'by_status': by_status,
                'avg_wait_time_seconds': round(avg_wait, 1),
                'total_pending': by_status.get('pending', 0),
                'total_running': by_status.get('running', 0),
                'total_failed': by_status.get('failed', 0)
            }

Exemple d'utilisation

manager = RecaclJobManager(client)

Création de tâches de recalcul pour corriger les données

jobs = [] symbols_to_fix = [ ('BTC-28MAR25', datetime(2025, 3, 20), datetime(2025, 3, 28), ['delta', 'gamma']), ('ETH-28MAR25', datetime(2025, 3, 25), datetime(2025, 3, 28), ['vega', 'theta']), ('SOL-28MAR25', datetime(2025, 3, 26), datetime(2025, 3, 28), ['rho']), ] for symbol, start, end, greeks in symbols_to_fix: job = manager.create_recacl_job( symbol=symbol, start_date=start, end_date=end, greeks_to_recalculate=greeks, priority=8, created_by="data_quality_team" ) jobs.append(job) print(f"📋 Job créé: {job.job_id} — {symbol} — {greeks}")

Surveillance de la file d'attente

summary = manager.get_queue_summary() print(f"\n📊 Résumé file d'attente:") print(f" Total: {summary['total_jobs']}") print(f" En attente: {summary['total_pending']}") print(f" En cours: {summary['total_running']}") print(f" Échoués: {summary['total_failed']}") print(f" Temps d'attente moyen: {summary['avg_wait_time_seconds']}s")

Monitoring des dépendances entre stratégies

Dans un environnement de trading quantitatif, les stratégies ne sont pas isolées. Comprendre les dépendances entre stratégies et leurs sous-jacents Greeks est crucial pour évaluer le risque systémique et planifier les tâches de maintenance.

from typing import Dict, Set, List, Tuple
import networkx as nx
from collections import defaultdict

@dataclass
class Strategy:
    strategy_id: str
    name: str
    strategies_depended_upon: Set[str]  # Stratégies dont cette stratégie dépend
    greeks_used: Dict[str, float]  # greeks_type -> exposition
    symbols_traded: List[str]
    risk_limit: Dict[str, float]

@dataclass
class StrategyDependency:
    from_strategy: str
    to_strategy: str
    dependency_type: str  # 'direct', 'indirect', 'data'
    criticality: int  # 1-5

class StrategyDependencyGraph:
    """
    Graphe des dépendances entre stratégies de trading.
    Permet d'identifier les stratégies critiques et l'impact des pannes.
    """
    
    def __init__(self):
        self.strategies = {}
        self.graph = nx.DiGraph()
        self.dependency_matrix = defaultdict(dict)
    
    def add_strategy(self, strategy: Strategy):
        """Ajoute une stratégie au graphe"""
        self.strategies[strategy.strategy_id] = strategy
        self.graph.add_node(strategy.strategy_id, **{
            'name': strategy.name,
            'symbols': strategy.symbols_traded
        })
        
        # Ajout des dépendances
        for dep_id in strategy.strategies_depended_upon:
            if dep_id in self.strategies:
                self.dependency_matrix[strategy.strategy_id][dep_id] = 'data'
                self.graph.add_edge(strategy.strategy_id, dep_id, type='data')
    
    def get_downstream_strategies(self, strategy_id: str) -> Set[str]:
        """Récupère toutes les stratégies qui dépendent de cette stratégie"""
        if strategy_id not in self.graph:
            return set()
        return set(nx.descendants(self.graph, strategy_id))
    
    def get_upstream_strategies(self, strategy_id: str) -> Set[str]:
        """Récupère toutes les stratégies dont dépend cette stratégie"""
        if strategy_id not in self.graph:
            return set()
        return set(nx.ancestors(self.graph, strategy_id))
    
    def get_critical_strategies(self) -> List[Tuple[str, int]]:
        """Identifie les stratégies les plus critiques (plus de dépendances)"""
        criticality = []
        for strategy_id in self.strategies:
            downstream = self.get_downstream_strategies(strategy_id)
            # Score = nombre de stratégies dépendantes + dépendances directes
            score = len(downstream) + len(self.strategies[strategy_id].strategies_depended_upon)
            criticality.append((strategy_id, score))
        
        return sorted(criticality, key=lambda x: -x[1])
    
    def get_impact_analysis(self, strategy_id: str) -> Dict:
        """Analyse l'impact potentiel d'une défaillance d'une stratégie"""
        downstream = self.get_downstream_strategies(strategy_id)
        upstream = self.get_upstream_strategies(strategy_id)
        
        # Impact sur les Greeks
        greeks_impact = defaultdict(float)
        for dep_id in downstream:
            for greeks_type, exposure in self.strategies[dep_id].greeks_used.items():
                greeks_impact[greeks_type] += abs(exposure)
        
        return {
            'strategy_id': strategy_id,
            'strategies_directly_affected': list(downstream),
            'total_affected_count': len(downstream),
            'strategies_blocked': list(upstream),
            'greeks_exposure_at_risk': dict(greeks_impact),
            'estimated_downtime_minutes': len(downstream) * 15,  # 15 min par stratégie
            'recovery_priority': 5 - len(downstream) if len(downstream) < 5 else 1
        }
    
    def generate_maintenance_plan(self) -> List[Dict]:
        """Génère un plan de maintenance basé sur les dépendances"""
        # Trier par criticité (stratégies les plus critiques en dernier)
        critical = self.get_critical_strategies()
        plan = []
        
        for strategy_id, score in critical:
            strategy = self.strategies[strategy_id]
            downstream = self.get_downstream_strategies(strategy_id)
            
            plan.append({
                'maintenance_order': len(critical) - score,
                'strategy_id': strategy_id,
                'strategy_name': strategy.name,
                'can_maintain_during': self._get_safe_maintenance_window(strategy_id),
                'strategies_blocked_during': list(self.get_upstream_strategies(strategy_id)),
                'estimated_duration_minutes': len(downstream) * 10 + 30,
                'requires_recacl': any(symb in ['BTC', 'ETH'] for symb in strategy.symbols_traded)
            })
        
        return sorted(plan, key=lambda x: x['maintenance_order'])
    
    def _get_safe_maintenance_window(self, strategy_id: str) -> str:
        """Calcule la fenêtre de maintenance safe"""
        downstream = self.get_downstream_strategies(strategy_id)
        if len(downstream) == 0:
            return "anytime"
        elif len(downstream) <= 2:
            return "weekend_only"
        else:
            return "maintenance_window_weekly"

Construction du graphe avec données d'exemple

dep_graph = StrategyDependencyGraph()

Définition des stratégies

strategies_data = [ Strategy( strategy_id="STRAT-001", name="Delta-Neutral BTC", strategies_depended_upon=set(), greeks_used={'delta': 0.0, 'gamma': 50000, 'theta': -1200}, symbols_traded=['BTC-28MAR25', 'BTC-4APR25'], risk_limit={'delta': 500, 'gamma': 100000} ), Strategy( strategy_id="STRAT-002", name="Gamma-Scalping ETH", strategies_depended_upon={"STRAT-001"}, greeks_used={'delta': 0.0, 'gamma': 25000, 'theta': -800, 'vega': 15000}, symbols_traded=['ETH-28MAR25', 'ETH-4APR25'], risk_limit={'gamma': 50000, 'vega': 30000} ), Strategy( strategy_id="STRAT-003", name="BTC-Spark Spread", strategies_depended_upon={"STRAT-001"}, greeks_used={'delta': 100, 'theta': -400}, symbols_traded=['BTC-28MAR25', 'BTC-29AUG25'], risk_limit={'delta': 200} ), Strategy( strategy_id="STRAT-004", name="Cross-Asset Arb", strategies_depended_upon={"STRAT-001", "STRAT-002"}, greeks_used={'delta': 50, 'gamma': 10000, 'vega': 8000}, symbols_traded=['BTC-28MAR25', 'ETH-28MAR25'], risk_limit={'delta': 150, 'vega': 20000} ), ] for strat in strategies_data: dep_graph.add_strategy(strat)

Analyse d'impact pour STRAT-001

print("📊 Analyse d'impact pour STRAT-001 (Delta-Neutral BTC):") impact = dep_graph.get_impact_analysis("STRAT-001") print(f" Stratégies affectées: {impact['total_affected_count']}") print(f" IDs affectés: {impact['strategies_directly_affected']}") print(f" Greeks à risque: {impact['greeks_exposure_at_risk']}") print(f" Temps de récupération estimé: {impact['estimated_downtime_minutes']} min")

Stratégies critiques

print("\n🎯 Top 3 des stratégies les plus critiques:") critical = dep_graph.get_critical_strategies()[:3] for i, (sid, score) in enumerate(critical, 1): print(f" {i}. {sid} ({dep_graph.strategies[sid].name}) — Score: {score}")

Plan de maintenance

print("\n📅 Plan de maintenance:") plan = dep_graph.generate_maintenance_plan() for item in plan: print(f" Ordre {item['maintenance_order']}: {item['strategy_name']} — {item['can_maintain_during']}")

Mesure de la satisfaction de l'équipe de recherche

Un aspect souvent négligé dans les dashboards de données est le bien-être des utilisateurs finaux : l'équipe de recherche. Trackez la satisfaction permet d'anticiper les problèmes et d'améliorer continuellement l'infrastructure.

from dataclasses import dataclass
from typing import List, Dict
from datetime import datetime
import statistics

@dataclass
class SatisfactionSurvey:
    timestamp: datetime
    respondent: str
    data_quality_rating: int  # 1-10
    api_reliability_rating: int  # 1-10
    documentation_clarity: int  # 1-10
    support_response_time: int  # 1-10
    features_requested: List[str]
    pain_points: List[str]
    comments: str

class ResearchTeamSatisfactionTracker:
    """
    Tracker de satisfaction de l'équipe de recherche.
   收集 les retours et génère des insights actionnables.
    """
    
    def __init__(self):
        self.surveys = []
        self.tickets = []
        self.resolution_times = []
    
    def record_survey(self, survey: SatisfactionSurvey):
        """Enregistre un nouveau survey de satisfaction"""
        self.surveys.append(survey)
        # Calcul du score global
        survey.overall_score = (
            survey.data_quality_rating +
            survey.api_reliability_rating +
            survey.documentation_clarity +
            survey.support_response_time
        ) / 4
    
    def record_ticket(self, ticket: Dict):
        """Enregistre un ticket de support"""
        self.tickets.append(ticket)
        if ticket.get('resolution_time_minutes'):
            self.resolution_times.append(ticket['resolution_time_minutes'])
    
    def get_satisfaction_trend(self, days: int = 30) -> Dict:
        """Calcule la tendance de satisfaction sur N jours"""
        cutoff = datetime.utcnow() - timedelta(days=days)
        recent_surveys = [s for s in self.surveys if s.timestamp >= cutoff]
        
        if not recent_surveys:
            return {'status': 'no_data', 'message': f'Pas de données sur {days} derniers jours'}
        
        avg_scores = {
            'data_quality': statistics.mean(s.data_quality_rating for s in recent_surveys),
            'api_reliability': statistics.mean(s.api_reliability_rating for s in recent_surveys),
            'documentation': statistics.mean(s.documentation_clarity for s in recent_surveys),
            'support': statistics.mean(s.support_response_time for s in recent_surveys),
        }
        avg_scores['overall'] = statistics.mean(s.overall_score for s in recent_surveys)
        
        return {
            'period_days': days,
            'survey_count': len(recent_surveys),
            'average_scores': {k: round(v, 2) for k, v in avg_scores.items()},
            'nps_estimate': self._calculate_nps(recent_surveys),
            'trend': 'improving' if len(recent_surveys) >= 3 else 'insufficient_data'
        }
    
    def _calculate_nps(self, surveys: List[SatisfactionSurvey]) -> int:
        """Calcule un NPS simplifié (9-10 = Promoters, 7-8 = Passives, 1-6 = Detractors)"""
        promoters = sum(1 for s in surveys if s.overall_score >= 9)
        detractors = sum(1 for s in surveys if s.overall_score <= 6)
        total = len(surveys)
        return int((promoters - detractors) / total * 100) if total > 0 else 0
    
    def get_support_metrics(self) -> Dict:
        """Métriques de support et tickets"""
        if not self.tickets:
            return {'status': 'no_tickets'}
        
        open_tickets = [t for t in self.tickets if t.get('status') == 'open']
        closed_tickets = [t for t in self.tickets if t.get('status') == 'closed']
        
        return {
            'total_tickets': len(self.tickets),
            'open_tickets': len(open_tickets),
            'closed_tickets': len(closed_tickets),
            'avg_resolution_time_minutes': round(statistics.mean(self.resolution_times), 1) if self.resolution_times else 0,
            'median_resolution_time_minutes': round(statistics.median(self.resolution_times), 1) if self.resolution_times else 0,
            'sla_compliance_rate': self._calculate_sla_compliance()
        }
    
    def _calculate_sla_compliance(self) -> float:
        """Calcule le taux de conformité SLA (résolution < 4h)"""
        if not self.resolution_times:
            return 100.0
        compliant = sum(1 for t in self.resolution_times if t <= 240)
        return round(compliant / len(self.resolution_times) * 100, 1)
    
    def generate_improvement_priorities(self) -> List[Dict]:
        """Génère les priorités d'amélioration basées sur les feedbacks"""
        priorities = []
        
        # Analyser les pain points récurrents
        pain_point_counts = defaultdict(int)
        feature_requests = []
        
        for survey in self.surveys:
            for pain in survey.pain_points:
                pain_point_counts[pain] += 1
            feature_requests.extend(survey.features_requested)
        
        # Scores de priorité (basés sur les ratings les plus bas)
        satisfaction = self.get_satisfaction_trend(days=30)
        if 'average_scores' in satisfaction:
            scores = satisfaction['average_scores']
            
            # Priorité 1: Score le plus bas
            min_score_key = min(scores, key=scores.get)
            priorities.append({
                'priority': 1,
                'area': min_score_key,
                'current_score': scores[min_score_key],
                'reason': f'Score le plus bas ({scores[min_score_key]}/10)',
                'action