Skip to content

Commit c6956dd

Browse files
Merge branch 'main' into HAN-98
2 parents 4f811b8 + b9315c8 commit c6956dd

File tree

6 files changed

+172
-59
lines changed

6 files changed

+172
-59
lines changed

app/database/database.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from sqlalchemy import create_engine, select, delete, column
2+
from sqlalchemy.orm import Session
3+
from dotenv import load_dotenv
4+
from os import environ
5+
from typing import Optional, Sequence, Union
6+
7+
from database.models.device_plant import DevicePlant
8+
from database.models.measurement import Measurement
9+
10+
load_dotenv()
11+
12+
13+
class SQLAlchemyClient():
14+
db_url = environ.get("DATABASE_URL").replace("postgres://", "postgresql://", 1)
15+
16+
engine = create_engine(db_url)
17+
18+
def __init__(self):
19+
self.conn = self.engine.connect()
20+
self.session = Session(self.engine)
21+
22+
def shutdown(self):
23+
self.conn.close()
24+
self.session.close()
25+
26+
def rollback(self):
27+
self.session.rollback()
28+
29+
def clean_table(self, table: Union[DevicePlant, Measurement]):
30+
query = delete(table) # type: ignore
31+
self.session.execute(query)
32+
self.session.commit()
33+
34+
def add(self, record: Union[DevicePlant, Measurement]):
35+
self.session.add(record)
36+
self.session.commit()
37+
38+
def find_by_device_id(self, id_device: str) -> DevicePlant:
39+
query = select(DevicePlant).where(DevicePlant.id_device == id_device)
40+
result = self.session.scalars(query).one()
41+
return result
42+
43+
def find_by_plant_id(self, id_plant: str) -> DevicePlant:
44+
query = select(DevicePlant).where(DevicePlant.id_plant == id_plant)
45+
result = self.session.scalars(query).one()
46+
return result
47+
48+
def find_all(self, limit: int) -> Sequence[DevicePlant]:
49+
query = select(DevicePlant).limit(limit)
50+
result = self.session.scalars(query).all()
51+
return result
52+
53+
def update_device_plant(self,
54+
id_device: str,
55+
id_plant: Optional[int],
56+
plant_type: Optional[str],
57+
id_user: Optional[int]):
58+
59+
query = select(DevicePlant).where(DevicePlant.id_device == id_device)
60+
61+
device_plant = self.session.scalars(query).one()
62+
if id_plant:
63+
device_plant.id_plant = id_plant
64+
if plant_type:
65+
device_plant.plant_type = plant_type
66+
if id_user:
67+
device_plant.id_user = id_user
68+
self.session.commit()
69+
70+
def get_last_measurement(self, id_plant: int) -> Measurement:
71+
query = select(Measurement).where(
72+
Measurement.id_plant == id_plant
73+
).order_by(Measurement.id.desc()).limit(1)
74+
result: Measurement = self.session.scalars(query).one()
75+
return result
76+
77+
def delete_by_field(self, field: str, value: str) -> int:
78+
query = delete(DevicePlant).where(column(field) == value)
79+
result = self.session.execute(query)
80+
self.session.commit()
81+
return result.rowcount

app/resources/__init__.py

Whitespace-only changes.

app/resources/parser.py

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
from typing import Literal, Optional
12
import pandas as pd
23
from datetime import datetime
34

5+
6+
from schemas.measurement import DeviatedParametersSchema, Measurement
7+
48
file_path = 'resources/plants_dataset.csv'
59
df = pd.read_csv(file_path)
610
DELTA = 5
@@ -10,11 +14,24 @@ def is_in_range(value, min, max):
1014
return min <= value <= max
1115

1216

13-
def check_t_rule(register, night_value, day_value):
17+
def is_deviated(value, min, max) -> Optional[Literal["lower", "higher"]]:
18+
if value < min:
19+
return "lower"
20+
21+
if value > max:
22+
return "higher"
23+
24+
return None
25+
26+
27+
def check_t_rule(
28+
register,
29+
night_value,
30+
day_value) -> Optional[Literal["lower", "higher"]]:
1431
if is_daytime():
15-
return is_in_range(register, day_value - DELTA, day_value + DELTA)
32+
return is_deviated(register, day_value - DELTA, day_value + DELTA)
1633
else:
17-
return is_in_range(register, night_value - DELTA, night_value + DELTA)
34+
return is_deviated(register, night_value - DELTA, night_value + DELTA)
1835

1936

2037
TEMP_RULES_MAP = {
@@ -24,22 +41,22 @@ def check_t_rule(register, night_value, day_value):
2441
}
2542

2643
HUMIDITY_RULES_MAP = {
27-
1: (is_in_range, (50, 100)),
28-
2: (is_in_range, (25, 50)),
29-
3: (is_in_range, (5, 25)),
44+
1: (is_deviated, (50, 100)),
45+
2: (is_deviated, (25, 50)),
46+
3: (is_deviated, (5, 25)),
3047
}
3148

3249
LIGHT_RULES_MAP = {
33-
1: (is_in_range, (350, 500)),
34-
2: (is_in_range, (200, 350)),
35-
3: (is_in_range, (75, 200)),
36-
4: (is_in_range, (25, 75)),
50+
1: (is_deviated, (350, 500)),
51+
2: (is_deviated, (200, 350)),
52+
3: (is_deviated, (75, 200)),
53+
4: (is_deviated, (25, 75)),
3754
}
3855

3956
WATERING_RULES_MAP = {
40-
1: (is_in_range, (70, 100)),
41-
2: (is_in_range, (40, 70)),
42-
3: (is_in_range, (10, 40)),
57+
1: (is_deviated, (70, 100)),
58+
2: (is_deviated, (40, 70)),
59+
3: (is_deviated, (10, 40)),
4360
}
4461

4562

@@ -48,52 +65,47 @@ def parse_values(string):
4865
return [int(value) for value in values]
4966

5067

51-
def apply_rules(register, plant_name):
68+
def eval_deviation(
69+
values: list[int],
70+
apply_rule_fn) -> Optional[Literal["lower", "higher"]]:
71+
72+
results = list(map(lambda v: apply_rule_fn(v), values))
73+
return None if any(map(lambda v: v is None, results)) else results[0]
74+
75+
76+
def apply_rules(
77+
register: Measurement,
78+
plant_name: str) -> DeviatedParametersSchema:
5279
plant_data = df[df['Botanical_Name'] == plant_name]
5380
h_value = plant_data['H'].values[0]
5481
l_value = plant_data['L'].values[0]
5582
t_value = plant_data['T'].values[0]
5683
w_value = plant_data['W'].values[0]
5784

58-
parameters = []
59-
6085
t_values = parse_values(t_value)
6186
h_values = parse_values(h_value)
6287
l_values = parse_values(l_value)
6388
w_values = parse_values(w_value)
6489

65-
is_temperature_deviated = all(
66-
not apply_temperature_rule(t_value, register.temperature)
67-
for t_value in t_values
68-
)
69-
if is_temperature_deviated:
70-
parameters.append('temperature')
71-
72-
is_watering_deviated = all(
73-
not apply_watering_rule(w_value, register.watering)
74-
for w_value in w_values
75-
)
76-
if is_watering_deviated:
77-
parameters.append('watering')
78-
79-
is_light_deviated = all(
80-
not apply_light_rule(l_value, register.light)
81-
for l_value in l_values
82-
)
83-
if is_light_deviated:
84-
parameters.append('light')
85-
86-
is_humidity_deviated = all(
87-
not apply_humidity_rule(h_value, register.humidity)
88-
for h_value in h_values
90+
return DeviatedParametersSchema(
91+
temperature=eval_deviation(
92+
t_values,
93+
lambda x: apply_temperature_rule(x, register.temperature)),
94+
humidity=eval_deviation(
95+
h_values,
96+
lambda x: apply_humidity_rule(x, register.humidity)),
97+
light=eval_deviation(
98+
l_values,
99+
lambda x: apply_light_rule(x, register.light)),
100+
watering=eval_deviation(
101+
w_values,
102+
lambda x: apply_watering_rule(x, register.watering)),
89103
)
90-
if is_humidity_deviated:
91-
parameters.append('humidity')
92-
93-
return parameters
94104

95105

96-
def apply_temperature_rule(rule, register):
106+
def apply_temperature_rule(
107+
rule,
108+
register) -> Optional[Literal["lower", "higher"]]:
97109
rule_function, rule_values = TEMP_RULES_MAP.get(rule, None)
98110
return rule_function(register, *rule_values)
99111

app/schemas/measurement.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,39 @@
1-
from pydantic import BaseModel
2-
from typing import Optional
1+
from pydantic import BaseModel, Field
2+
from typing import Optional, Literal
33

44

5-
class MeasurementSavedSchema(BaseModel):
6-
id: int
7-
id_plant: int
8-
plant_type: str
9-
time_stamp: str
5+
class DeviatedParametersSchema(BaseModel):
6+
temperature: Optional[Literal['lower', "higher"]] = Field(default=None)
7+
humidity: Optional[Literal['lower', "higher"]] = Field(default=None)
8+
light: Optional[Literal['lower', "higher"]] = Field(default=None)
9+
watering: Optional[Literal['lower', "higher"]] = Field(default=None)
10+
11+
def hasDeviations(self) -> bool:
12+
return (self.temperature is not None or
13+
self.humidity is not None or
14+
self.light is not None or
15+
self.watering is not None)
16+
17+
18+
class Measurement(BaseModel):
1019
temperature: Optional[int]
1120
humidity: Optional[int]
1221
light: Optional[int]
1322
watering: Optional[int]
1423

1524

16-
class MeasurementReadingSchema(BaseModel):
25+
class MeasurementSavedSchema(Measurement):
26+
id: int
27+
id_plant: int
28+
plant_type: str
29+
time_stamp: str
30+
deviations: Optional[DeviatedParametersSchema] = Field(None)
31+
32+
33+
class MeasurementReadingSchema(Measurement):
1734
"""
1835
Schema used to parse the data from measurements obtained via RabbitMQ.
1936
"""
20-
temperature: Optional[int]
21-
humidity: Optional[int]
22-
light: Optional[int]
23-
watering: Optional[int]
2437
id_device: str
2538
time_stamp: str
2639
device_token: Optional[str]

app/service/measurements.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from fastapi.responses import JSONResponse
33
from repository.measurements import MeasurementsRepository
44
from exceptions.MeasurementsException import PlantNotFound
5+
from schemas.measurement import MeasurementSavedSchema
6+
from resources.parser import apply_rules
57

68

79
class MeasurementsService:
@@ -13,6 +15,12 @@ def get_plant_last_measurement(self, id_plant):
1315
id_plant)
1416
if not last_measurement:
1517
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content={})
18+
last_measurement = MeasurementSavedSchema.model_validate(
19+
last_measurement.__dict__)
20+
last_measurement.deviations = apply_rules(
21+
last_measurement,
22+
last_measurement.plant_type
23+
)
1624
return last_measurement
1725

1826
def create_device_plant_relation(self, plant, device_plant):

app/service/rabbitmq/consumer.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from os import environ
2020
from firebase_admin import messaging
2121

22-
2322
Base = declarative_base(
2423
metadata=MetaData(schema=environ.get("POSTGRES_SCHEMA", "measurements_service"))
2524
)
@@ -84,7 +83,7 @@ def send_notification(self, id_user, measurement, error, details):
8483

8584
def apply_rules(self, measurement, device_plant):
8685
deviated_parameters = apply_rules(measurement, device_plant.plant_type)
87-
if len(deviated_parameters) > 0:
86+
if deviated_parameters.hasDeviations():
8887
raise DeviatedParametersError(deviated_parameters)
8988

9089
def save_measurement(self, measurement_from_rabbit, device_plant):

0 commit comments

Comments
 (0)