koprogo_api/application/ports/
linky_api_client.rs

1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5/// Linky API client port for fetching electricity consumption data
6#[async_trait]
7pub trait LinkyApiClient: Send + Sync {
8    /// Exchange OAuth2 authorization code for access token + refresh token
9    async fn exchange_authorization_code(
10        &self,
11        authorization_code: &str,
12        redirect_uri: &str,
13    ) -> Result<OAuth2TokenResponse, LinkyApiError>;
14
15    /// Refresh OAuth2 access token using refresh token
16    async fn refresh_access_token(
17        &self,
18        refresh_token: &str,
19    ) -> Result<OAuth2TokenResponse, LinkyApiError>;
20
21    /// Get daily electricity consumption (30-minute granularity)
22    /// Returns consumption data for each day in the range
23    async fn get_daily_consumption(
24        &self,
25        prm: &str,
26        access_token: &str,
27        start_date: DateTime<Utc>,
28        end_date: DateTime<Utc>,
29    ) -> Result<Vec<ConsumptionDataPoint>, LinkyApiError>;
30
31    /// Get monthly electricity consumption (aggregated by month)
32    async fn get_monthly_consumption(
33        &self,
34        prm: &str,
35        access_token: &str,
36        start_date: DateTime<Utc>,
37        end_date: DateTime<Utc>,
38    ) -> Result<Vec<ConsumptionDataPoint>, LinkyApiError>;
39
40    /// Get consumption load curve (granularity: 30 minutes)
41    /// High-frequency data for detailed analysis
42    async fn get_consumption_load_curve(
43        &self,
44        prm: &str,
45        access_token: &str,
46        start_date: DateTime<Utc>,
47        end_date: DateTime<Utc>,
48    ) -> Result<Vec<ConsumptionDataPoint>, LinkyApiError>;
49
50    /// Get maximum power draw (kW) over a period
51    async fn get_max_power(
52        &self,
53        prm: &str,
54        access_token: &str,
55        start_date: DateTime<Utc>,
56        end_date: DateTime<Utc>,
57    ) -> Result<Vec<PowerDataPoint>, LinkyApiError>;
58}
59
60/// OAuth2 token response
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct OAuth2TokenResponse {
63    pub access_token: String,
64    pub refresh_token: Option<String>,
65    pub expires_in: i64,    // Seconds
66    pub token_type: String, // Usually "Bearer"
67}
68
69/// Consumption data point (single reading)
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ConsumptionDataPoint {
72    pub timestamp: DateTime<Utc>,
73    pub value: f64,              // kWh
74    pub quality: Option<String>, // Data quality indicator (e.g., "good", "estimated")
75}
76
77/// Power data point (instantaneous power draw)
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct PowerDataPoint {
80    pub timestamp: DateTime<Utc>,
81    pub value: f64,                // kW
82    pub direction: Option<String>, // "consumption" or "production" (for solar panels)
83}
84
85/// Linky API errors
86#[derive(Debug, thiserror::Error)]
87pub enum LinkyApiError {
88    #[error("HTTP error: {0}")]
89    HttpError(String),
90
91    #[error("Authentication failed: {0}")]
92    AuthenticationFailed(String),
93
94    #[error("Invalid authorization code: {0}")]
95    InvalidAuthorizationCode(String),
96
97    #[error("Token expired or invalid: {0}")]
98    TokenExpired(String),
99
100    #[error("Invalid PRM: {0}")]
101    InvalidPRM(String),
102
103    #[error("Rate limit exceeded: {0}")]
104    RateLimitExceeded(String),
105
106    #[error("Invalid date range: {0}")]
107    InvalidDateRange(String),
108
109    #[error("No data available for period: {0}")]
110    NoDataAvailable(String),
111
112    #[error("API timeout: {0}")]
113    Timeout(String),
114
115    #[error("Internal API error: {0}")]
116    InternalError(String),
117
118    #[error("Deserialization error: {0}")]
119    DeserializationError(String),
120}