Passed
Push — main ( 63789e...7948ea )
by Guy
01:23
created

ims_envista.meteo_data   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 254
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 186
dl 0
loc 254
rs 10
c 0
b 0
f 0
wmc 16

3 Functions

Rating   Name   Duplication   Size   Complexity  
C meteo_data_from_json() 0 68 8
A station_meteo_data_from_json() 0 7 2
A _fix_datetime_offset() 0 18 2

4 Methods

Rating   Name   Duplication   Size   Complexity  
A MeteorologicalData.__repr__() 0 2 1
A MeteorologicalData.__str__() 0 2 1
A MeteorologicalData._pretty_print() 0 35 1
A StationMeteorologicalReadings.__repr__() 0 3 1
1
"""Data Class for IMS Meteorological Readings."""
2
3
from __future__ import annotations
4
5
import datetime
6
import textwrap
7
import time
8
from dataclasses import dataclass, field
9
10
import pytz
11
12
from .const import (
13
    API_BP,
14
    API_CHANNELS,
15
    API_DATA,
16
    API_DATETIME,
17
    API_DIFF,
18
    API_GRAD,
19
    API_NAME,
20
    API_NIP,
21
    API_RAIN,
22
    API_RAIN_1_MIN,
23
    API_RH,
24
    API_STATION_ID,
25
    API_STATUS,
26
    API_STD_WD,
27
    API_TD,
28
    API_TD_MAX,
29
    API_TD_MIN,
30
    API_TG,
31
    API_TIME,
32
    API_TW,
33
    API_VALID,
34
    API_VALUE,
35
    API_WD,
36
    API_WD_MAX,
37
    API_WS,
38
    API_WS_1MM,
39
    API_WS_10MM,
40
    API_WS_MAX,
41
    VARIABLES,
42
)
43
44
MAX_HOUR_INT = 24
45
46
@dataclass
47
class MeteorologicalData:
48
    """Meteorological Data."""
49
50
    station_id: int
51
    """Station ID"""
52
    datetime: datetime.datetime
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable datetime does not seem to be defined.
Loading history...
53
    """Date and time of the data"""
54
    rain: float
55
    """Rainfall in mm"""
56
    ws: float
57
    """Wind speed in m/s"""
58
    ws_max: float
59
    """Gust wind speed in m/s"""
60
    wd: float
61
    """Wind direction in deg"""
62
    wd_max: float
63
    """Gust wind direction in deg"""
64
    std_wd: float
65
    """Standard deviation wind direction in deg"""
66
    td: float
67
    """Temperature in °C"""
68
    td_max: float
69
    """Maximum Temperature in °C"""
70
    td_min: float
71
    """Minimum Temperature in °C"""
72
    tg: float
73
    """Ground Temperature in °C"""
74
    tw: float
75
    """TW Temperature (?) in °C"""
76
    rh: float
77
    """Relative humidity in %"""
78
    ws_1mm: float
79
    """Maximum 1 minute wind speed in m/s"""
80
    ws_10mm: float
81
    """Maximum 10 minute wind speed in m/s"""
82
    time: datetime.time
83
    """Time"""
84
    bp: float
85
    """Maximum barometric pressure in mb"""
86
    diff_r: float
87
    """Distributed radiation in w/m^2"""
88
    grad: float
89
    """Global radiation in w/m^2"""
90
    nip: float
91
    """Direct radiation in w/m^2"""
92
    rain_1_min: float
93
    """Rainfall per minute in mm"""
94
95
    def _pretty_print(self) -> str:
96
        return textwrap.dedent(
97
            """Station: {}, Date: {}, Readings: [(TD: {}{}), (TDmax: {}{}), (TDmin: {}{}), (TG: {}{}), (RH: {}{}), (Rain: {}{}), (WS: {}{}), (WSmax: {}{}), (WD: {}{}), (WDmax: {}{}),  (STDwd: {}{}), (WS1mm: {}{}), (WS10mm: {}{}), (Time: {}{})]
98
            """
99
        ).format(
100
            self.station_id,
101
            self.datetime,
102
            self.td,
103
            VARIABLES[API_TD].unit,
104
            self.td_max,
105
            VARIABLES[API_TD_MAX].unit,
106
            self.td_min,
107
            VARIABLES[API_TD_MIN].unit,
108
            self.tg,
109
            VARIABLES[API_TG].unit,
110
            self.rh,
111
            VARIABLES[API_RH].unit,
112
            self.rain,
113
            VARIABLES[API_RAIN].unit,
114
            self.ws,
115
            VARIABLES[API_WS].unit,
116
            self.ws_max,
117
            VARIABLES[API_WS_MAX].unit,
118
            self.wd,
119
            VARIABLES[API_WD].unit,
120
            self.wd_max,
121
            VARIABLES[API_WD_MAX].unit,
122
            self.std_wd,
123
            VARIABLES[API_STD_WD].unit,
124
            self.ws_1mm,
125
            VARIABLES[API_WS_1MM].unit,
126
            self.ws_10mm,
127
            VARIABLES[API_WS_10MM].unit,
128
            self.time,
129
            VARIABLES[API_TIME].unit,
130
        )
131
132
    def __str__(self) -> str:
133
        return self._pretty_print()
134
135
    def __repr__(self) -> str:
136
        return self._pretty_print().replace("\n", " ")
137
138
139
@dataclass
140
class StationMeteorologicalReadings:
141
    """Station Meteorological Readings."""
142
143
    station_id: int
144
    """ Station Id"""
145
    data: list[MeteorologicalData] = field(default_factory=list)
146
    """ List of Meteorological Data """
147
148
    def __repr__(self) -> str:
149
        return textwrap.dedent("""Station ({}), Data: {}""").format(
150
            self.station_id, self.data
151
        )
152
153
tz = pytz.timezone("Asia/Jerusalem")
154
155
156
def _fix_datetime_offset(dt: datetime.datetime) -> tuple[datetime.datetime, bool]:
157
    dt = dt.replace(tzinfo=None)
158
    dt = tz.localize(dt)
159
160
    # Get the UTC offset in seconds
161
    offset_seconds = dt.utcoffset().total_seconds()
162
163
    # Create a fixed timezone with the same offset and name
164
    fixed_timezone = datetime.timezone(datetime.timedelta(seconds=offset_seconds), dt.tzname())
165
166
    # Replace the pytz tzinfo with the fixed timezone
167
    dt = dt.replace(tzinfo=fixed_timezone)
168
169
    is_dst = dt.dst() != datetime.timedelta(0)
170
    if is_dst:
171
        dt = dt + datetime.timedelta(hours=1)
172
173
    return dt,is_dst
174
175
176
def meteo_data_from_json(station_id: int, data: dict) -> MeteorologicalData:
177
    """Create a MeteorologicalData object from a JSON object."""
178
    dt = datetime.datetime.fromisoformat(data[API_DATETIME])
179
    dt, is_dst = _fix_datetime_offset(dt)
180
181
    channel_value_dict = {}
182
    for channel_value in data[API_CHANNELS]:
183
        if channel_value[API_VALID] is True and channel_value[API_STATUS] == 1:
184
            channel_value_dict[channel_value[API_NAME]] = float(
185
                channel_value[API_VALUE]
186
            )
187
188
    rain = channel_value_dict.get(API_RAIN)
189
    ws_max = channel_value_dict.get(API_WS_MAX)
190
    wd_max = channel_value_dict.get(API_WD_MAX)
191
    ws = channel_value_dict.get(API_WS)
192
    wd = channel_value_dict.get(API_WD)
193
    std_wd = channel_value_dict.get(API_STD_WD)
194
    td = channel_value_dict.get(API_TD)
195
    rh = channel_value_dict.get(API_RH)
196
    td_max = channel_value_dict.get(API_TD_MAX)
197
    td_min = channel_value_dict.get(API_TD_MIN)
198
    ws_1mm = channel_value_dict.get(API_WS_1MM)
199
    ws_10mm = channel_value_dict.get(API_WS_10MM)
200
    tg = channel_value_dict.get(API_TG)
201
    tw = channel_value_dict.get(API_TW)
202
    time_val = channel_value_dict.get(API_TIME)
203
    if time_val:
204
        time_int = int(time_val)
205
        if time_int <= MAX_HOUR_INT:
206
            t = time.strptime(str(time_int), "%H")
207
        else :
208
            t = time.strptime(str(time_int), "%H%M")
209
        time_val = datetime.time(t.tm_hour, t.tm_min, tzinfo=tz)
210
    bp = channel_value_dict.get(API_BP)
211
    diff_r = channel_value_dict.get(API_DIFF)
212
    grad = channel_value_dict.get(API_GRAD)
213
    nip = channel_value_dict.get(API_NIP)
214
    rain_1_min = channel_value_dict.get(API_RAIN_1_MIN)
215
216
    if is_dst and time_val:
217
        # Strange IMS logic :o
218
        dt = dt + datetime.timedelta(hours=1)
219
        time_val = time_val.replace(hour=(time_val.hour+1)%24)
220
221
    return MeteorologicalData(
222
        station_id=station_id,
223
        datetime=dt,
224
        rain=rain,
225
        ws=ws,
226
        ws_max=ws_max,
227
        wd=wd,
228
        wd_max=wd_max,
229
        std_wd=std_wd,
230
        td=td,
231
        td_max=td_max,
232
        td_min=td_min,
233
        tg=tg,
234
        tw=tw,
235
        rh=rh,
236
        ws_1mm=ws_1mm,
237
        ws_10mm=ws_10mm,
238
        time=time_val,
239
        bp=bp,
240
        diff_r=diff_r,
241
        grad=grad,
242
        nip=nip,
243
        rain_1_min=rain_1_min
244
    )
245
246
247
def station_meteo_data_from_json(json: dict) -> StationMeteorologicalReadings | None:
248
    station_id = int(json[API_STATION_ID])
249
    data = json.get(API_DATA)
250
    if not data:
251
        return None
252
    meteo_data = [meteo_data_from_json(station_id, single_meteo_data) for single_meteo_data in data]
253
    return StationMeteorologicalReadings(station_id, meteo_data)
254