leneda-client 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- leneda_client-0.1.0/MANIFEST.in +6 -0
- leneda_client-0.1.0/PKG-INFO +58 -0
- leneda_client-0.1.0/README.md +21 -0
- leneda_client-0.1.0/examples/advanced_usage.py +543 -0
- leneda_client-0.1.0/examples/basic_usage.py +164 -0
- leneda_client-0.1.0/pyproject.toml +27 -0
- leneda_client-0.1.0/requirements.txt +2 -0
- leneda_client-0.1.0/setup.cfg +4 -0
- leneda_client-0.1.0/setup.py +58 -0
- leneda_client-0.1.0/src/leneda/__init__.py +36 -0
- leneda_client-0.1.0/src/leneda/client.py +226 -0
- leneda_client-0.1.0/src/leneda/models.py +235 -0
- leneda_client-0.1.0/src/leneda/obis_codes.py +232 -0
- leneda_client-0.1.0/src/leneda/version.py +3 -0
- leneda_client-0.1.0/src/leneda_client.egg-info/PKG-INFO +58 -0
- leneda_client-0.1.0/src/leneda_client.egg-info/SOURCES.txt +19 -0
- leneda_client-0.1.0/src/leneda_client.egg-info/dependency_links.txt +1 -0
- leneda_client-0.1.0/src/leneda_client.egg-info/requires.txt +2 -0
- leneda_client-0.1.0/src/leneda_client.egg-info/top_level.txt +1 -0
- leneda_client-0.1.0/tests/__init__.py +1 -0
- leneda_client-0.1.0/tests/test_client.py +226 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: leneda-client
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: Python client for the Leneda energy data platform
|
5
|
+
Home-page: https://github.com/fedus/leneda-client
|
6
|
+
Author: fedus
|
7
|
+
Author-email: fedus@dillendapp.eu
|
8
|
+
Project-URL: Bug Reports, https://github.com/yourusername/leneda-client/issues
|
9
|
+
Project-URL: Source, https://github.com/yourusername/leneda-client
|
10
|
+
Project-URL: Documentation, https://github.com/yourusername/leneda-client#readme
|
11
|
+
Keywords: leneda,energy,api,client
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
13
|
+
Classifier: Intended Audience :: Developers
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
21
|
+
Classifier: Topic :: Utilities
|
22
|
+
Requires-Python: >=3.8
|
23
|
+
Description-Content-Type: text/markdown
|
24
|
+
Requires-Dist: requests>=2.25.0
|
25
|
+
Requires-Dist: python-dateutil>=2.8.2
|
26
|
+
Dynamic: author
|
27
|
+
Dynamic: author-email
|
28
|
+
Dynamic: classifier
|
29
|
+
Dynamic: description
|
30
|
+
Dynamic: description-content-type
|
31
|
+
Dynamic: home-page
|
32
|
+
Dynamic: keywords
|
33
|
+
Dynamic: project-url
|
34
|
+
Dynamic: requires-dist
|
35
|
+
Dynamic: requires-python
|
36
|
+
Dynamic: summary
|
37
|
+
|
38
|
+
# Leneda API Client
|
39
|
+
|
40
|
+
[![PyPI version]](https://pypi.org/project/leneda-client/)
|
41
|
+
[![Python versions]](https://pypi.org/project/leneda-client/)
|
42
|
+
[![License]](https://github.com/fedus/leneda-client/blob/main/LICENSE)
|
43
|
+
|
44
|
+
A Python client for interacting with the Leneda energy data platform API.
|
45
|
+
|
46
|
+
## Overview
|
47
|
+
|
48
|
+
This client provides a simple interface to the Leneda API, which allows users to:
|
49
|
+
|
50
|
+
- Retrieve metering data for specific time ranges
|
51
|
+
- Get aggregated metering data (hourly, daily, weekly, monthly, or total)
|
52
|
+
- Create metering data access requests
|
53
|
+
- Use predefined OBIS code constants for easy reference
|
54
|
+
|
55
|
+
## Installation
|
56
|
+
|
57
|
+
```bash
|
58
|
+
pip install leneda-client
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Leneda API Client
|
2
|
+
|
3
|
+
[![PyPI version]](https://pypi.org/project/leneda-client/)
|
4
|
+
[![Python versions]](https://pypi.org/project/leneda-client/)
|
5
|
+
[![License]](https://github.com/fedus/leneda-client/blob/main/LICENSE)
|
6
|
+
|
7
|
+
A Python client for interacting with the Leneda energy data platform API.
|
8
|
+
|
9
|
+
## Overview
|
10
|
+
|
11
|
+
This client provides a simple interface to the Leneda API, which allows users to:
|
12
|
+
|
13
|
+
- Retrieve metering data for specific time ranges
|
14
|
+
- Get aggregated metering data (hourly, daily, weekly, monthly, or total)
|
15
|
+
- Create metering data access requests
|
16
|
+
- Use predefined OBIS code constants for easy reference
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
```bash
|
21
|
+
pip install leneda-client
|
@@ -0,0 +1,543 @@
|
|
1
|
+
"""
|
2
|
+
Advanced usage examples for the Leneda API client.
|
3
|
+
|
4
|
+
This script demonstrates more complex use cases of the Leneda API client,
|
5
|
+
including data analysis, visualization, and error handling.
|
6
|
+
It accepts API credentials via command-line arguments or environment variables.
|
7
|
+
|
8
|
+
Environment variables:
|
9
|
+
LENEDA_API_KEY: Your Leneda API key
|
10
|
+
LENEDA_ENERGY_ID: Your Energy ID
|
11
|
+
|
12
|
+
Usage:
|
13
|
+
python advanced_usage.py --api-key YOUR_API_KEY --energy-id YOUR_ENERGY_ID
|
14
|
+
python advanced_usage.py --metering-point LU-METERING_POINT1 --example 2
|
15
|
+
"""
|
16
|
+
|
17
|
+
import argparse
|
18
|
+
import logging
|
19
|
+
import os
|
20
|
+
import sys
|
21
|
+
from datetime import datetime, timedelta
|
22
|
+
from typing import Optional
|
23
|
+
|
24
|
+
import matplotlib.pyplot as plt
|
25
|
+
import pandas as pd
|
26
|
+
|
27
|
+
from leneda import (
|
28
|
+
AggregatedMeteringData,
|
29
|
+
ElectricityConsumption,
|
30
|
+
LenedaClient,
|
31
|
+
MeteringData,
|
32
|
+
)
|
33
|
+
|
34
|
+
# Set up logging
|
35
|
+
logging.basicConfig(
|
36
|
+
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
37
|
+
)
|
38
|
+
logger = logging.getLogger("leneda_advanced_example")
|
39
|
+
|
40
|
+
|
41
|
+
def parse_arguments():
|
42
|
+
"""Parse command-line arguments."""
|
43
|
+
parser = argparse.ArgumentParser(description="Leneda API Client Advanced Usage Example")
|
44
|
+
|
45
|
+
# API credentials
|
46
|
+
parser.add_argument(
|
47
|
+
"--api-key",
|
48
|
+
help="Your Leneda API key (or set LENEDA_API_KEY environment variable)",
|
49
|
+
)
|
50
|
+
parser.add_argument(
|
51
|
+
"--energy-id",
|
52
|
+
help="Your Energy ID (or set LENEDA_ENERGY_ID environment variable)",
|
53
|
+
)
|
54
|
+
|
55
|
+
# Other parameters
|
56
|
+
parser.add_argument(
|
57
|
+
"--metering-point",
|
58
|
+
default="LU-METERING_POINT1",
|
59
|
+
help="Metering point code (default: LU-METERING_POINT1)",
|
60
|
+
)
|
61
|
+
parser.add_argument(
|
62
|
+
"--example",
|
63
|
+
type=int,
|
64
|
+
choices=[1, 2, 3, 4],
|
65
|
+
default=0,
|
66
|
+
help="Run a specific example (1-4) or all if not specified",
|
67
|
+
)
|
68
|
+
parser.add_argument(
|
69
|
+
"--days",
|
70
|
+
type=int,
|
71
|
+
default=7,
|
72
|
+
help="Number of days to retrieve data for (default: 7)",
|
73
|
+
)
|
74
|
+
parser.add_argument(
|
75
|
+
"--year",
|
76
|
+
type=int,
|
77
|
+
default=datetime.now().year,
|
78
|
+
help=f"Year for analysis (default: {datetime.now().year})",
|
79
|
+
)
|
80
|
+
parser.add_argument(
|
81
|
+
"--threshold",
|
82
|
+
type=float,
|
83
|
+
default=50.0,
|
84
|
+
help="Anomaly detection threshold percentage (default: 50.0)",
|
85
|
+
)
|
86
|
+
parser.add_argument(
|
87
|
+
"--save-plots",
|
88
|
+
action="store_true",
|
89
|
+
help="Save plots to files instead of displaying them",
|
90
|
+
)
|
91
|
+
parser.add_argument(
|
92
|
+
"--output-dir",
|
93
|
+
default="./plots",
|
94
|
+
help="Directory to save plots (default: ./plots)",
|
95
|
+
)
|
96
|
+
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
97
|
+
|
98
|
+
return parser.parse_args()
|
99
|
+
|
100
|
+
|
101
|
+
def get_credentials(args):
|
102
|
+
"""Get API credentials from arguments or environment variables."""
|
103
|
+
api_key = args.api_key or os.environ.get("LENEDA_API_KEY")
|
104
|
+
energy_id = args.energy_id or os.environ.get("LENEDA_ENERGY_ID")
|
105
|
+
|
106
|
+
if not api_key:
|
107
|
+
logger.error(
|
108
|
+
"API key not provided. Use --api-key or set LENEDA_API_KEY environment variable."
|
109
|
+
)
|
110
|
+
sys.exit(1)
|
111
|
+
|
112
|
+
if not energy_id:
|
113
|
+
logger.error(
|
114
|
+
"Energy ID not provided. Use --energy-id or set LENEDA_ENERGY_ID environment variable."
|
115
|
+
)
|
116
|
+
sys.exit(1)
|
117
|
+
|
118
|
+
return api_key, energy_id
|
119
|
+
|
120
|
+
|
121
|
+
def convert_to_dataframe(metering_data: MeteringData) -> pd.DataFrame:
|
122
|
+
"""Convert MeteringData to a pandas DataFrame for analysis."""
|
123
|
+
data = [
|
124
|
+
{
|
125
|
+
"timestamp": item.started_at,
|
126
|
+
"value": item.value,
|
127
|
+
"unit": metering_data.unit,
|
128
|
+
"metering_point": metering_data.metering_point_code,
|
129
|
+
"obis_code": metering_data.obis_code,
|
130
|
+
"type": item.type,
|
131
|
+
"version": item.version,
|
132
|
+
"calculated": item.calculated,
|
133
|
+
}
|
134
|
+
for item in metering_data.items
|
135
|
+
]
|
136
|
+
|
137
|
+
df = pd.DataFrame(data)
|
138
|
+
df.set_index("timestamp", inplace=True)
|
139
|
+
return df
|
140
|
+
|
141
|
+
|
142
|
+
def convert_aggregated_to_dataframe(
|
143
|
+
aggregated_data: AggregatedMeteringData,
|
144
|
+
) -> pd.DataFrame:
|
145
|
+
"""Convert AggregatedMeteringData to a pandas DataFrame for analysis."""
|
146
|
+
data = [
|
147
|
+
{
|
148
|
+
"start_date": item.started_at,
|
149
|
+
"end_date": item.ended_at,
|
150
|
+
"value": item.value,
|
151
|
+
"unit": aggregated_data.unit,
|
152
|
+
"calculated": item.calculated,
|
153
|
+
}
|
154
|
+
for item in aggregated_data.aggregated_time_series
|
155
|
+
]
|
156
|
+
|
157
|
+
df = pd.DataFrame(data)
|
158
|
+
df.set_index("start_date", inplace=True)
|
159
|
+
return df
|
160
|
+
|
161
|
+
|
162
|
+
def plot_consumption_data(df: pd.DataFrame, title: str, save_path: Optional[str] = None) -> None:
|
163
|
+
"""Plot consumption data from a DataFrame."""
|
164
|
+
plt.figure(figsize=(12, 6))
|
165
|
+
plt.plot(df.index, df["value"], marker="o", linestyle="-", markersize=4)
|
166
|
+
plt.title(title)
|
167
|
+
plt.xlabel("Time")
|
168
|
+
plt.ylabel(f"Consumption ({df['unit'].iloc[0]})")
|
169
|
+
plt.grid(True)
|
170
|
+
plt.tight_layout()
|
171
|
+
|
172
|
+
if save_path:
|
173
|
+
# Create directory if it doesn't exist
|
174
|
+
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
175
|
+
plt.savefig(save_path)
|
176
|
+
logger.info(f"Plot saved to {save_path}")
|
177
|
+
else:
|
178
|
+
plt.show()
|
179
|
+
|
180
|
+
plt.close()
|
181
|
+
|
182
|
+
|
183
|
+
def compare_consumption_periods(
|
184
|
+
client: LenedaClient,
|
185
|
+
metering_point: str,
|
186
|
+
period1_start: datetime,
|
187
|
+
period1_end: datetime,
|
188
|
+
period2_start: datetime,
|
189
|
+
period2_end: datetime,
|
190
|
+
save_path: Optional[str] = None,
|
191
|
+
) -> None:
|
192
|
+
"""Compare consumption data between two time periods."""
|
193
|
+
# Get data for period 1
|
194
|
+
period1_data = client.get_metering_data(
|
195
|
+
metering_point_code=metering_point,
|
196
|
+
obis_code=ElectricityConsumption.ACTIVE,
|
197
|
+
start_date_time=period1_start,
|
198
|
+
end_date_time=period1_end,
|
199
|
+
)
|
200
|
+
|
201
|
+
# Get data for period 2
|
202
|
+
period2_data = client.get_metering_data(
|
203
|
+
metering_point_code=metering_point,
|
204
|
+
obis_code=ElectricityConsumption.ACTIVE,
|
205
|
+
start_date_time=period2_start,
|
206
|
+
end_date_time=period2_end,
|
207
|
+
)
|
208
|
+
|
209
|
+
# Convert to DataFrames
|
210
|
+
df1 = convert_to_dataframe(period1_data)
|
211
|
+
df2 = convert_to_dataframe(period2_data)
|
212
|
+
|
213
|
+
# Resample to daily data for better comparison
|
214
|
+
daily1 = df1.resample("D").sum()
|
215
|
+
daily2 = df2.resample("D").sum()
|
216
|
+
|
217
|
+
# Calculate total consumption
|
218
|
+
total1 = df1["value"].sum()
|
219
|
+
total2 = df2["value"].sum()
|
220
|
+
|
221
|
+
# Calculate average consumption
|
222
|
+
avg1 = df1["value"].mean()
|
223
|
+
avg2 = df2["value"].mean()
|
224
|
+
|
225
|
+
# Calculate percentage difference
|
226
|
+
pct_diff = ((total2 - total1) / total1) * 100 if total1 > 0 else 0
|
227
|
+
|
228
|
+
# Print comparison
|
229
|
+
period1_str = f"{period1_start.strftime('%Y-%m-%d')} to {period1_end.strftime('%Y-%m-%d')}"
|
230
|
+
period2_str = f"{period2_start.strftime('%Y-%m-%d')} to {period2_end.strftime('%Y-%m-%d')}"
|
231
|
+
|
232
|
+
print("\nConsumption Comparison:")
|
233
|
+
print(f"Period 1 ({period1_str}):")
|
234
|
+
print(f" - Total: {total1:.2f} {df1['unit'].iloc[0]}")
|
235
|
+
print(f" - Average: {avg1:.2f} {df1['unit'].iloc[0]}")
|
236
|
+
|
237
|
+
print(f"\nPeriod 2 ({period2_str}):")
|
238
|
+
print(f" - Total: {total2:.2f} {df2['unit'].iloc[0]}")
|
239
|
+
print(f" - Average: {avg2:.2f} {df2['unit'].iloc[0]}")
|
240
|
+
|
241
|
+
print("\nComparison:")
|
242
|
+
print(f" - Absolute difference: {total2 - total1:.2f} {df1['unit'].iloc[0]}")
|
243
|
+
print(f" - Percentage difference: {pct_diff:.2f}%")
|
244
|
+
|
245
|
+
# Plot comparison
|
246
|
+
plt.figure(figsize=(12, 6))
|
247
|
+
|
248
|
+
plt.subplot(2, 1, 1)
|
249
|
+
plt.bar(range(len(daily1)), daily1["value"], label=f"Period 1 ({period1_str})")
|
250
|
+
plt.xticks(range(len(daily1)), [d.strftime("%a %d") for d in daily1.index], rotation=45)
|
251
|
+
plt.ylabel(f"Daily Consumption ({df1['unit'].iloc[0]})")
|
252
|
+
plt.legend()
|
253
|
+
plt.grid(True, alpha=0.3)
|
254
|
+
|
255
|
+
plt.subplot(2, 1, 2)
|
256
|
+
plt.bar(
|
257
|
+
range(len(daily2)),
|
258
|
+
daily2["value"],
|
259
|
+
label=f"Period 2 ({period2_str})",
|
260
|
+
color="orange",
|
261
|
+
)
|
262
|
+
plt.xticks(range(len(daily2)), [d.strftime("%a %d") for d in daily2.index], rotation=45)
|
263
|
+
plt.ylabel(f"Daily Consumption ({df2['unit'].iloc[0]})")
|
264
|
+
plt.legend()
|
265
|
+
plt.grid(True, alpha=0.3)
|
266
|
+
|
267
|
+
plt.tight_layout()
|
268
|
+
|
269
|
+
if save_path:
|
270
|
+
# Create directory if it doesn't exist
|
271
|
+
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
272
|
+
plt.savefig(save_path)
|
273
|
+
logger.info(f"Plot saved to {save_path}")
|
274
|
+
else:
|
275
|
+
plt.show()
|
276
|
+
|
277
|
+
plt.close()
|
278
|
+
|
279
|
+
|
280
|
+
def analyze_monthly_trends(
|
281
|
+
client: LenedaClient,
|
282
|
+
metering_point: str,
|
283
|
+
year: int,
|
284
|
+
save_path: Optional[str] = None,
|
285
|
+
) -> None:
|
286
|
+
"""Analyze monthly consumption trends for a specific year."""
|
287
|
+
# Get monthly aggregated data for the year
|
288
|
+
start_date = datetime(year, 1, 1)
|
289
|
+
end_date = datetime(year, 12, 31)
|
290
|
+
|
291
|
+
monthly_data = client.get_aggregated_metering_data(
|
292
|
+
metering_point_code=metering_point,
|
293
|
+
obis_code=ElectricityConsumption.ACTIVE,
|
294
|
+
start_date=start_date,
|
295
|
+
end_date=end_date,
|
296
|
+
aggregation_level="Month",
|
297
|
+
transformation_mode="Accumulation",
|
298
|
+
)
|
299
|
+
|
300
|
+
# Convert to DataFrame
|
301
|
+
df = convert_aggregated_to_dataframe(monthly_data)
|
302
|
+
|
303
|
+
# Print monthly consumption
|
304
|
+
print(f"\nMonthly Consumption for {year}:")
|
305
|
+
for idx, row in df.iterrows():
|
306
|
+
month_name = idx.strftime("%B")
|
307
|
+
print(f" - {month_name}: {row['value']:.2f} {row['unit']}")
|
308
|
+
|
309
|
+
# Calculate statistics
|
310
|
+
total = df["value"].sum()
|
311
|
+
average = df["value"].mean()
|
312
|
+
max_month = df["value"].idxmax().strftime("%B")
|
313
|
+
min_month = df["value"].idxmin().strftime("%B")
|
314
|
+
|
315
|
+
print("\nYearly Statistics:")
|
316
|
+
print(f" - Total consumption: {total:.2f} {df['unit'].iloc[0]}")
|
317
|
+
print(f" - Average monthly consumption: {average:.2f} {df['unit'].iloc[0]}")
|
318
|
+
print(
|
319
|
+
f" - Highest consumption month: {max_month} ({df['value'].max():.2f} {df['unit'].iloc[0]})"
|
320
|
+
)
|
321
|
+
print(
|
322
|
+
f" - Lowest consumption month: {min_month} ({df['value'].min():.2f} {df['unit'].iloc[0]})"
|
323
|
+
)
|
324
|
+
|
325
|
+
# Plot monthly consumption
|
326
|
+
plt.figure(figsize=(12, 6))
|
327
|
+
plt.bar(range(len(df)), df["value"], color="green")
|
328
|
+
plt.xticks(range(len(df)), [d.strftime("%b") for d in df.index], rotation=0)
|
329
|
+
plt.title(f"Monthly Electricity Consumption for {year}")
|
330
|
+
plt.xlabel("Month")
|
331
|
+
plt.ylabel(f"Consumption ({df['unit'].iloc[0]})")
|
332
|
+
plt.grid(True, axis="y", alpha=0.3)
|
333
|
+
plt.tight_layout()
|
334
|
+
|
335
|
+
if save_path:
|
336
|
+
# Create directory if it doesn't exist
|
337
|
+
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
338
|
+
plt.savefig(save_path)
|
339
|
+
logger.info(f"Plot saved to {save_path}")
|
340
|
+
else:
|
341
|
+
plt.show()
|
342
|
+
|
343
|
+
plt.close()
|
344
|
+
|
345
|
+
|
346
|
+
def detect_consumption_anomalies(
|
347
|
+
client: LenedaClient,
|
348
|
+
metering_point: str,
|
349
|
+
start_date: datetime,
|
350
|
+
end_date: datetime,
|
351
|
+
threshold_pct: float = 50.0,
|
352
|
+
save_path: Optional[str] = None,
|
353
|
+
) -> None:
|
354
|
+
"""Detect anomalies in consumption data based on percentage deviation from the mean."""
|
355
|
+
# Get hourly consumption data
|
356
|
+
consumption_data = client.get_metering_data(
|
357
|
+
metering_point_code=metering_point,
|
358
|
+
obis_code=ElectricityConsumption.ACTIVE,
|
359
|
+
start_date_time=start_date,
|
360
|
+
end_date_time=end_date,
|
361
|
+
)
|
362
|
+
|
363
|
+
# Convert to DataFrame
|
364
|
+
df = convert_to_dataframe(consumption_data)
|
365
|
+
|
366
|
+
# Calculate statistics
|
367
|
+
mean = df["value"].mean()
|
368
|
+
std = df["value"].std()
|
369
|
+
threshold = mean * (threshold_pct / 100)
|
370
|
+
|
371
|
+
# Detect anomalies
|
372
|
+
anomalies = df[abs(df["value"] - mean) > threshold].copy()
|
373
|
+
|
374
|
+
print("\nAnomaly Detection:")
|
375
|
+
print(f" - Period: {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}")
|
376
|
+
print(f" - Average consumption: {mean:.2f} {df['unit'].iloc[0]}")
|
377
|
+
print(f" - Standard deviation: {std:.2f} {df['unit'].iloc[0]}")
|
378
|
+
print(
|
379
|
+
f" - Threshold for anomaly: {threshold:.2f} {df['unit'].iloc[0]} ({threshold_pct}% of mean)"
|
380
|
+
)
|
381
|
+
print(f" - Number of anomalies detected: {len(anomalies)}")
|
382
|
+
|
383
|
+
if not anomalies.empty:
|
384
|
+
print("\nTop 5 Anomalies:")
|
385
|
+
# Sort by absolute deviation from mean
|
386
|
+
anomalies["deviation"] = abs(anomalies["value"] - mean)
|
387
|
+
anomalies = anomalies.sort_values("deviation", ascending=False)
|
388
|
+
|
389
|
+
for idx, row in anomalies.head(5).iterrows():
|
390
|
+
deviation_pct = (row["deviation"] / mean) * 100
|
391
|
+
print(
|
392
|
+
f" - {idx.strftime('%Y-%m-%d %H:%M')}: {row['value']:.2f} {row['unit']} "
|
393
|
+
f"(Deviation: {deviation_pct:.2f}%)"
|
394
|
+
)
|
395
|
+
|
396
|
+
# Plot the data with anomalies highlighted
|
397
|
+
plt.figure(figsize=(12, 6))
|
398
|
+
plt.plot(
|
399
|
+
df.index,
|
400
|
+
df["value"],
|
401
|
+
marker=".",
|
402
|
+
linestyle="-",
|
403
|
+
markersize=2,
|
404
|
+
label="Normal",
|
405
|
+
)
|
406
|
+
plt.scatter(anomalies.index, anomalies["value"], color="red", s=50, label="Anomaly")
|
407
|
+
plt.axhline(y=mean, color="green", linestyle="--", label=f"Mean ({mean:.2f})")
|
408
|
+
plt.axhline(
|
409
|
+
y=mean + threshold,
|
410
|
+
color="orange",
|
411
|
+
linestyle="--",
|
412
|
+
label=f"Upper Threshold ({mean + threshold:.2f})",
|
413
|
+
)
|
414
|
+
plt.axhline(
|
415
|
+
y=mean - threshold,
|
416
|
+
color="orange",
|
417
|
+
linestyle="--",
|
418
|
+
label=f"Lower Threshold ({mean - threshold:.2f})",
|
419
|
+
)
|
420
|
+
|
421
|
+
plt.title("Electricity Consumption with Anomalies")
|
422
|
+
plt.xlabel("Time")
|
423
|
+
plt.ylabel(f"Consumption ({df['unit'].iloc[0]})")
|
424
|
+
plt.legend()
|
425
|
+
plt.grid(True, alpha=0.3)
|
426
|
+
plt.tight_layout()
|
427
|
+
|
428
|
+
if save_path:
|
429
|
+
# Create directory if it doesn't exist
|
430
|
+
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
431
|
+
plt.savefig(save_path)
|
432
|
+
logger.info(f"Plot saved to {save_path}")
|
433
|
+
else:
|
434
|
+
plt.show()
|
435
|
+
|
436
|
+
plt.close()
|
437
|
+
|
438
|
+
|
439
|
+
def main():
|
440
|
+
# Parse command-line arguments
|
441
|
+
args = parse_arguments()
|
442
|
+
|
443
|
+
# Set up debug logging if requested
|
444
|
+
if args.debug:
|
445
|
+
logging.getLogger("leneda").setLevel(logging.DEBUG)
|
446
|
+
logger.setLevel(logging.DEBUG)
|
447
|
+
logger.debug("Debug logging enabled")
|
448
|
+
|
449
|
+
# Get API credentials
|
450
|
+
api_key, energy_id = get_credentials(args)
|
451
|
+
|
452
|
+
# Get other parameters
|
453
|
+
metering_point = args.metering_point
|
454
|
+
example_num = args.example
|
455
|
+
days = args.days
|
456
|
+
year = args.year
|
457
|
+
threshold = args.threshold
|
458
|
+
save_plots = args.save_plots
|
459
|
+
output_dir = args.output_dir
|
460
|
+
|
461
|
+
# Initialize the client
|
462
|
+
client = LenedaClient(api_key, energy_id, debug=args.debug)
|
463
|
+
|
464
|
+
try:
|
465
|
+
# Run all examples or a specific one based on the command-line argument
|
466
|
+
if example_num == 0 or example_num == 1:
|
467
|
+
# Example 1: Get and visualize hourly electricity consumption for the last week
|
468
|
+
end_date = datetime.now()
|
469
|
+
start_date = end_date - timedelta(days=days)
|
470
|
+
|
471
|
+
print(
|
472
|
+
f"\nExample 1: Visualizing hourly electricity consumption for the last {days} days"
|
473
|
+
)
|
474
|
+
consumption_data = client.get_metering_data(
|
475
|
+
metering_point_code=metering_point,
|
476
|
+
obis_code=ElectricityConsumption.ACTIVE,
|
477
|
+
start_date_time=start_date,
|
478
|
+
end_date_time=end_date,
|
479
|
+
)
|
480
|
+
|
481
|
+
# Convert to DataFrame and plot
|
482
|
+
df = convert_to_dataframe(consumption_data)
|
483
|
+
plot_consumption_data(
|
484
|
+
df,
|
485
|
+
f"Hourly Electricity Consumption ({start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')})",
|
486
|
+
save_path=(
|
487
|
+
os.path.join(output_dir, "hourly_consumption.png") if save_plots else None
|
488
|
+
),
|
489
|
+
)
|
490
|
+
|
491
|
+
if example_num == 0 or example_num == 2:
|
492
|
+
# Example 2: Compare consumption between two weeks
|
493
|
+
current_week_end = datetime.now()
|
494
|
+
current_week_start = current_week_end - timedelta(days=7)
|
495
|
+
previous_week_end = current_week_start
|
496
|
+
previous_week_start = previous_week_end - timedelta(days=7)
|
497
|
+
|
498
|
+
print("\nExample 2: Comparing consumption between two weeks")
|
499
|
+
compare_consumption_periods(
|
500
|
+
client,
|
501
|
+
metering_point,
|
502
|
+
previous_week_start,
|
503
|
+
previous_week_end,
|
504
|
+
current_week_start,
|
505
|
+
current_week_end,
|
506
|
+
save_path=(
|
507
|
+
os.path.join(output_dir, "weekly_comparison.png") if save_plots else None
|
508
|
+
),
|
509
|
+
)
|
510
|
+
|
511
|
+
if example_num == 0 or example_num == 3:
|
512
|
+
# Example 3: Analyze monthly trends for the specified year
|
513
|
+
print(f"\nExample 3: Analyzing monthly trends for {year}")
|
514
|
+
analyze_monthly_trends(
|
515
|
+
client,
|
516
|
+
metering_point,
|
517
|
+
year,
|
518
|
+
save_path=(
|
519
|
+
os.path.join(output_dir, f"monthly_trends_{year}.png") if save_plots else None
|
520
|
+
),
|
521
|
+
)
|
522
|
+
|
523
|
+
if example_num == 0 or example_num == 4:
|
524
|
+
# Example 4: Detect consumption anomalies for the last 30 days
|
525
|
+
end_date = datetime.now()
|
526
|
+
start_date = end_date - timedelta(days=30)
|
527
|
+
|
528
|
+
print("\nExample 4: Detecting consumption anomalies for the last 30 days")
|
529
|
+
detect_consumption_anomalies(
|
530
|
+
client,
|
531
|
+
metering_point,
|
532
|
+
start_date,
|
533
|
+
end_date,
|
534
|
+
threshold_pct=threshold,
|
535
|
+
save_path=(os.path.join(output_dir, "anomalies.png") if save_plots else None),
|
536
|
+
)
|
537
|
+
|
538
|
+
except Exception as e:
|
539
|
+
logger.error(f"Error: {e}", exc_info=True)
|
540
|
+
|
541
|
+
|
542
|
+
if __name__ == "__main__":
|
543
|
+
main()
|