koprogo_api/application/ports/
grid_participation_port.rs

1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6/// Identifiant opaque d'une tâche BOINC soumise.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct GridTaskId(pub String);
9
10/// Type de calcul batch délégué à BOINC.
11/// IMPORTANT: Toutes les variantes sont anonymisées — pas de PII.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub enum GridTaskKind {
14    /// Optimisation achat groupé énergie (historique kWh anonymisé)
15    EnergyGroupOptimisation {
16        building_id: Uuid,
17        /// JSON agrégé anonymisé (kWh par période, pas de données personnelles)
18        anonymised_readings_json: String,
19        simulation_months: u32,
20    },
21    /// Simulation thermique bâtiment (données météo + consommation agrégée)
22    BuildingThermalSimulation {
23        building_id: Uuid,
24        insulation_score: f64,
25        surface_m2: f64,
26        heating_degree_days: f64,
27    },
28}
29
30/// Tâche soumise à BOINC.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct GridTask {
33    /// Référence interne KoproGo (UUID stocké en DB)
34    pub internal_id: Uuid,
35    /// Copropriété propriétaire (isolation multi-tenant)
36    pub copropriete_id: Uuid,
37    /// Organisation propriétaire
38    pub organization_id: Uuid,
39    /// Type de calcul
40    pub kind: GridTaskKind,
41    /// Priorité (0-10, défaut 5)
42    pub priority: u8,
43    /// Date limite pour le résultat (BOINC deadline)
44    pub deadline: DateTime<Utc>,
45}
46
47/// Statut d'une tâche BOINC.
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
49pub enum GridTaskStatus {
50    /// Soumise, en attente d'allocation
51    Queued,
52    /// Prise en charge par un worker BOINC
53    Running { started_at: DateTime<Utc> },
54    /// Terminée avec succès
55    Completed {
56        completed_at: DateTime<Utc>,
57        /// JSON agrégé anonymisé
58        result_json: String,
59    },
60    /// Échec (timeout, erreur worker, quota BOINC)
61    Failed {
62        failed_at: DateTime<Utc>,
63        reason: String,
64    },
65    /// Annulée par l'opérateur
66    Cancelled,
67}
68
69/// Consentement BOINC d'un propriétaire (GDPR Article 6.1.a).
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct BoincConsent {
72    pub owner_id: Uuid,
73    pub organization_id: Uuid,
74    pub granted: bool,
75    pub granted_at: Option<DateTime<Utc>>,
76    pub revoked_at: Option<DateTime<Utc>>,
77    /// IP au moment du consentement (GDPR Article 30)
78    pub consent_ip: Option<String>,
79    /// Version de la clause de consentement acceptée
80    pub consent_version: String,
81}
82
83/// Erreurs grid computing
84#[derive(Debug, thiserror::Error)]
85pub enum GridError {
86    #[error("Consent not granted for owner {0}")]
87    ConsentNotGranted(Uuid),
88
89    #[error("BOINC RPC failed: {0}")]
90    RpcFailed(String),
91
92    #[error("Task not found: {0}")]
93    TaskNotFound(String),
94
95    #[error("K-anonymity constraint violated: minimum {min} participants required, got {got}")]
96    KAnonymityViolated { min: usize, got: usize },
97
98    #[error("Process error: {0}")]
99    ProcessError(String),
100
101    #[error("Result parse error: {0}")]
102    ResultParseError(String),
103}
104
105/// Port pour la participation au calcul distribué BOINC.
106/// Gère consentement GDPR + soumission/poll de tâches.
107/// L'adapter `BoincGridAdapter` dans infrastructure/grid/ implémente ce trait.
108///
109/// GDPR: le consentement est explicite (Art. 6.1.a), révocable à tout moment (Art. 7.3).
110/// K-anonymité: minimum 5 participants pour toute tâche de groupe.
111#[async_trait]
112pub trait GridParticipationPort: Send + Sync {
113    // ── Consentement GDPR ────────────────────────────────────────────────────
114
115    /// Vérifie si le propriétaire a consenti à la participation BOINC.
116    async fn check_consent(&self, owner_id: Uuid) -> Result<bool, GridError>;
117
118    /// Récupère les détails du consentement (pour affichage RGPD).
119    async fn get_consent(&self, owner_id: Uuid) -> Result<Option<BoincConsent>, GridError>;
120
121    /// Enregistre le consentement explicite (GDPR Article 7).
122    async fn grant_consent(
123        &self,
124        owner_id: Uuid,
125        organization_id: Uuid,
126        consent_version: &str,
127        consent_ip: Option<&str>,
128    ) -> Result<BoincConsent, GridError>;
129
130    /// Révoque le consentement (GDPR Article 7.3 - droit de retrait immédiat).
131    async fn revoke_consent(&self, owner_id: Uuid) -> Result<(), GridError>;
132
133    // ── Gestion des tâches ───────────────────────────────────────────────────
134
135    /// Soumet une tâche de calcul au cluster BOINC.
136    /// Pré-condition: check_consent() doit être true (vérifié par le use case).
137    async fn submit_task(&self, task: GridTask) -> Result<GridTaskId, GridError>;
138
139    /// Interroge le statut d'une tâche (polling).
140    async fn poll_result(&self, task_id: &GridTaskId) -> Result<GridTaskStatus, GridError>;
141
142    /// Annule une tâche en cours.
143    async fn cancel_task(&self, task_id: &GridTaskId) -> Result<(), GridError>;
144}