Skip to content

Commit 0d35cc8

Browse files
committed
feat: initialise tests with dev db seed
1 parent daeb8b6 commit 0d35cc8

File tree

10 files changed

+231
-152
lines changed

10 files changed

+231
-152
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,6 @@ GitHub.sublime-settings
136136
!.vscode/launch.json
137137
!.vscode/extensions.json
138138
.history
139+
140+
test_seed_data.csv
141+
db.sqlite3

bikes/tests/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
from model import *
2-
from views import *
1+
from .model import *
2+
from .views import *

bikes/tests/model.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ def setUp(self):
1212

1313
def test_bike_creation(self):
1414
# Fetch the bike object to ensure it was created correctly
15-
bike = Bikes.objects.get(date_time="2023-04-30 14:30:00+00")
16-
print(bike.date_time)
15+
bike = Bikes.objects.get(date_time="2023-04-30 14:00:00+00")
1716
self.assertEqual(bike.forecast, Decimal('103.12345'))
18-
self.assertEqual(bike.date_time.strftime("%Y-%m-%d %H:%M:%S"), "2023-04-30 14:30:00")
17+
self.assertEqual(bike.date_time.strftime("%Y-%m-%d %H:%M:%S"), "2023-04-30 14:00:00")

bikes/tests/views.py

+61-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,71 @@
11
from django.test import TestCase
22
from django.utils import timezone
33
from bikes.views import Bikes
4-
from datetime import datetime
4+
from bikes.utils.views import *
5+
from test_helpers import *
6+
from datetime import datetime, time, timedelta
57
from decimal import Decimal
8+
import csv
9+
import os.path
610

711
class BikesViewsTestCase(TestCase):
8-
# def setUp(self):
9-
# dt = timezone.make_aware(datetime(2023, 4, 30, 14, 00, 0))
10-
# Bikes.objects.create(actual=100.0, forecast=103.12345, upper=123.12345, lower=78.12345, date_time=dt)
12+
13+
def setUp(self):
14+
reader = csv.DictReader(open('test_seed_data.csv'))
15+
day_num = 0
16+
day = timezone.now() - timedelta(days=day_num+1)
17+
for row in reader:
18+
if day_num == row['day']:
19+
dt = timezone.make_aware(datetime.combine(day.date(), time(row['hour'], 0, 0)))
20+
Bikes.objects.create(actual=row['actual'], forecast=row['forecast'], upper=row['upper'], lower=row['lower'], date_time=dt)
21+
else:
22+
day_num = int(row['day'])
23+
day = timezone.now() - timedelta(days=day_num+1)
24+
dt = timezone.make_aware(datetime.combine(day.date(), time(int(row['hour']), 0, 0)))
25+
if row['actual'] == '':
26+
row['actual'] = 0
27+
Bikes.objects.create(
28+
actual=row['actual'],
29+
forecast=Decimal(row['forecast']), upper=Decimal(row['upper']), lower=Decimal(row['lower']), date_time=dt)
30+
31+
1132

1233

13-
# def test_bike_creation(self):
14-
# # Fetch the bike object to ensure it was created correctly
15-
# bike = Bikes.objects.get(date_time="2023-04-30 14:30:00+00")
16-
# print(bike.date_time)
17-
# self.assertEqual(bike.forecast, Decimal('103.12345'))
18-
# self.assertEqual(bike.date_time.strftime("%Y-%m-%d %H:%M:%S"), "2023-04-30 14:30:00")
1934

2035
# def test_get_data_from_open_data(self):
21-
# b = Bikes()
22-
print('toto')
36+
# """Let's see if we can get data from open data"""
37+
# data = get_data_from_open_data(id_compteur='100057445-103057445')
38+
# """We should be receiving data"""
39+
# self.assertNotEqual(data, "Waiting...")
40+
# """The data should be a list"""
41+
# self.assertIsInstance(data, list)
42+
# """Each entry in the list should have a 'sum_counts' and a 'date'"""
43+
# for obj in data:
44+
# self.assertTrue("sum_counts" in obj, f"'sum_counts' attribute is missing for {obj}")
45+
# self.assertTrue("date" in obj, f"'date' attribute is missing for {obj}")
46+
47+
async def test_get_treated_data(self):
48+
treated_data = await get_treated_data()
49+
print('hello',treated_data)
50+
self.assertNotEqual(treated_data, "Waiting...")
51+
52+
# async def test_e2e(self):
53+
# """Let's see if we can save the results to the DB"""
54+
# context = {}
55+
# data = get_data_from_open_data(id_compteur='100057445-103057445')
56+
# treated_data = await get_treated_data()
57+
# for item in data:
58+
# item['DateTime'] = change_date_string_to_datetime(item['date'])
59+
# today = pd.to_datetime(data[10]['DateTime']).tz_convert('Europe/Paris')
60+
# is_entry_today = await check_is_entry_today(today)
61+
# if not is_entry_today:
62+
# MLResult = runMLModel(merge_data(data, treated_data))[-72:]
63+
# await saveResultsToDB(MLResult)
64+
# for item in MLResult:
65+
# item['DateTime'] = item['DateTime'].strftime("%Y-%m-%d %H:%M:%S")
66+
# context["forecast"] = MLResult
67+
# else:
68+
# sorted_treated_data = sorted(treated_data, key=lambda x: x['DateTime'])
69+
# for item in sorted_treated_data:
70+
# item['DateTime'] = item['DateTime'].strftime("%Y-%m-%d %H:%M:%S")
71+
# context["forecast"] = sorted_treated_data[-72:]

bikes/utils/views.py

+120
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
import math
2+
import pandas as pd
13
import requests, time
24
import datetime
5+
from tzlocal import get_localzone
6+
from asgiref.sync import sync_to_async
7+
from pytz import NonExistentTimeError
8+
from bikes.models import Bikes
9+
from greykite.framework.templates.autogen.forecast_config import ForecastConfig, MetadataParam, ModelComponentsParam
10+
from greykite.framework.templates.forecaster import Forecaster
311

412
def get_data_from_open_data(date=None, id_compteur='100057445-103057445'):
513
url = "https://opendata.paris.fr/api/explore/v2.1/catalog/datasets/comptage-velo-donnees-compteurs/records"
@@ -26,3 +34,115 @@ def get_data_from_open_data(date=None, id_compteur='100057445-103057445'):
2634
return data
2735
else:
2836
return "Waiting..."
37+
38+
@sync_to_async
39+
def saveResultsToDB(result):
40+
local_tz = get_localzone() # Get local timezone
41+
for data in result:
42+
actual_data = None
43+
if not math.isnan(data['actual']):
44+
actual_data = data["actual"]
45+
try:
46+
# Convert string to datetime object and localize to 'Europe/Paris' timezone
47+
date_time = pd.to_datetime(data["DateTime"]).tz_localize('Europe/Paris')
48+
except NonExistentTimeError:
49+
# Handle NonExistentTimeError by using local timezone instead
50+
date_time = pd.to_datetime(data["DateTime"]).tz_localize(local_tz, ambiguous='NaT')
51+
result_to_save = Bikes(
52+
actual=actual_data,
53+
forecast=data["forecast"],
54+
upper=data["forecast_upper"],
55+
lower=data["forecast_lower"],
56+
date_time=date_time
57+
)
58+
print(result_to_save.__dict__)
59+
result_to_save.save_result()
60+
61+
def merge_data(data, treated_data):
62+
filtered_data = list(filter(lambda entry: entry['actual'] is not None, treated_data))
63+
treated_dates = {item['DateTime'] for item in filtered_data}
64+
unique_data = [item for item in data if item['DateTime'] not in treated_dates]
65+
abridged_treated_data = []
66+
for item in filtered_data:
67+
abridged_treated_data.append({'DateTime': item['DateTime'], 'sum_counts': item['actual']})
68+
merged_data = abridged_treated_data + unique_data
69+
return sorted(merged_data, key=lambda x: x['DateTime'])
70+
71+
def runMLModel(data):
72+
forecast_config = ForecastConfig(
73+
model_template = "SILVERKITE",
74+
forecast_horizon = 24,
75+
coverage = 0.85,
76+
metadata_param = MetadataParam(
77+
time_col = "DateTimeCol",
78+
value_col = "sum_counts",
79+
freq = "H",
80+
train_end_date=datetime.datetime.today()
81+
)
82+
)
83+
df = pd.DataFrame(data)
84+
df['DateTime'] = pd.to_datetime(df['DateTime'], utc=True, errors='coerce')
85+
df['DateTime'] = df['DateTime'].values
86+
df.sort_values(by='DateTime', inplace=True)
87+
df['DateTimeCol'] = df['DateTime']
88+
df.set_index('DateTime', inplace=True)
89+
90+
forecaster = Forecaster()
91+
silverkite = forecaster.run_forecast_config(
92+
df = df,
93+
config = forecast_config
94+
)
95+
96+
forecast_data_json = silverkite.forecast.df.to_dict()
97+
98+
last_24_entries = {}
99+
for forecast_key in forecast_data_json:
100+
if forecast_key == "DateTime":
101+
for y in forecast_data_json[forecast_key]:
102+
forecast_data_json[forecast_key][y] = forecast_data_json[forecast_key][y].strftime("%Y-%m-%d %X")
103+
forecast_value = dict(list(forecast_data_json[forecast_key].items())[-100:])
104+
else :
105+
forecast_value = dict(list(forecast_data_json[forecast_key].items())[-100:])
106+
for index_key in forecast_value:
107+
if index_key in last_24_entries:
108+
last_24_entries[index_key][forecast_key] = forecast_value[index_key]
109+
else:
110+
last_24_entries[index_key] = {forecast_key: forecast_value[index_key]}
111+
112+
result = list(last_24_entries.values())
113+
for item in result:
114+
if 'DateTimeCol' in item:
115+
item['DateTime'] = item.pop('DateTimeCol')
116+
117+
return result
118+
119+
@sync_to_async
120+
def get_treated_data():
121+
bikes_queryset = Bikes.objects.all()
122+
data = []
123+
for bikes_data in bikes_queryset:
124+
actual_data = None
125+
if bikes_data.actual != None:
126+
actual_data = float(bikes_data.actual)
127+
data.append({
128+
"actual":actual_data,
129+
"forecast":float(bikes_data.forecast),
130+
"forecast_upper":float(bikes_data.upper),
131+
"forecast_lower":float(bikes_data.lower),
132+
"DateTime":bikes_data.date_time
133+
})
134+
135+
return data
136+
137+
def change_date_string_to_datetime(date_string):
138+
return datetime.datetime.fromisoformat(date_string)
139+
140+
@sync_to_async
141+
def check_is_entry_today(today):
142+
todays_data = Bikes.objects.filter(date_time=today)
143+
print('doays_data', todays_data, today)
144+
if todays_data:
145+
print("todays_data", todays_data[0].__dict__)
146+
print("todays_data.exists() and todays_data[0].actual != None", todays_data.exists() and todays_data[0].actual != None)
147+
print("todays_data[0].actual != None", todays_data[0].actual != None, "todays_data[0]", todays_data[0].__dict__)
148+
return todays_data.exists() and todays_data[0].actual != None

0 commit comments

Comments
 (0)