1
+ import datetime
1
2
import pandas as pd
2
3
import seaborn as sns
3
4
from pathlib import Path
4
- from shiny import App , ui
5
5
import matplotlib .pyplot as plt
6
+ from pymongo import MongoClient
7
+ from shiny import App , ui , reactive , render_ui , render
6
8
from maidr .widget .shiny import render_maidr
7
9
8
- file_path = Path (__file__ ).parent / "healthdata.csv"
9
- df = pd .read_csv (file_path )
10
10
11
11
def parse_date (date_str ):
12
+ """Try parsing the date in two possible formats."""
12
13
try :
13
- return pd .to_datetime (date_str , format = "%b %d, %Y" ) # Format: "Jun 13, 2024"
14
+ return pd .to_datetime (date_str , format = "%b %d, %Y" ) # e.g., "Jun 13, 2024"
14
15
except ValueError :
15
16
try :
16
17
return pd .to_datetime (date_str , format = "%d %b %Y" , dayfirst = True )
17
18
except ValueError :
18
- return pd .NaT # Handle invalid dates
19
+ return pd .NaT # Return NaT for invalid dates
19
20
20
- df ['parsed_date' ] = df ['date' ].apply (parse_date )
21
- df = df .dropna (subset = ['parsed_date' ]) # Remove rows with invalid parsed dates
21
+ def fetch_data ():
22
+ """Connect to Azure Cosmos DB, flatten the nested documents, and return a DataFrame."""
23
+ connection_string = (
24
+ "mongodb://pcha:DBQWfFLdaAwofXET3QyLDt1ndCAbJdGwoq8iF4u79P2A0QArmODzkbENAMqtobHZOhDn765q2dlmACDbeuMcHg=="
25
+ "@pcha.mongo.cosmos.azure.com:10255/?ssl=true&retrywrites=false&replicaSet=globaldb"
26
+ "&maxIdleTimeMS=120000&appName=@pcha@"
27
+ )
28
+ client = MongoClient (connection_string , tlsAllowInvalidCertificates = True )
29
+ db = client ['pcha_db' ]
30
+ collection = db ['health_data' ]
31
+ cursor = collection .find ()
22
32
23
- # min_date = df['parsed_date'].min()
24
- # max_date = df['parsed_date'].max()
33
+ flattened_data = []
34
+ for document in cursor :
35
+ _id = document ["_id" ]
36
+ user_id = document ["user_id" ]
37
+ for date_entry in document ["dates" ]:
38
+ flattened_entry = {"_id" : _id , "user_id" : user_id , "date" : date_entry ["date" ]}
39
+ # Unpack nested data values
40
+ for key , value in date_entry ["data" ].items ():
41
+ flattened_entry [key ] = value ["value" ]
42
+ flattened_data .append (flattened_entry )
43
+
44
+ df = pd .DataFrame (flattened_data )
45
+ # Parse dates and drop rows with invalid dates
46
+ df ['parsed_date' ] = df ['date' ].apply (parse_date )
47
+ df = df .dropna (subset = ['parsed_date' ])
48
+ return df
49
+
50
+ # Fetch the initial data when the app starts.
51
+ initial_df = fetch_data ()
52
+ users = initial_df ["user_id" ].unique ().tolist ()
25
53
26
- users = df ["user_id" ].unique ().tolist ()
54
+ # -------------------------------------------------------
55
+ # 2. Define the UI
56
+ # -------------------------------------------------------
27
57
28
- # Define the UI
29
58
app_ui = ui .page_fluid (
30
59
ui .h2 ("PCHA Health Dashboard" ),
31
- ui .page_sidebar (
60
+ ui .page_sidebar (
32
61
ui .sidebar (
62
+ # Render the user selection dynamically.
33
63
ui .input_select ("user_id" , label = "Select User" , choices = users ),
64
+ # Button to refresh the data.
34
65
ui .input_action_button ("refresh_data" , "Refresh Data" ),
66
+ # ui.input_action_button("reset_filter", "Reset Filters"),
67
+ # Additional inputs (e.g., for date filtering)
35
68
ui .input_text ("start_day" , label = "Start Day" , placeholder = "DD" ),
36
69
ui .input_text ("start_month" , label = "Start Month" , placeholder = "MM" ),
37
70
ui .input_text ("start_year" , label = "Start Year" , placeholder = "YYYY" ),
38
71
ui .input_text ("end_day" , label = "End Day" , placeholder = "DD" ),
39
72
ui .input_text ("end_month" , label = "End Month" , placeholder = "MM" ),
40
73
ui .input_text ("end_year" , label = "End Year" , placeholder = "YYYY" ),
41
- bg = "#f8f8f8"
42
- ),
43
- ui .navset_card_tab (
44
- ui .nav_panel ("Step Count" , ui .card (ui .output_ui ("plot_stepCount" ))),
45
- ui .nav_panel ("Distance Walking/Running" , ui .card (ui .output_ui ("plot_distance" ))),
46
- ui .nav_panel ("Exercise Time" , ui .card (ui .output_ui ("plot_exerciseTime" ))),
47
- ui .nav_panel ("Basal Energy Burned" , ui .card (ui .output_ui ("plot_basalEnergy" )),),
48
- ui .nav_panel ("Active Energy Burned" , ui .card (ui .output_ui ("plot_activeEnergy" ))),
49
- ),
50
- )
74
+ bg = "#f8f8f8"
75
+ ),
76
+ ui .navset_card_tab (
77
+ ui .nav_panel ("Step Count" , ui .card (ui .output_ui ("plot_stepCount" ))),
78
+ ui .nav_panel ("Distance Walking/Running" , ui .card (ui .output_ui ("plot_distance" ))),
79
+ ui .nav_panel ("Exercise Time" , ui .card (ui .output_ui ("plot_exerciseTime" ))),
80
+ ui .nav_panel ("Basal Energy Burned" , ui .card (ui .output_ui ("plot_basalEnergy" )),),
81
+ ui .nav_panel ("Active Energy Burned" , ui .card (ui .output_ui ("plot_activeEnergy" ))),
82
+ ),
83
+ )
51
84
)
52
85
53
- def server (inp , _ , __ ):
86
+
87
+ # -------------------------------------------------------
88
+ # 3. Define Server Logic
89
+ # -------------------------------------------------------
90
+
91
+ def server (input , output , session ):
92
+ data_store = reactive .Value (initial_df )
93
+
94
+ @reactive .Effect
95
+ def refresh_data_effect ():
96
+ _ = input .refresh_data ()
97
+ new_df = fetch_data ()
98
+ data_store .set (new_df )
99
+ print ("Data refreshed at" , datetime .datetime .now ())
100
+
101
+ # @reactive.Effect
102
+ # def reset_filter_effect():
103
+ # _ = input.reset_filter()
104
+ # df = fetch_data()
105
+ # start_date = df['parsed_date'].min()
106
+ # end_date = df['parsed_date'].max()
107
+
54
108
55
109
@render_maidr
56
110
def plot_stepCount ():
57
- user_data = df [df ["user_id" ] == inp .user_id ()]
58
- user_id = inp .user_id () # Selected user
59
-
60
- # Fetch start and end dates. If null, default to min and max dates
111
+ df_latest = data_store .get ()
112
+ user_id = input .user_id ()
113
+ df = df_latest [df_latest ["user_id" ] == user_id ]
61
114
try :
62
- start_date = pd .to_datetime (f"{ inp .start_year ()} -{ inp .start_month ()} -{ inp .start_day ()} " )
115
+ start_date = pd .to_datetime (f"{ input .start_year ()} -{ input .start_month ()} -{ input .start_day ()} " )
63
116
except ValueError :
64
117
start_date = df ['parsed_date' ].min () # Default to earliest date in the dataset
65
118
66
119
try :
67
- end_date = pd .to_datetime (f"{ inp .end_year ()} -{ inp .end_month ()} -{ inp .end_day ()} " )
120
+ end_date = pd .to_datetime (f"{ input .end_year ()} -{ input .end_month ()} -{ input .end_day ()} " )
68
121
except ValueError :
69
122
end_date = df ['parsed_date' ].max ()
70
123
@@ -74,6 +127,7 @@ def plot_stepCount():
74
127
(df ["parsed_date" ] >= start_date ) &
75
128
(df ["parsed_date" ] <= end_date )
76
129
]
130
+ user_data = user_data .sort_values (by = "parsed_date" )
77
131
78
132
fig , ax = plt .subplots (figsize = (10 , 8 ))
79
133
s_plot = sns .lineplot (data = user_data , x = "date" , y = "stepCount" , marker = "o" , ax = ax )
@@ -85,17 +139,18 @@ def plot_stepCount():
85
139
86
140
@render_maidr
87
141
def plot_distance ():
88
- user_data = df [df ["user_id" ] == inp .user_id ()]
89
- user_id = inp .user_id () # Selected user
142
+ df_latest = data_store .get ()
143
+ user_id = input .user_id ()
144
+ df = df_latest [df_latest ["user_id" ] == user_id ]
90
145
91
146
# Fetch start and end dates. If null, default to min and max dates
92
147
try :
93
- start_date = pd .to_datetime (f"{ inp .start_year ()} -{ inp .start_month ()} -{ inp .start_day ()} " )
148
+ start_date = pd .to_datetime (f"{ input .start_year ()} -{ input .start_month ()} -{ input .start_day ()} " )
94
149
except ValueError :
95
150
start_date = df ['parsed_date' ].min () # Default to earliest date in the dataset
96
151
97
152
try :
98
- end_date = pd .to_datetime (f"{ inp .end_year ()} -{ inp .end_month ()} -{ inp .end_day ()} " )
153
+ end_date = pd .to_datetime (f"{ input .end_year ()} -{ input .end_month ()} -{ input .end_day ()} " )
99
154
except ValueError :
100
155
end_date = df ['parsed_date' ].max ()
101
156
@@ -117,17 +172,18 @@ def plot_distance():
117
172
118
173
@render_maidr
119
174
def plot_basalEnergy ():
120
- user_data = df [df ["user_id" ] == inp .user_id ()]
121
- user_id = inp .user_id () # Selected user
175
+ df_latest = data_store .get ()
176
+ user_id = input .user_id ()
177
+ df = df_latest [df_latest ["user_id" ] == user_id ]
122
178
123
179
# Fetch start and end dates. If null, default to min and max dates
124
180
try :
125
- start_date = pd .to_datetime (f"{ inp .start_year ()} -{ inp .start_month ()} -{ inp .start_day ()} " )
181
+ start_date = pd .to_datetime (f"{ input .start_year ()} -{ input .start_month ()} -{ input .start_day ()} " )
126
182
except ValueError :
127
183
start_date = df ['parsed_date' ].min () # Default to earliest date in the dataset
128
184
129
185
try :
130
- end_date = pd .to_datetime (f"{ inp .end_year ()} -{ inp .end_month ()} -{ inp .end_day ()} " )
186
+ end_date = pd .to_datetime (f"{ input .end_year ()} -{ input .end_month ()} -{ input .end_day ()} " )
131
187
except ValueError :
132
188
end_date = df ['parsed_date' ].max ()
133
189
@@ -148,17 +204,18 @@ def plot_basalEnergy():
148
204
149
205
@render_maidr
150
206
def plot_activeEnergy ():
151
- user_data = df [df ["user_id" ] == inp .user_id ()]
152
- user_id = inp .user_id () # Selected user
207
+ df_latest = data_store .get ()
208
+ user_id = input .user_id ()
209
+ df = df_latest [df_latest ["user_id" ] == user_id ]
153
210
154
211
# Fetch start and end dates. If null, default to min and max dates
155
212
try :
156
- start_date = pd .to_datetime (f"{ inp .start_year ()} -{ inp .start_month ()} -{ inp .start_day ()} " )
213
+ start_date = pd .to_datetime (f"{ input .start_year ()} -{ input .start_month ()} -{ input .start_day ()} " )
157
214
except ValueError :
158
215
start_date = df ['parsed_date' ].min () # Default to earliest date in the dataset
159
216
160
217
try :
161
- end_date = pd .to_datetime (f"{ inp .end_year ()} -{ inp .end_month ()} -{ inp .end_day ()} " )
218
+ end_date = pd .to_datetime (f"{ input .end_year ()} -{ input .end_month ()} -{ input .end_day ()} " )
162
219
except ValueError :
163
220
end_date = df ['parsed_date' ].max ()
164
221
@@ -179,17 +236,18 @@ def plot_activeEnergy():
179
236
180
237
@render_maidr
181
238
def plot_exerciseTime ():
182
- user_data = df [df ["user_id" ] == inp .user_id ()]
183
- user_id = inp .user_id () # Selected user
239
+ df_latest = data_store .get ()
240
+ user_id = input .user_id ()
241
+ df = df_latest [df_latest ["user_id" ] == user_id ]
184
242
185
243
# Fetch start and end dates. If null, default to min and max dates
186
244
try :
187
- start_date = pd .to_datetime (f"{ inp .start_year ()} -{ inp .start_month ()} -{ inp .start_day ()} " )
245
+ start_date = pd .to_datetime (f"{ input .start_year ()} -{ input .start_month ()} -{ input .start_day ()} " )
188
246
except ValueError :
189
247
start_date = df ['parsed_date' ].min () # Default to earliest date in the dataset
190
248
191
249
try :
192
- end_date = pd .to_datetime (f"{ inp .end_year ()} -{ inp .end_month ()} -{ inp .end_day ()} " )
250
+ end_date = pd .to_datetime (f"{ input .end_year ()} -{ input .end_month ()} -{ input .end_day ()} " )
193
251
except ValueError :
194
252
end_date = df ['parsed_date' ].max ()
195
253
@@ -209,9 +267,11 @@ def plot_exerciseTime():
209
267
ax .tick_params (axis = "x" , rotation = 90 , labelsize = 5 )
210
268
return s_plot
211
269
212
- # Create the app
270
+ # -------------------------------------------------------
271
+ # 4. Create and Run the App
272
+ # -------------------------------------------------------
273
+
213
274
app = App (app_ui , server )
214
275
215
- # Run the app
216
276
if __name__ == "__main__" :
217
- app .run ()
277
+ app .run ()
0 commit comments