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)
|