koprogo_api/application/use_cases/
energy_bill_upload_use_cases.rs

1use std::sync::Arc;
2use uuid::Uuid;
3
4use crate::application::ports::{EnergyBillUploadRepository, EnergyCampaignRepository};
5use crate::domain::entities::{CampaignStatus, EnergyBillUpload};
6
7pub struct EnergyBillUploadUseCases {
8    upload_repo: Arc<dyn EnergyBillUploadRepository>,
9    campaign_repo: Arc<dyn EnergyCampaignRepository>,
10}
11
12impl EnergyBillUploadUseCases {
13    pub fn new(
14        upload_repo: Arc<dyn EnergyBillUploadRepository>,
15        campaign_repo: Arc<dyn EnergyCampaignRepository>,
16    ) -> Self {
17        Self {
18            upload_repo,
19            campaign_repo,
20        }
21    }
22
23    /// Upload energy bill with GDPR consent
24    pub async fn upload_bill(&self, upload: EnergyBillUpload) -> Result<EnergyBillUpload, String> {
25        // Validate campaign exists and is accepting uploads
26        let campaign = self
27            .campaign_repo
28            .find_by_id(upload.campaign_id)
29            .await?
30            .ok_or_else(|| "Campaign not found".to_string())?;
31
32        if campaign.status != CampaignStatus::CollectingData {
33            return Err("Campaign is not collecting data".to_string());
34        }
35
36        // Check if unit already uploaded for this campaign
37        let existing = self
38            .upload_repo
39            .find_by_campaign_and_unit(upload.campaign_id, upload.unit_id)
40            .await?;
41
42        if existing.is_some() {
43            return Err("Unit has already uploaded bill for this campaign".to_string());
44        }
45
46        self.upload_repo.create(&upload).await
47    }
48
49    /// Get upload by ID
50    pub async fn get_upload(&self, id: Uuid) -> Result<Option<EnergyBillUpload>, String> {
51        self.upload_repo.find_by_id(id).await
52    }
53
54    /// Get all uploads for a campaign
55    pub async fn get_uploads_by_campaign(
56        &self,
57        campaign_id: Uuid,
58    ) -> Result<Vec<EnergyBillUpload>, String> {
59        self.upload_repo.find_by_campaign(campaign_id).await
60    }
61
62    /// Get my uploads (for a specific unit)
63    pub async fn get_my_uploads(&self, unit_id: Uuid) -> Result<Vec<EnergyBillUpload>, String> {
64        self.upload_repo.find_by_unit(unit_id).await
65    }
66
67    /// Verify upload (manual verification by admin)
68    pub async fn verify_upload(
69        &self,
70        upload_id: Uuid,
71        verified_by: Uuid,
72    ) -> Result<EnergyBillUpload, String> {
73        let mut upload = self
74            .upload_repo
75            .find_by_id(upload_id)
76            .await?
77            .ok_or_else(|| "Upload not found".to_string())?;
78
79        upload.mark_verified(verified_by)?;
80        self.upload_repo.update(&upload).await
81    }
82
83    /// Anonymize upload (add to building aggregate)
84    pub async fn anonymize_upload(&self, upload_id: Uuid) -> Result<EnergyBillUpload, String> {
85        let mut upload = self
86            .upload_repo
87            .find_by_id(upload_id)
88            .await?
89            .ok_or_else(|| "Upload not found".to_string())?;
90
91        upload.anonymize()?;
92        self.upload_repo.update(&upload).await
93    }
94
95    /// Batch anonymize all verified uploads for a campaign
96    pub async fn batch_anonymize_campaign(&self, campaign_id: Uuid) -> Result<i32, String> {
97        let uploads = self
98            .upload_repo
99            .find_verified_by_campaign(campaign_id)
100            .await?;
101
102        let mut count = 0;
103        for mut upload in uploads {
104            if !upload.anonymized {
105                if upload.anonymize().is_ok() {
106                    self.upload_repo.update(&upload).await?;
107                    count += 1;
108                }
109            }
110        }
111
112        Ok(count)
113    }
114
115    /// Decrypt consumption data (requires encryption key and ownership)
116    pub async fn decrypt_consumption(
117        &self,
118        upload_id: Uuid,
119        requester_unit_id: Uuid,
120        encryption_key: &[u8; 32],
121    ) -> Result<f64, String> {
122        let upload = self
123            .upload_repo
124            .find_by_id(upload_id)
125            .await?
126            .ok_or_else(|| "Upload not found".to_string())?;
127
128        // Verify requester owns this unit (authorization check)
129        if upload.unit_id != requester_unit_id {
130            return Err("Unauthorized: You can only access your own data".to_string());
131        }
132
133        upload.decrypt_kwh(encryption_key)
134    }
135
136    /// Delete upload (GDPR Art. 17 - Right to erasure)
137    pub async fn delete_upload(
138        &self,
139        upload_id: Uuid,
140        requester_unit_id: Uuid,
141    ) -> Result<(), String> {
142        let mut upload = self
143            .upload_repo
144            .find_by_id(upload_id)
145            .await?
146            .ok_or_else(|| "Upload not found".to_string())?;
147
148        // Verify requester owns this unit
149        if upload.unit_id != requester_unit_id {
150            return Err("Unauthorized: You can only delete your own data".to_string());
151        }
152
153        upload.delete()?;
154        self.upload_repo.update(&upload).await?;
155        Ok(())
156    }
157
158    /// Withdraw consent (GDPR Art. 7.3 - Immediate deletion)
159    pub async fn withdraw_consent(
160        &self,
161        upload_id: Uuid,
162        requester_unit_id: Uuid,
163    ) -> Result<(), String> {
164        let mut upload = self
165            .upload_repo
166            .find_by_id(upload_id)
167            .await?
168            .ok_or_else(|| "Upload not found".to_string())?;
169
170        // Verify requester owns this unit
171        if upload.unit_id != requester_unit_id {
172            return Err("Unauthorized: You can only withdraw your own consent".to_string());
173        }
174
175        upload.withdraw_consent()?;
176        self.upload_repo.update(&upload).await?;
177        Ok(())
178    }
179
180    /// Get count of verified uploads for a campaign (k-anonymity check)
181    pub async fn get_verified_count(&self, campaign_id: Uuid) -> Result<i32, String> {
182        self.upload_repo
183            .count_verified_by_campaign(campaign_id)
184            .await
185    }
186
187    /// Check if k-anonymity threshold is met (minimum 5 participants)
188    pub async fn check_k_anonymity(&self, campaign_id: Uuid) -> Result<bool, String> {
189        let count = self.get_verified_count(campaign_id).await?;
190        Ok(count >= 5)
191    }
192
193    /// Auto-delete expired uploads (GDPR retention policy)
194    pub async fn cleanup_expired(&self) -> Result<i32, String> {
195        self.upload_repo.delete_expired().await
196    }
197
198    /// Get expired uploads count (for reporting)
199    pub async fn get_expired_count(&self) -> Result<usize, String> {
200        let expired = self.upload_repo.find_expired().await?;
201        Ok(expired.len())
202    }
203}