Spaces:
Build error
Build error
Update app.R
Browse files
app.R
CHANGED
@@ -1,58 +1,569 @@
|
|
|
|
1 |
library(shiny)
|
2 |
-
library(
|
3 |
library(dplyr)
|
4 |
-
library(
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
),
|
24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
)
|
26 |
|
|
|
|
|
27 |
server <- function(input, output, session) {
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
)
|
|
|
|
|
|
|
43 |
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
|
52 |
-
|
53 |
-
|
54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
}
|
57 |
|
58 |
-
shinyApp(ui, server)
|
|
|
1 |
+
#app.R
|
2 |
library(shiny)
|
3 |
+
library(BioAge)
|
4 |
library(dplyr)
|
5 |
+
#library(RSQLite)
|
6 |
+
library(googlesheets4)
|
7 |
+
library(googledrive)
|
8 |
+
library(shinyvalidate)
|
9 |
+
library(shinyjs)
|
10 |
+
library(plotly)
|
11 |
+
library(lubridate)
|
12 |
+
library(pander)
|
13 |
+
library(tidyr)
|
14 |
+
library(shiny.router)
|
15 |
+
|
16 |
+
|
17 |
+
|
18 |
+
#options(gargle_oauth_cache = ".secrets")
|
19 |
+
|
20 |
+
createReactiveDataset <- function() {
|
21 |
+
return(reactiveVal(data.frame(date = as.Date(character()), quantity = integer(), item = character())))
|
22 |
+
}
|
23 |
+
|
24 |
+
# Define dataset and its reactive variable
|
25 |
+
dataset <- createReactiveDataset()
|
26 |
+
|
27 |
+
# Dummy dataset for development
|
28 |
+
localDataset <- reactiveVal(data.frame(
|
29 |
+
date = as.Date(c('2024-12-01', '2024-12-02')),
|
30 |
+
quantity = c(10, 20),
|
31 |
+
item = c('Apples', 'Oranges')
|
32 |
+
))
|
33 |
+
|
34 |
+
# For local development, bypass authentication
|
35 |
+
isLocal <- TRUE
|
36 |
+
|
37 |
+
home_route <- route("/", function() {
|
38 |
+
fluidPage(
|
39 |
+
h1("Home Page"),
|
40 |
+
DT::dataTableOutput("dataTable")
|
41 |
+
)
|
42 |
+
})
|
43 |
+
|
44 |
+
|
45 |
+
checkRange <- function(value, min, max){
|
46 |
+
if(!is.na(value) & (value < min || value > max)){
|
47 |
+
paste0("Please specify a number that is between ", min, " and ", max, ", thank you!")
|
48 |
+
} else {
|
49 |
+
""
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
search_string = ""
|
54 |
+
query_params <- parseQueryString(search_string)
|
55 |
+
|
56 |
+
#if !is.null()
|
57 |
+
emiglio = query_params$useremail
|
58 |
+
nomignolo = paste0(query_params$firstname, " ", query_params$lastname)
|
59 |
+
|
60 |
+
phone_prefix_regex <- "^\\+?\\d{1,3}$"
|
61 |
+
phone_number_regex <- "^[0-9]{10,10}$"
|
62 |
+
|
63 |
+
validatePhone <- function(value, n_char_min){
|
64 |
+
if(value != ""){
|
65 |
+
|
66 |
+
if(grepl("^[0-9]{1,100}$", value)){
|
67 |
+
if (nchar(value) == n_char_min) {
|
68 |
+
""
|
69 |
+
} else{
|
70 |
+
paste0("Please specify a number that contains ", n_char_min, " digits")
|
71 |
+
}
|
72 |
+
}else{
|
73 |
+
"Please enter only digits"
|
74 |
+
}
|
75 |
+
} else{
|
76 |
+
""
|
77 |
+
}
|
78 |
+
|
79 |
+
}
|
80 |
+
|
81 |
+
|
82 |
+
ui <- fluidPage(
|
83 |
+
tags$head(
|
84 |
+
tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"),
|
85 |
+
tags$style(HTML("
|
86 |
+
.control-label {
|
87 |
+
color: black !important;
|
88 |
+
}
|
89 |
+
|
90 |
+
.strongy{
|
91 |
+
background-color: #c8e6c9;
|
92 |
+
padding: 10px
|
93 |
+
}
|
94 |
+
|
95 |
+
.form-control{
|
96 |
+
border-color: grey !important
|
97 |
+
}
|
98 |
+
|
99 |
+
#loading {
|
100 |
+
display: none;
|
101 |
+
text-align: center;
|
102 |
+
}
|
103 |
+
.loader {
|
104 |
+
border: 6px solid #f3f3f3; /* Light grey */
|
105 |
+
border-top: 6px solid #3498db; /* Blue */
|
106 |
+
border-radius: 50%;
|
107 |
+
width: 50px;
|
108 |
+
height: 50px;
|
109 |
+
animation: spin 2s linear infinite;
|
110 |
+
margin: 20px auto;
|
111 |
+
}
|
112 |
+
@keyframes spin {
|
113 |
+
0% { transform: rotate(0deg); }
|
114 |
+
100% { transform: rotate(360deg); }
|
115 |
+
}
|
116 |
+
"))
|
117 |
),
|
118 |
+
useShinyjs(),
|
119 |
+
titlePanel("Biological Age Calculator"),
|
120 |
+
|
121 |
+
sidebarLayout(
|
122 |
+
sidebarPanel(
|
123 |
+
fluidRow(
|
124 |
+
column(6, textInput("Name", "Enter name", query_params$firstname)),
|
125 |
+
column(6, textInput("Surname", "Enter your last name", query_params$lastname))
|
126 |
+
),
|
127 |
+
fluidRow(
|
128 |
+
column(6, textInput("phone_prefix", "Enter Country Code", "+91")),
|
129 |
+
column(6, textInput("phone_number", "Enter your phone number", ""))
|
130 |
+
),
|
131 |
+
dateInput("dob", "Date of Birth*", value="1990-01-01"),
|
132 |
+
dateInput("bloodTestDate", "Date of TEST", value = Sys.Date()),
|
133 |
+
numericInput("albumin", "Albumin (g/dL)", value = NA, min = 0),
|
134 |
+
verbatimTextOutput("values"),
|
135 |
+
numericInput("lymph", "Lymphocytes (%)", value = NA, min = 0),
|
136 |
+
numericInput("mcv", "Mean Cell Volume (MCV)", value = NA, min = 0),
|
137 |
+
numericInput("glucose", "Glucose (mg/L)", value = NA, min = 0),
|
138 |
+
numericInput("rdw", "Red Cell Dist Width (RDW)", value = NA, min = 0),
|
139 |
+
numericInput("creat", "Creatinine (mg/dL)", value = NA, min = 0),
|
140 |
+
numericInput("crp", "CRP (mg/L)", value = NA, min = 0),
|
141 |
+
numericInput("alp", "Alkaline Phosphatase (U/L)", value = NA, min = 0),
|
142 |
+
numericInput("wbc", "White Blood Cells (cells/mL)", value = NA, min = 0),
|
143 |
+
actionButton("submit", "Submit", title = "Fill in the phone number field to enable")
|
144 |
+
),
|
145 |
+
mainPanel(
|
146 |
+
tabsetPanel(
|
147 |
+
tabPanel("Calculator",
|
148 |
+
div(id = "loading", class = "loader", style = "display: none"), # Loading spinner
|
149 |
+
uiOutput("results"),
|
150 |
+
uiOutput("message")),
|
151 |
+
tabPanel("My historical data - Plot",
|
152 |
+
uiOutput("notlogged"),
|
153 |
+
actionLink("openLinkButton", "Create an account here / log in here", href = "", style = "background-color:#c8e6c9; padding: 10px; font-size: 2em"),
|
154 |
+
plotOutput("biologicalAgePlot"))
|
155 |
+
,
|
156 |
+
tabPanel("My historical data - Table",
|
157 |
+
tableOutput("table"),
|
158 |
+
actionLink("openLinkButton", "Create an account here / log in here", href = "", style = "background-color:#c8e6c9; padding: 10px; font-size: 2em"),
|
159 |
+
uiOutput("notlogged1"))
|
160 |
+
)
|
161 |
+
)
|
162 |
+
)
|
163 |
)
|
164 |
|
165 |
+
|
166 |
+
|
167 |
server <- function(input, output, session) {
|
168 |
+
sheet_id <- "https://docs.google.com/spreadsheets/d/1xQpghper_5FCWkYFByIpdVKFmBofgM7uVrG_bsaGW58/edit?gid=0#gid=0"
|
169 |
+
values <- reactiveVal(data.frame())
|
170 |
+
|
171 |
+
router_server(
|
172 |
+
list(home_route)
|
173 |
+
)
|
174 |
+
|
175 |
+
# Dummy dataset
|
176 |
+
#dataset <- data.frame(date = as.Date(c('2024-12-01', '2024-12-02')),
|
177 |
+
#quantity = c(10, 20),
|
178 |
+
#item = c('Apples', 'Oranges'))
|
179 |
+
|
180 |
+
# Render the dataset in a table
|
181 |
+
#output$dataTable <- DT::renderDataTable({
|
182 |
+
#dataset
|
183 |
+
#})
|
184 |
|
185 |
+
|
186 |
+
observeEvent(input$submit, {
|
187 |
+
shinyjs::show(id = "loading") # Show loading spinner
|
188 |
+
|
189 |
+
# Collecting inputs
|
190 |
+
name <- paste0(input$Name, " ", input$Surname)
|
191 |
+
ageAtTestDate <- round(as.numeric(difftime(Sys.Date(), input$dob, units = "days")) / 365.25, 1)
|
192 |
+
biological_age <- runif(1, min = ageAtTestDate - 5, max = ageAtTestDate + 5) # Simulated calculation
|
193 |
+
|
194 |
+
bioage_color <- if (biological_age < ageAtTestDate) "green" else if (biological_age > ageAtTestDate) "red" else "yellow"
|
195 |
+
|
196 |
+
# Show the BioAge Modal
|
197 |
+
show_bioage_modal(name, ageAtTestDate, biological_age, bioage_color)
|
198 |
+
|
199 |
+
output$results <- renderUI({
|
200 |
+
paste("Thank you for submitting your details. Your biological age will be calculated based on the provided data.")
|
201 |
+
})
|
202 |
+
|
203 |
+
})
|
204 |
+
|
205 |
+
# Define show_bioage_modal function outside observeEvent
|
206 |
+
show_bioage_modal <- function(name, ageAtTestDate, biological_age, bioage_color) {
|
207 |
+
diff <- round(abs(biological_age - ageAtTestDate), 2)
|
208 |
+
message <- if (biological_age < ageAtTestDate) {
|
209 |
+
tags$p(
|
210 |
+
paste0(
|
211 |
+
name,", your healthy choices show! your BioAge is ", diff, " years younger than your calendar Age."
|
212 |
+
),
|
213 |
+
style = "padding-bottom: 24px; font-size: 16px; font-weight: bold; font-family: sans-serif; font-variant: contextual;"
|
214 |
+
)
|
215 |
+
} else if (biological_age > ageAtTestDate) {
|
216 |
+
tags$p(
|
217 |
+
paste0(
|
218 |
+
name,", your BioAge is ", diff, " years higher than your Calendar Age. Don’t worry—small, consistent steps in the right direction can help you close the gap!"
|
219 |
+
),
|
220 |
+
style = "padding-bottom: 24px; font-size: 16px; font-weight: bold; font-family: sans-serif; font-variant: contextual;"
|
221 |
+
)
|
222 |
+
} else {
|
223 |
+
tags$p(
|
224 |
+
paste0(
|
225 |
+
name,", your BioAge matches your Calendar Age, indicating balanced health markers."
|
226 |
+
),
|
227 |
+
style = "padding-bottom: 24px; font-size: 16px; font-weight: bold; font-family: sans-serif; font-variant: contextual;"
|
228 |
+
)
|
229 |
+
}
|
230 |
+
|
231 |
+
showModal(
|
232 |
+
modalDialog(
|
233 |
+
title = NULL,
|
234 |
+
div(
|
235 |
+
style = "background-color: #0E406B; color: white; padding: 24px; text-align: center;
|
236 |
+
border-top-left-radius: 5px; border-top-right-radius: 5px; margin: -15px -15px 0 -15px; font-family: 'Archia', sans-serif;",
|
237 |
+
h3(
|
238 |
+
paste0(name,"'s BioAge Results"),
|
239 |
+
style = "margin: 0; padding: 0; font-size: 26px; text-align: center; font-family: 'Archia', sans-serif; font-variant: common-ligatures; color: white; line-height: 1;"
|
240 |
+
)
|
241 |
+
),
|
242 |
+
div(
|
243 |
+
style = "background-color: white; text-align: center; font-family: 'Archia', sans-serif; padding-top: 20px;",
|
244 |
+
fluidRow(
|
245 |
+
column(5, div(
|
246 |
+
tags$p("Calendar Age", style = "font-weight: bold; font-size: 20px; color: black;"),
|
247 |
+
tags$p(paste(ageAtTestDate, "years"), style = "font-size: 24px; font-weight: bold; color: black;")
|
248 |
+
)),
|
249 |
+
column(2, div(
|
250 |
+
tags$p("→", style = "font-size: 59px; font-weight: bold; color: darkblue; margin-top: 8px;")
|
251 |
+
)),
|
252 |
+
column(5, div(
|
253 |
+
tags$p("BioAge", style = "font-weight: bold; font-size: 20px; color: black;"),
|
254 |
+
tags$p(
|
255 |
+
paste(round(biological_age, 2), "years"),
|
256 |
+
style = sprintf("font-size: 24px; font-weight: bold; color: %s;", bioage_color)
|
257 |
+
)
|
258 |
+
))
|
259 |
+
)
|
260 |
+
),
|
261 |
+
div(
|
262 |
+
style = "text-align: center; margin-top: 20px; color: #333333; margin-bottom: 0px;",
|
263 |
+
message
|
264 |
+
),
|
265 |
+
div(
|
266 |
+
style = "text-align: center;",
|
267 |
+
tags$p("Complete report sent to your WhatsApp",
|
268 |
+
tags$i(class = "fa-brands fa-whatsapp fa-beat",
|
269 |
+
style = "color: #25D366; margin-left: 5px; font-size: 16px;"),
|
270 |
+
style = "font-size: 14px; color: black; margin-bottom: 0px;"),
|
271 |
+
tags$p("xxxxxxxx4321", style = "font-size: 14px; color: black; margin-bottom: -16px;")
|
272 |
+
),
|
273 |
+
easyClose = FALSE,
|
274 |
+
footer = tags$button(
|
275 |
+
"Close",
|
276 |
+
onclick = "Shiny.setInputValue('close_modal', true);",
|
277 |
+
style = "background-color: #86D1F1; color: black; border: none; padding: 10px 20px; border-radius: 5px; font-size: 16px; cursor: pointer;"
|
278 |
)
|
279 |
+
)
|
280 |
+
)
|
281 |
+
}
|
282 |
|
283 |
+
output$message <- renderUI({
|
284 |
+
HTML("<div class='biological-age-text'>
|
285 |
+
<h2>What is Biological Age?</h2>
|
286 |
+
<p>Biological age refers to how old a person seems based on the functioning and condition of their
|
287 |
+
body systems, rather than the time since birth (chronological age). It is influenced by genetics,
|
288 |
+
lifestyle, and environmental factors, and can provide a more accurate representation of an
|
289 |
+
individual's health and longevity prospects.</p>
|
290 |
+
<h3 class = 'strongy'>Our tool can estimate your biological age using even one blood test parameter. However, for the most accurate results, we recommend including as many parameters as possible.</h3>
|
291 |
+
<h2>Here is what each of the parameters mean for your health:</h2>
|
292 |
+
<ol>
|
293 |
+
<li><strong>Albumin:</strong> A protein in the blood that helps maintain fluid balance and transport hormones,
|
294 |
+
vitamins, and drugs; low levels can indicate liver or kidney disease.</li>
|
295 |
+
<li><strong>Creatinine:</strong> A waste product from muscle metabolism; its blood level is a marker of kidney
|
296 |
+
function, with high levels indicating potential kidney impairment.</li>
|
297 |
+
<li><strong>Glucose:</strong> The main sugar found in the blood and the body's primary energy source; levels
|
298 |
+
are used to diagnose and monitor diabetes.</li>
|
299 |
+
<li><strong>C-reactive Protein (CRP):</strong> A substance produced by the liver in response to inflammation;
|
300 |
+
high CRP levels can indicate infection or chronic inflammatory diseases.</li>
|
301 |
+
<li><strong>Lymphocyte (Lymphs):</strong> A type of white blood cell involved in the immune response;
|
302 |
+
changes in lymphocyte levels can indicate infections, autoimmune diseases, or blood disorders.</li>
|
303 |
+
<li><strong>Mean Cell Volume (MCV):</strong> The average size of red blood cells; it helps diagnose and classify
|
304 |
+
anemias, with high MCV indicating macrocytic anemia and low MCV indicating microcytic
|
305 |
+
anemia.</li>
|
306 |
+
<li><strong>Red Cell Distribution Width (RDW):</strong> A measure of the variation in red blood cell size;
|
307 |
+
increased RDW can indicate anemia and has been associated with cardiovascular diseases.</li>
|
308 |
+
<li><strong>Alkaline Phosphatase:</strong> An enzyme found in the liver, bones, and other tissues; high levels
|
309 |
+
can indicate liver disease, bone disorders, or bile duct obstruction.</li>
|
310 |
+
<li><strong>White Blood Cells (WBCs):</strong> Cells in the immune system that help defend against infections;
|
311 |
+
abnormal levels can indicate infection, inflammation, or immune system disorders.</li>
|
312 |
+
</ol>
|
313 |
+
|
314 |
+
<h2>Based on scientific data:</h2>
|
315 |
+
<p>We have based this calculation of your biological age on scientific data from the National Health
|
316 |
+
and Nutrition Examination Survey (NHANES). The package uses published biomarker
|
317 |
+
algorithms to calculate three biological aging measures: Klemera-Doubal Method (KDM)
|
318 |
+
biological age, phenotypic age, and homeostatic dysregulation.</p>
|
319 |
+
|
320 |
+
<h2>Citation:</h2>
|
321 |
+
<p>Kwon, D., Belsky, D.W. A toolkit for quantification of biological age from blood chemistry and organ function
|
322 |
+
test data: BioAge. GeroScience 43, 2795–2808 (2021).</p>
|
323 |
+
|
324 |
+
<h2>Disclaimer:</h2>
|
325 |
+
<p>These results are solely for informational use and shouldn't replace professional medical advice,
|
326 |
+
diagnosis, or treatment. Always seek the guidance of a healthcare provider for any medical
|
327 |
+
conditions or before making changes to your health regimen, including starting new diets,
|
328 |
+
exercises, or supplements, or altering medication. Never discontinue medication or follow any
|
329 |
+
health advice without consulting your healthcare provider.</p></div>")
|
330 |
+
})
|
331 |
+
|
332 |
|
333 |
+
|
334 |
+
observeEvent(input$submit, {
|
335 |
+
# Handle form submission
|
336 |
+
output$results <- renderUI({
|
337 |
+
paste("Thank you for submitting your details. Your biological age will be calculated based on the provided data.")
|
338 |
+
})
|
339 |
+
})
|
340 |
+
|
341 |
+
output$notlogged <- renderUI({
|
342 |
+
if (is.null(emiglio)){
|
343 |
+
HTML("<div class='biological-age-text'>
|
344 |
+
<h2>Please log in to View your history</h2>")
|
345 |
+
} else {
|
346 |
+
""
|
347 |
+
}
|
348 |
+
|
349 |
+
})
|
350 |
+
|
351 |
+
output$notlogged1 <- renderUI({
|
352 |
+
if (is.null(emiglio)){
|
353 |
+
HTML("<div class='biological-age-text'>
|
354 |
+
<h2>Please log in to View your history</h2>")
|
355 |
+
} else {
|
356 |
+
""
|
357 |
+
}
|
358 |
+
|
359 |
+
})
|
360 |
+
|
361 |
+
|
362 |
+
output$biologicalAgePlot <- renderPlot(
|
363 |
+
if (!is.null(emiglio)){
|
364 |
+
if(nrow(values %>% filter(email == emiglio))>0){
|
365 |
+
values %>%
|
366 |
+
filter(email == emiglio) %>%
|
367 |
+
select(c(age, age_bio, date)) %>%
|
368 |
+
group_by(date) %>%
|
369 |
+
mutate(age = mean(age, na.rm = T), age_bio = mean(age_bio, na.rm = T)) %>%
|
370 |
+
rename(
|
371 |
+
`Actual age` = age,
|
372 |
+
`Biological age` = age_bio
|
373 |
+
) %>%
|
374 |
+
mutate(Date = as_date(date)) %>%
|
375 |
+
pivot_longer(cols = c(1:2), names_to = "name", values_to = "value") %>%
|
376 |
+
ggplot(aes(
|
377 |
+
x = Date,
|
378 |
+
y = value,
|
379 |
+
color = name
|
380 |
+
)
|
381 |
+
)+
|
382 |
+
geom_line(size = 1.3)+
|
383 |
+
theme_bw()+
|
384 |
+
scale_color_discrete(name = "")+
|
385 |
+
scale_y_continuous(breaks = round(seq(min(c(values$age, values$age_bio)), max(c(values$age, values$age_bio)), by = 2),0))
|
386 |
+
}
|
387 |
+
}
|
388 |
+
)
|
389 |
+
|
390 |
+
output$table <- renderTable(
|
391 |
+
if (!is.null(emiglio)){
|
392 |
+
|
393 |
+
if(nrow(values %>% filter(email == emiglio))>0){
|
394 |
+
values %>%
|
395 |
+
filter(email == emiglio) %>%
|
396 |
+
mutate_at(c(1, 11), round, 1) %>%
|
397 |
+
select(c(15, 2:10, 1, 11)) %>%
|
398 |
+
#arrange(-date) %>%
|
399 |
+
mutate(date = as.character(as.Date(date)))%>%
|
400 |
+
rename(
|
401 |
+
Creatinine = creat,
|
402 |
+
`Lymphocyte (Lymphs)` = lymph,
|
403 |
+
`CRP (C-reactive Protein)` = crp,
|
404 |
+
`White Blood Cells` = wbc,
|
405 |
+
`Mean Cell Volume` = mcv,
|
406 |
+
`Red cells distribution width` = rdw,
|
407 |
+
Glucose = glucose,
|
408 |
+
`Alkaline Phosphatase` = alp,
|
409 |
+
`Actual Age` = age,
|
410 |
+
`Biological Age` = age_bio
|
411 |
+
)
|
412 |
+
|
413 |
+
}
|
414 |
+
}
|
415 |
)
|
416 |
+
|
417 |
+
observe({
|
418 |
+
iv <- InputValidator$new()
|
419 |
+
iv$add_rule("albumin", checkRange, 2, 8)
|
420 |
+
iv$add_rule("creat", checkRange, .2, 2)
|
421 |
+
iv$add_rule("rdw", checkRange, 10, 20)
|
422 |
+
iv$add_rule("crp", checkRange, 0, 50)
|
423 |
+
iv$add_rule("lymph", checkRange, 0, 100)
|
424 |
+
iv$add_rule("glucose", checkRange, 40, 400)
|
425 |
+
iv$add_rule("wbc", checkRange, 2, 25)
|
426 |
+
iv$add_rule("alp", checkRange, 0, 300)
|
427 |
+
iv$add_rule("mcv", checkRange, 70, 120)
|
428 |
+
iv$add_rule("phone_number", validatePhone, 10)
|
429 |
+
|
430 |
+
iv$enable()
|
431 |
+
|
432 |
+
output$values <- renderPrint({
|
433 |
+
req(iv$is_valid())
|
434 |
+
})
|
435 |
+
})
|
436 |
+
|
437 |
+
observeEvent(input$submit, {
|
438 |
+
shinyjs::show(id = "loading") # Show loading spinner
|
439 |
+
|
440 |
+
nome = paste0(input$Name, " ", input$Surname)
|
441 |
+
|
442 |
+
ageAtTestDate <- round(as.numeric(difftime(Sys.Date(), input$dob, units = "days")) / 365.25, 1)
|
443 |
+
|
444 |
+
if (!is.null(input$dob)) {
|
445 |
+
allInputs <- data.frame(
|
446 |
+
age = as.numeric(difftime(Sys.Date(), input$dob, units = "days")) / 365.25,
|
447 |
+
albumin = input$albumin,
|
448 |
+
lymph = input$lymph,
|
449 |
+
mcv = input$mcv,
|
450 |
+
glucose = input$glucose,
|
451 |
+
rdw = input$rdw,
|
452 |
+
creat = input$creat,
|
453 |
+
crp = input$crp,
|
454 |
+
alp = input$alp,
|
455 |
+
wbc = input$wbc
|
456 |
+
)
|
457 |
+
}
|
458 |
+
|
459 |
+
if (!is.null(input$phone_number) && !is.null(phone_number_regex) &&
|
460 |
+
grepl(phone_prefix_regex, input$phone_prefix) &&
|
461 |
+
grepl(phone_number_regex, input$phone_number)) {
|
462 |
+
validated_phone <- paste(input$phone_prefix, input$phone_number)
|
463 |
+
} else {
|
464 |
+
validated_phone = NA
|
465 |
+
}
|
466 |
+
|
467 |
+
mrkrs = colnames(allInputs[, colSums(is.na(allInputs)) < nrow(allInputs)])
|
468 |
+
|
469 |
+
train = phenoage_calc(NHANES3, biomarkers = mrkrs)
|
470 |
+
|
471 |
+
biological_age <- phenoage_calc(allInputs, biomarkers = mrkrs, fit = train$fit)$data$phenoage
|
472 |
+
|
473 |
+
allInputs_tosave = allInputs %>%
|
474 |
+
mutate(
|
475 |
+
age_bio = biological_age,
|
476 |
+
email = ifelse(is.null(emiglio), "", emiglio),
|
477 |
+
name = nome,
|
478 |
+
phone = validated_phone,
|
479 |
+
date= input$bloodTestDate
|
480 |
+
)
|
481 |
+
|
482 |
+
biologicalAge <- if (!is.null(input$dob)) {
|
483 |
+
paste("Your biological age is approximately:", round(biological_age, 1), "years")
|
484 |
+
} else {
|
485 |
+
"Please enter at least your date of birth"
|
486 |
+
}
|
487 |
+
|
488 |
+
observeEvent(input$close_modal, {
|
489 |
+
removeModal()
|
490 |
+
})
|
491 |
+
|
492 |
+
output$results <- renderUI(
|
493 |
+
HTML(
|
494 |
+
paste0(
|
495 |
+
"<div style='background-color: #c8e6c9; padding: 10px;'>",
|
496 |
+
"<h2>Hello ", nome, "</h2>",
|
497 |
+
"<p>Your biological age is ", round(biological_age, 1), " years </p>",
|
498 |
+
"<p>Your actual age is ", ageAtTestDate, " years</p></div>"
|
499 |
+
)
|
500 |
+
)
|
501 |
+
)
|
502 |
+
|
503 |
+
# Append data to Google Sheet
|
504 |
+
if (nrow(allInputs_tosave) == 1) {
|
505 |
+
if (nrow(values()) == 0) {
|
506 |
+
sheet_write(data = allInputs_tosave, ss = sheet_id, sheet = "longevity")
|
507 |
+
} else {
|
508 |
+
sheet_append(data = allInputs_tosave, ss = sheet_id, sheet = "longevity")
|
509 |
+
}
|
510 |
+
}
|
511 |
+
|
512 |
+
# Update values to reflect the new data
|
513 |
+
values(read_sheet(ss = sheet_id, sheet = "longevity"))
|
514 |
+
|
515 |
+
shinyjs::hide(id = "loading") # Hide loading spinner
|
516 |
+
|
517 |
+
# Generate biological age plot
|
518 |
+
output$biologicalAgePlot <- renderPlot({
|
519 |
+
if (!is.null(emiglio)) {
|
520 |
+
if (nrow(values() %>% filter(email == emiglio)) > 0) {
|
521 |
+
values() %>%
|
522 |
+
filter(email == emiglio) %>%
|
523 |
+
select(c(age, age_bio, date)) %>%
|
524 |
+
group_by(date) %>%
|
525 |
+
mutate(age = mean(age, na.rm = TRUE), age_bio = mean(age_bio, na.rm = TRUE)) %>%
|
526 |
+
rename(`Actual age` = age, `Biological age` = age_bio) %>%
|
527 |
+
mutate(Date = as_date(date)) %>%
|
528 |
+
pivot_longer(cols = c(1:2), names_to = "name", values_to = "value") %>%
|
529 |
+
ggplot(aes(x = Date, y = value, color = name)) +
|
530 |
+
geom_line(size = 1.3) +
|
531 |
+
theme_bw() +
|
532 |
+
scale_color_discrete(name = "") +
|
533 |
+
scale_y_continuous(breaks = round(seq(min(c(values()$age, values()$age_bio)),
|
534 |
+
max(c(values()$age, values()$age_bio)), by = 2), 0))
|
535 |
+
}
|
536 |
+
}
|
537 |
+
})
|
538 |
+
|
539 |
+
# Generate biological age table
|
540 |
+
output$table <- renderTable({
|
541 |
+
if (!is.null(emiglio)) {
|
542 |
+
if (nrow(values() %>% filter(email == emiglio)) > 0) {
|
543 |
+
values() %>%
|
544 |
+
filter(email == emiglio) %>%
|
545 |
+
mutate_at(c(1, 11), round, 1) %>%
|
546 |
+
select(c(15, 2:10, 1, 11)) %>%
|
547 |
+
mutate(date = as.character(as.Date(date))) %>%
|
548 |
+
rename(
|
549 |
+
Creatinine = creat,
|
550 |
+
`Lymphocyte (Lymphs)` = lymph,
|
551 |
+
`CRP (C-reactive Protein)` = crp,
|
552 |
+
`White Blood Cells` = wbc,
|
553 |
+
`Mean Cell Volume` = mcv,
|
554 |
+
`Alkaline Phosphatase` = alp,
|
555 |
+
Glucose = glucose,
|
556 |
+
`Red cells distribution width` = rdw,
|
557 |
+
`Actual Age` = age,
|
558 |
+
`Biological Age` = age_bio
|
559 |
+
)
|
560 |
+
}
|
561 |
+
}
|
562 |
+
})
|
563 |
+
})
|
564 |
+
|
565 |
+
|
566 |
+
|
567 |
}
|
568 |
|
569 |
+
shinyApp(ui = ui, server = server)
|